From 1ea76c3f9ab30a90707efbaaad4ac5cbc8c3a292 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Fri, 27 Dec 2024 14:17:17 -0500 Subject: [PATCH 001/108] [SPARKNLP-1105] Introducing AlbertForMultipleChoice --- ...in_Spark_NLP_AlbertForMultipleChoice.ipynb | 529 +++ ...X_in_Spark_NLP_BertForMultipleChoice.ipynb | 72 +- ...in_Spark_NLP_AlbertForMultipleChoice.ipynb | 2903 +++++++++++++++++ .../annotator/classifier_dl/__init__.py | 1 + .../albert_for_multiple_choice.py | 161 + python/sparknlp/internal/__init__.py | 9 + .../albert_for_multiple_choice_test.py | 79 + .../ml/ai/AlbertClassification.scala | 90 + .../dl/AlbertForMultipleChoice.scala | 356 ++ .../dl/AlbertForMultipleChoiceTest.scala | 56 + 10 files changed, 4188 insertions(+), 68 deletions(-) create mode 100644 examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_AlbertForMultipleChoice.ipynb create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_AlbertForMultipleChoice.ipynb create mode 100644 python/sparknlp/annotator/classifier_dl/albert_for_multiple_choice.py create mode 100644 python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_AlbertForMultipleChoice.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_AlbertForMultipleChoice.ipynb new file mode 100644 index 00000000000000..c17e5a0a3dc99b --- /dev/null +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_AlbertForMultipleChoice.ipynb @@ -0,0 +1,529 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "PAsu8UVGoLVf" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_AlbertForMultipleChoice.ipynb)\n", + "\n", + "## Import ONNX AlbertForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- ONNX support was introduced in `Spark NLP 5.0.0`, enabling high performance inference for models.\n", + "- `AlbertForMultipleChoice` is only available since in `Spark NLP 5.6.0` and after. So please make sure you have upgraded to the latest Spark NLP release\n", + "- You can import ALBERT models trained/fine-tuned for question answering via `AlbertForMultipleChoice` or `AlbertForMultipleChoice`. These models are usually under `Multiple Choice` category and have `bert` in their labels\n", + "- Reference: [AlbertForMultipleChoice](https://huggingface.co/docs/transformers/main/en/model_doc/albert#transformers.AlbertForMultipleChoice)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OzijcdtQpOx9" + }, + "source": [ + "## Export and Save HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MlgoClMXpSg4" + }, + "source": [ + "- Let's install `transformers` package with the `onnx` extension and it's dependencies. You don't need `onnx` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cJWbob-kHICU", + "outputId": "d05b0dac-d342-40b4-aafc-f8ebd52d97a7" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m424.1/424.1 kB\u001b[0m \u001b[31m30.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m113.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m212.7/212.7 kB\u001b[0m \u001b[31m21.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m4.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m480.6/480.6 kB\u001b[0m \u001b[31m39.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m84.5/84.5 kB\u001b[0m \u001b[31m8.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m62.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m455.8/455.8 kB\u001b[0m \u001b[31m39.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m13.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m179.3/179.3 kB\u001b[0m \u001b[31m19.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m9.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m15.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m106.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.5/55.5 kB\u001b[0m \u001b[31m5.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m19.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q --upgrade transformers[onnx] optimum" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XtewR2xdOa5s" + }, + "source": [ + "- HuggingFace has an extension called Optimum which offers specialized model inference, including ONNX. We can use this to import and export ONNX models with `from_pretrained` and `save_pretrained`.\n", + "- We'll use the treained model above as an example and load it as a `ORTModelForMultipleChoice`, representing an ONNX model." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "87VKKCh1N-Ut" + }, + "outputs": [], + "source": [ + "!pip install -q --upgrade transformers[onnx] optimum" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "Id33annImYM8" + }, + "outputs": [], + "source": [ + "from optimum.onnxruntime import ORTModelForMultipleChoice\n", + "\n", + "MODEL_NAME = \"Ariffiq99/CRAB_COPA_KUCI_e_care_albert_Base_Finetuned\"\n", + "ONNX_MODEL_PATH = f\"onnx_models/albert_multiple_choice\"\n", + "\n", + "ort_model = ORTModelForMultipleChoice.from_pretrained(MODEL_NAME, export=True)\n", + "ort_model.save_pretrained(ONNX_MODEL_PATH)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e1696tiVO51u" + }, + "source": [ + "Let's have a look inside these two directories and see what we are dealing with:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "NFamGuT4OJC2", + "outputId": "724401e5-2d11-4c89-ba0b-d995e6276ba5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 48M\n", + "-rw-r--r-- 1 root root 871 Dec 27 19:03 config.json\n", + "-rw-r--r-- 1 root root 45M Dec 27 19:03 model.onnx\n", + "-rw-r--r-- 1 root root 970 Dec 27 19:03 special_tokens_map.json\n", + "-rw-r--r-- 1 root root 743K Dec 27 19:03 spiece.model\n", + "-rw-r--r-- 1 root root 1.5K Dec 27 19:03 tokenizer_config.json\n", + "-rw-r--r-- 1 root root 2.2M Dec 27 19:03 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -lh {ONNX_MODEL_PATH}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "THEhUhYRO6-y" + }, + "source": [ + "We need the `spiece.model` for the Tokenizer. This is the same for every model, these are assets (saved in /assets) needed for tokenization inside Spark NLP." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "N_-ljjz1PVLD" + }, + "outputs": [], + "source": [ + "!mkdir {ONNX_MODEL_PATH}/assets" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "id": "MI0KJCcJPjoX" + }, + "outputs": [], + "source": [ + "!mv {ONNX_MODEL_PATH}/spiece.model {ONNX_MODEL_PATH}/assets/" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rOT64bl9Ppk-" + }, + "source": [ + "Voila! We have our vocab.txt inside assets directory" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "1BcINpaqPmgQ", + "outputId": "a705bff5-f98d-405b-dcea-0449b0383d27" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "onnx_models/albert_multiple_choice:\n", + "total 48312\n", + "drwxr-xr-x 2 root root 4096 Dec 27 19:04 assets\n", + "-rw-r--r-- 1 root root 871 Dec 27 19:03 config.json\n", + "-rw-r--r-- 1 root root 47180962 Dec 27 19:03 model.onnx\n", + "-rw-r--r-- 1 root root 970 Dec 27 19:03 special_tokens_map.json\n", + "-rw-r--r-- 1 root root 1442 Dec 27 19:03 tokenizer_config.json\n", + "-rw-r--r-- 1 root root 2272611 Dec 27 19:03 tokenizer.json\n", + "\n", + "onnx_models/albert_multiple_choice/assets:\n", + "total 744\n", + "-rw-r--r-- 1 root root 760289 Dec 27 19:03 spiece.model\n" + ] + } + ], + "source": [ + "!ls -lR {ONNX_MODEL_PATH}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3rgd1jHMRC7q" + }, + "source": [ + "## Import and Save AlbertForMultipleChoice in Spark NLP" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "N0dY2lHcRG5t" + }, + "source": [ + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9ld2osF6STCv" + }, + "outputs": [], + "source": [ + "!wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "u1kTC9LQRHbg", + "outputId": "6add9710-c8ee-4323-9944-960ff9fcfd65" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Apache Spark version: 3.5.3\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "h3lTxyr-R9LH" + }, + "source": [ + "- Let's use `loadSavedModel` functon in `AlbertForMultipleChoice` which allows us to load TensorFlow model in SavedModel format\n", + "- Most params can be set later when you are loading this model in `AlbertForMultipleChoice` in runtime like `setMaxSentenceLength`, so don't worry what you are setting them now\n", + "- `loadSavedModel` accepts two params, first is the path to the TF SavedModel. The second is the SparkSession that is `spark` variable we previously started via `sparknlp.start()`\n", + "- NOTE: `loadSavedModel` accepts local paths in addition to distributed file systems such as `HDFS`, `S3`, `DBFS`, etc. This feature was introduced in Spark NLP 4.2.2 release. Keep in mind the best and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "id": "6O6v4t3HSFRU" + }, + "outputs": [], + "source": [ + "from sparknlp.annotator import *\n", + "from sparknlp.base import *\n", + "\n", + "\n", + "albertMultpleChoiceClassifier = AlbertForMultipleChoice.loadSavedModel(\n", + " f\"{ONNX_MODEL_PATH}\",\n", + " spark\n", + " )\\\n", + " .setInputCols([\"document_question\", \"document_context\"])\\\n", + " .setOutputCol(\"answer\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OmxG3UynSxFf" + }, + "source": [ + "Let's save it on disk so it is easier to be moved around and also be used later via .load function" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "id": "dl9v_UCISfbJ" + }, + "outputs": [], + "source": [ + "albertMultpleChoiceClassifier.write().overwrite().save(\"./{}_spark_nlp_onnx\".format(MODEL_NAME))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YPSFjBLuS2Lk" + }, + "source": [ + "Let's clean up stuff we don't need anymore" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "id": "spbp5G5sS2lR" + }, + "outputs": [], + "source": [ + "!rm -rf {ONNX_MODEL_PATH}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LxK9WcnJS_XC" + }, + "source": [ + "Now let's see how we can use it on other machines, clusters, or any place you wish to use your new and shiny `AlbertForMultipleChoice` model in Spark NLP ๐Ÿš€ pipeline!" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "id": "Gs3VQBACg8jm" + }, + "outputs": [], + "source": [ + " testing_data = [\n", + " (\"In Italy, pizza served in formal settings, such as at a restaurant, is presented unsliced.\",\n", + " \"It is eaten with a fork and a knife, It is eaten while held in the hand.\"),\n", + "\n", + " (\"The Eiffel Tower is located in which country?\",\n", + " \"Germany, France, Italy\"),\n", + "\n", + " (\"Which animal is known as the king of the jungle?\",\n", + " \"Lion, Elephant, Tiger, Leopard\"),\n", + "\n", + " (\"Water boils at what temperature?\",\n", + " \"90ยฐC, 120ยฐC, 100ยฐC\"),\n", + "\n", + " (\"Which planet is known as the Red Planet?\",\n", + " \"Jupiter, Mars, Venus\"),\n", + "\n", + " (\"Which language is primarily spoken in Brazil?\",\n", + " \"Spanish, Portuguese, English\"),\n", + "\n", + " (\"The Great Wall of China was built to protect against invasions from which group?\",\n", + " \"The Greeks, The Romans, The Mongols, The Persians\"),\n", + "\n", + " (\"Which chemical element has the symbol 'O'?\",\n", + " \"Oxygenm, Osmium, Ozone\"),\n", + "\n", + " (\"Which continent is the Sahara Desert located in?\",\n", + " \"Asia, Africa, South America\"),\n", + "\n", + " (\"Which artist painted the Mona Lisa?\",\n", + " \"Vincent van Gogh, Leonardo da Vinci, Pablo Picasso\")\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "wQ-hmCBSPCsU", + "outputId": "929d3ea1-193c-409a-eb00-8ada21e3b18f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------------------------------------+------------------------------------------------------------------------+\n", + "|question |choices |\n", + "+------------------------------------------------------------------------------------------+------------------------------------------------------------------------+\n", + "|In Italy, pizza served in formal settings, such as at a restaurant, is presented unsliced.|It is eaten with a fork and a knife, It is eaten while held in the hand.|\n", + "|The Eiffel Tower is located in which country? |Germany, France, Italy |\n", + "|Which animal is known as the king of the jungle? |Lion, Elephant, Tiger, Leopard |\n", + "|Water boils at what temperature? |90ยฐC, 120ยฐC, 100ยฐC |\n", + "|Which planet is known as the Red Planet? |Jupiter, Mars, Venus |\n", + "|Which language is primarily spoken in Brazil? |Spanish, Portuguese, English |\n", + "|The Great Wall of China was built to protect against invasions from which group? |The Greeks, The Romans, The Mongols, The Persians |\n", + "|Which chemical element has the symbol 'O'? |Oxygenm, Osmium, Ozone |\n", + "|Which continent is the Sahara Desert located in? |Asia, Africa, South America |\n", + "|Which artist painted the Mona Lisa? |Vincent van Gogh, Leonardo da Vinci, Pablo Picasso |\n", + "+------------------------------------------------------------------------------------------+------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "testing_df = spark.createDataFrame(testing_data, [\"question\", \"choices\"])\n", + "testing_df.show(truncate=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8IX6B1rHTNwt", + "outputId": "b5d5d8b1-5d3e-42e2-b219-9dc6cd80017d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------------------------------------------------------+\n", + "|answer |\n", + "+------------------------------------------------------------------------------------------------------------+\n", + "|[{chunk, 0, 35, It is eaten while held in the hand., {sentence -> 0, chunk -> 0, score -> 0.55574197}, []}]|\n", + "|[{chunk, 0, 5, Italy, {sentence -> 0, chunk -> 0, score -> 0.3497405}, []}] |\n", + "|[{chunk, 0, 8, Elephant, {sentence -> 0, chunk -> 0, score -> 0.28558698}, []}] |\n", + "|[{chunk, 0, 5, 100ยฐC, {sentence -> 0, chunk -> 0, score -> 0.34499714}, []}] |\n", + "|[{chunk, 0, 4, Mars, {sentence -> 0, chunk -> 0, score -> 0.3803456}, []}] |\n", + "|[{chunk, 0, 10, Portuguese, {sentence -> 0, chunk -> 0, score -> 0.36515844}, []}] |\n", + "|[{chunk, 0, 11, The Mongols, {sentence -> 0, chunk -> 0, score -> 0.2663425}, []}] |\n", + "|[{chunk, 0, 6, Osmium, {sentence -> 0, chunk -> 0, score -> 0.35382026}, []}] |\n", + "|[{chunk, 0, 13, South America, {sentence -> 0, chunk -> 0, score -> 0.38049418}, []}] |\n", + "|[{chunk, 0, 13, Pablo Picasso, {sentence -> 0, chunk -> 0, score -> 0.3762705}, []}] |\n", + "+------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "albert_for_multiple_choice = AlbertForMultipleChoice() \\\n", + " .load(\"./{}_spark_nlp_onnx\".format(MODEL_NAME)) \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, albert_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "A100", + "machine_shape": "hm", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_BertForMultipleChoice.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_BertForMultipleChoice.ipynb index 7503cfd9f8b000..b6faed087c98ea 100644 --- a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_BertForMultipleChoice.ipynb +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_BertForMultipleChoice.ipynb @@ -91,30 +91,6 @@ "- We'll use the treained model above as an example and load it as a `ORTModelForMultipleChoice`, representing an ONNX model." ] }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "avTe8Oe5N-vw", - "outputId": "270cf088-de9d-4dd2-d0cf-56daba62e141" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount(\"/content/drive\", force_remount=True).\n" - ] - } - ], - "source": [ - "from google.colab import drive\n", - "drive.mount('/content/drive')" - ] - }, { "cell_type": "code", "execution_count": 5, @@ -446,51 +422,11 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "al3szq-HRy2s", - "outputId": "a08dc94b-614a-44f8-daf1-98149d057011" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: pyspark in /usr/local/lib/python3.10/dist-packages (3.5.3)\n", - "Requirement already satisfied: py4j==0.10.9.7 in /usr/local/lib/python3.10/dist-packages (from pyspark) (0.10.9.7)\n" - ] - } - ], - "source": [ - "!pip install pyspark" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "9ld2osF6STCv", - "outputId": "ad4bd7ce-b2f9-406c-bc47-63a18f8b1ee6" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processing ./spark_nlp-5.5.0-py2.py3-none-any.whl\n", - "Installing collected packages: spark-nlp\n", - "Successfully installed spark-nlp-5.5.0\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ - "!pip install spark_nlp-5.5.0-py2.py3-none-any.whl" + "!wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" ] }, { diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_AlbertForMultipleChoice.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_AlbertForMultipleChoice.ipynb new file mode 100644 index 00000000000000..26b152eeb84987 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_AlbertForMultipleChoice.ipynb @@ -0,0 +1,2903 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "_V5XcDCnVgSi" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_AlbertForMultipleChoice..ipynb)\n", + "\n", + "# Import OpenVINO AlbertForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and exporting AlbertForMultipleChoice models from HuggingFace for use in Spark NLP, leveraging the various tools provided in the [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html) ecosystem.\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance inference for models. Please make sure you have upgraded to the latest Spark NLP release.\n", + "- You can import models for AlbertForMultipleChoice from ALBERT and they have to be in `For Multiple Choice` category." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aghasVppVgSk" + }, + "source": [ + "## 1. Export and Save the HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "be4HsTDMVgSk" + }, + "source": [ + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future releases, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "-7L-2ZWUVgSl", + "outputId": "132f54a4-06ec-42d1-a9ef-f1866d0ec6d9" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m43.8/43.8 kB\u001b[0m \u001b[31m2.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m9.1/9.1 MB\u001b[0m \u001b[31m76.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m3.6/3.6 MB\u001b[0m \u001b[31m66.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m38.7/38.7 MB\u001b[0m \u001b[31m23.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m215.7/215.7 kB\u001b[0m \u001b[31m18.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m480.6/480.6 kB\u001b[0m \u001b[31m40.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m424.1/424.1 kB\u001b[0m \u001b[31m31.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m102.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m10.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m179.3/179.3 kB\u001b[0m \u001b[31m16.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m12.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m3.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m17.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m9.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.1/13.1 MB\u001b[0m \u001b[31m30.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m57.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "google-ai-generativelanguage 0.6.10 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-api-core 2.19.2 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0.dev0,>=3.19.5, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-aiplatform 1.74.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-bigquery-connection 1.17.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-bigquery-storage 2.27.0 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-bigtable 2.27.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-datastore 2.20.2 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-firestore 2.19.0 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-functions 1.19.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-iam 2.17.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-language 2.16.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-pubsub 2.27.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-resource-manager 1.14.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-translate 3.19.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "googleapis-common-protos 1.66.0 requires protobuf!=3.20.0,!=3.20.1,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0.dev0,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "grpc-google-iam-v1 0.13.1 requires protobuf!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.1 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.1 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.1 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q --upgrade transformers==4.41.2\n", + "!pip install -q --upgrade openvino==2024.1\n", + "!pip install -q --upgrade optimum-intel==1.17.0\n", + "!pip install -q --upgrade onnx==1.12.0" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vI7uz_6hVgSl" + }, + "source": [ + "[Optimum Intel](https://github.com/huggingface/optimum-intel?tab=readme-ov-file#openvino) is the interface between the Transformers library and the various model optimization and acceleration tools provided by Intel. HuggingFace models loaded with optimum-intel are automatically optimized for OpenVINO, while being compatible with the Transformers API.\n", + "- Normally, to load a HuggingFace model directly for inference/export, just replace the `AutoModelForXxx` class with the corresponding `OVModelForXxx` class. However, ForMultipleChoice is not yet available so we will use `openvino.convert_model()` after exporting ONNX model\n", + "- We'll use [Ariffiq99/CRAB_COPA_KUCI_e_care_albert_Base_Finetuned](https://huggingface.co/Ariffiq99/CRAB_COPA_KUCI_e_care_albert_Base_Finetuned) model from HuggingFace as an example\n", + "- We also need the `spiece.model` for the Tokenizer. This is the same for every model, these are assets (saved in `/assets`) needed for tokenization inside Spark NLP." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TDapJ_09nqXQ", + "outputId": "80fd3e48-9a26-4b7a-8a77-fbcf263a4f41" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pip in /usr/local/lib/python3.10/dist-packages (24.1.2)\n", + "Collecting pip\n", + " Downloading pip-24.3.1-py3-none-any.whl.metadata (3.7 kB)\n", + "Downloading pip-24.3.1-py3-none-any.whl (1.8 MB)\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m50.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: pip\n", + " Attempting uninstall: pip\n", + " Found existing installation: pip 24.1.2\n", + " Uninstalling pip-24.1.2:\n", + " Successfully uninstalled pip-24.1.2\n", + "Successfully installed pip-24.3.1\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m154.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m3.0/3.0 MB\u001b[0m \u001b[31m107.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m10.1/10.1 MB\u001b[0m \u001b[31m164.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m54.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "optimum-intel 1.17.0 requires transformers<4.42.0,>=4.36.0, but you have transformers 4.47.1 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install --upgrade pip\n", + "!pip install -q --upgrade transformers[onnx] optimum openvino==2024.1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 313, + "referenced_widgets": [ + "b290505d643f4b55b53d321b7a3b7173", + "d5d0a4b4e6ec4930b7d9a63570c5f628", + "1227b58f17714176b3d4f214e9a9a6ab", + "37da5707cece4783b0156359e59855dd", + "812a25b979bc4fcdb16a4cfcf3edf514", + "532f8611f3874da8840129b2db849434", + "75a3920d9fbb4ce69c82d7188f1723fe", + "322472e75c82489f8fb60195d9b2423e", + "959d91379231457683a338e40433a622", + "7a47aefb34314b518f5d658dc7a43e95", + "04aa5c39a0de4890b0c2c1d896898980", + "a3087040ac924bc48d5c0f499e5fb013", + "7d16ef5ff9cb45738b582e813c8432c6", + "b17129cfbece4d8395c3797524f5bfda", + "df6e4f2516754d8998c024914c359197", + "df3c5e3409dc4c63bf4f82b623692755", + "9c5fb2b927a84959b1994753cc1bc795", + "5f4c10fd4f354aa890b9b9d77b70a4f2", + "338041f26ac94c1e9b0050822b3d2ce5", + "57446a6c5f6540aa8186b6d1b45eb8a0", + "ec733c40e8c34b7799bf6649eb8d3204", + "6f4f194995a54a768cb1745e058ebdb4", + "d8f38d77054c49ba8ef8219a46835c6a", + "5606f6413bc24e98ac82738eb6b77352", + "c72b72dd936f43ca9e7b43d8ddaf07e5", + "10d731456a4f4191a3f5314f291e5d6a", + "0864bcd49347402fb361b936e809e8b5", + "4ba486719c4346a39f7a370b14615bda", + "5c06ce14a17a4379b68715a39d415f47", + "a65d309c4b594219a376cb0c1090b771", + "5b40f96a47a044a591d3b90d00107fcd", + "968e53c4ba3a4e2fb997033acb3aed0c", + "de73f6ed034d461487fabda18e300d5a", + "bfc4493fd3db439dac341667459f69f1", + "ec4fcdf171ca40eea96c43b81ddfa0a4", + "6439b0871cb74e9cb356b902c8a59032", + "98f33af7917546bbb2810b02d074c3fb", + "1a8c88e7890347d6b4905ef82864f455", + "cbb2ae4aaf384a32984e517f09717d51", + "c611475f56134802abd842b4460ff431", + "cab619bce0c74ec5b6530d01b93d9b62", + "9997518db62f4006a88b706d6f9c7fa3", + "64b3823221904eb28019dea6aefc4997", + "dacbc568ea88490289e37598beb78f08", + "3e58d77978364f35902afdc2be9a9b44", + "cfc1f531d5e645f5b3a4c04b1292957f", + "523b7bc7b387485580ff424dc24fa763", + "8fac9a91dfe648c0b20d714711b4a35a", + "8abcb56f574148928c687174a9ef39b5", + "011ecb9cf9544dde898ae74b2b2e431d", + "69130a78308a4763b33cc41b199f72fc", + "f52636bcf35c47f78d12379a53b5616e", + "6f0bac3fcb214b2dbed4f275afbf999c", + "f765cb1a1fca4711a781b15c79a577b8", + "15a2b5f09571454a9b7564bfd5422988", + "3ab40c11ced64bea9f65deea308bd91c", + "f6586a0726704b1dbc7cca992dad4c40", + "db301c8a0e90423f834bba34d3e2ce6a", + "c76db86d92694c0695dd84a4569e63d7", + "744c1e5bf1a04328b751ac2540d0d2b7", + "0257e8b83d0b4b5b810d5b9dbff81087", + "f0441c125ec44e558b927c3d6733e16e", + "fb17131bd9584b16b3fba62b717c46c9", + "c35c725dc3024614bdef0d7f4140105e", + "a401e3458bba49279572b3cf723ac58e", + "fdcec4bc637f4cd384c6e06fb05fb744" + ] + }, + "id": "_b89GvQKosA0", + "outputId": "25eeadc1-aba4-4ad9-856e-214f9855bbd2" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b290505d643f4b55b53d321b7a3b7173", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/866 [00:00 0, chunk -> 0, score -> 0.5146987}, []}]|\n", + "|[{chunk, 0, 6, France, {sentence -> 0, chunk -> 0, score -> 0.34097242}, []}] |\n", + "|[{chunk, 0, 3, Lion, {sentence -> 0, chunk -> 0, score -> 0.26465067}, []}] |\n", + "|[{chunk, 0, 3, 90ยฐC, {sentence -> 0, chunk -> 0, score -> 0.34688318}, []}] |\n", + "|[{chunk, 0, 5, Venus, {sentence -> 0, chunk -> 0, score -> 0.35853413}, []}] |\n", + "|[{chunk, 0, 7, English, {sentence -> 0, chunk -> 0, score -> 0.38890713}, []}] |\n", + "|[{chunk, 0, 9, The Greeks, {sentence -> 0, chunk -> 0, score -> 0.29366478}, []}] |\n", + "|[{chunk, 0, 5, Ozone, {sentence -> 0, chunk -> 0, score -> 0.34738493}, []}] |\n", + "|[{chunk, 0, 6, Africa, {sentence -> 0, chunk -> 0, score -> 0.35337886}, []}] |\n", + "|[{chunk, 0, 15, Vincent van Gogh, {sentence -> 0, chunk -> 0, score -> 0.37136987}, []}] |\n", + "+----------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.ml import Pipeline, PipelineModel\n", + "\n", + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "albert_for_multiple_choice = AlbertForMultipleChoice() \\\n", + " .load(f\"{MODEL_NAME}_spark_nlp_openvino\") \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, albert_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lpxiq1igoj6c" + }, + "source": [ + "That's it! You can now go wild and use hundreds of `AlbertForMultipleChoice` models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "A100", + "machine_shape": "hm", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "011ecb9cf9544dde898ae74b2b2e431d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "0257e8b83d0b4b5b810d5b9dbff81087": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "04aa5c39a0de4890b0c2c1d896898980": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "0864bcd49347402fb361b936e809e8b5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "10d731456a4f4191a3f5314f291e5d6a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_968e53c4ba3a4e2fb997033acb3aed0c", + "placeholder": "โ€‹", + "style": "IPY_MODEL_de73f6ed034d461487fabda18e300d5a", + "value": "โ€‡1.41k/1.41kโ€‡[00:00<00:00,โ€‡126kB/s]" + } + }, + "1227b58f17714176b3d4f214e9a9a6ab": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_322472e75c82489f8fb60195d9b2423e", + "max": 866, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_959d91379231457683a338e40433a622", + "value": 866 + } + }, + "15a2b5f09571454a9b7564bfd5422988": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "1a8c88e7890347d6b4905ef82864f455": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "322472e75c82489f8fb60195d9b2423e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "338041f26ac94c1e9b0050822b3d2ce5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "37da5707cece4783b0156359e59855dd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_7a47aefb34314b518f5d658dc7a43e95", + "placeholder": "โ€‹", + "style": "IPY_MODEL_04aa5c39a0de4890b0c2c1d896898980", + "value": "โ€‡866/866โ€‡[00:00<00:00,โ€‡75.3kB/s]" + } + }, + "3ab40c11ced64bea9f65deea308bd91c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_f6586a0726704b1dbc7cca992dad4c40", + "IPY_MODEL_db301c8a0e90423f834bba34d3e2ce6a", + "IPY_MODEL_c76db86d92694c0695dd84a4569e63d7" + ], + "layout": "IPY_MODEL_744c1e5bf1a04328b751ac2540d0d2b7" + } + }, + "3e58d77978364f35902afdc2be9a9b44": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_cfc1f531d5e645f5b3a4c04b1292957f", + "IPY_MODEL_523b7bc7b387485580ff424dc24fa763", + "IPY_MODEL_8fac9a91dfe648c0b20d714711b4a35a" + ], + "layout": "IPY_MODEL_8abcb56f574148928c687174a9ef39b5" + } + }, + "4ba486719c4346a39f7a370b14615bda": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "523b7bc7b387485580ff424dc24fa763": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_f52636bcf35c47f78d12379a53b5616e", + "max": 2272611, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_6f0bac3fcb214b2dbed4f275afbf999c", + "value": 2272611 + } + }, + "532f8611f3874da8840129b2db849434": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5606f6413bc24e98ac82738eb6b77352": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_4ba486719c4346a39f7a370b14615bda", + "placeholder": "โ€‹", + "style": "IPY_MODEL_5c06ce14a17a4379b68715a39d415f47", + "value": "tokenizer_config.json:โ€‡100%" + } + }, + "57446a6c5f6540aa8186b6d1b45eb8a0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "5b40f96a47a044a591d3b90d00107fcd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "5c06ce14a17a4379b68715a39d415f47": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "5f4c10fd4f354aa890b9b9d77b70a4f2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "6439b0871cb74e9cb356b902c8a59032": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_cab619bce0c74ec5b6530d01b93d9b62", + "max": 760289, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_9997518db62f4006a88b706d6f9c7fa3", + "value": 760289 + } + }, + "64b3823221904eb28019dea6aefc4997": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "69130a78308a4763b33cc41b199f72fc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "6f0bac3fcb214b2dbed4f275afbf999c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "6f4f194995a54a768cb1745e058ebdb4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "744c1e5bf1a04328b751ac2540d0d2b7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "75a3920d9fbb4ce69c82d7188f1723fe": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "7a47aefb34314b518f5d658dc7a43e95": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7d16ef5ff9cb45738b582e813c8432c6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_9c5fb2b927a84959b1994753cc1bc795", + "placeholder": "โ€‹", + "style": "IPY_MODEL_5f4c10fd4f354aa890b9b9d77b70a4f2", + "value": "model.safetensors:โ€‡100%" + } + }, + "812a25b979bc4fcdb16a4cfcf3edf514": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8abcb56f574148928c687174a9ef39b5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8fac9a91dfe648c0b20d714711b4a35a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_f765cb1a1fca4711a781b15c79a577b8", + "placeholder": "โ€‹", + "style": "IPY_MODEL_15a2b5f09571454a9b7564bfd5422988", + "value": "โ€‡2.27M/2.27Mโ€‡[00:00<00:00,โ€‡3.53MB/s]" + } + }, + "959d91379231457683a338e40433a622": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "968e53c4ba3a4e2fb997033acb3aed0c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "98f33af7917546bbb2810b02d074c3fb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_64b3823221904eb28019dea6aefc4997", + "placeholder": "โ€‹", + "style": "IPY_MODEL_dacbc568ea88490289e37598beb78f08", + "value": "โ€‡760k/760kโ€‡[00:00<00:00,โ€‡50.3MB/s]" + } + }, + "9997518db62f4006a88b706d6f9c7fa3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "9c5fb2b927a84959b1994753cc1bc795": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a3087040ac924bc48d5c0f499e5fb013": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_7d16ef5ff9cb45738b582e813c8432c6", + "IPY_MODEL_b17129cfbece4d8395c3797524f5bfda", + "IPY_MODEL_df6e4f2516754d8998c024914c359197" + ], + "layout": "IPY_MODEL_df3c5e3409dc4c63bf4f82b623692755" + } + }, + "a401e3458bba49279572b3cf723ac58e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a65d309c4b594219a376cb0c1090b771": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b17129cfbece4d8395c3797524f5bfda": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_338041f26ac94c1e9b0050822b3d2ce5", + "max": 46740836, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_57446a6c5f6540aa8186b6d1b45eb8a0", + "value": 46740836 + } + }, + "b290505d643f4b55b53d321b7a3b7173": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_d5d0a4b4e6ec4930b7d9a63570c5f628", + "IPY_MODEL_1227b58f17714176b3d4f214e9a9a6ab", + "IPY_MODEL_37da5707cece4783b0156359e59855dd" + ], + "layout": "IPY_MODEL_812a25b979bc4fcdb16a4cfcf3edf514" + } + }, + "bfc4493fd3db439dac341667459f69f1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_ec4fcdf171ca40eea96c43b81ddfa0a4", + "IPY_MODEL_6439b0871cb74e9cb356b902c8a59032", + "IPY_MODEL_98f33af7917546bbb2810b02d074c3fb" + ], + "layout": "IPY_MODEL_1a8c88e7890347d6b4905ef82864f455" + } + }, + "c35c725dc3024614bdef0d7f4140105e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "c611475f56134802abd842b4460ff431": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "c72b72dd936f43ca9e7b43d8ddaf07e5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_a65d309c4b594219a376cb0c1090b771", + "max": 1412, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_5b40f96a47a044a591d3b90d00107fcd", + "value": 1412 + } + }, + "c76db86d92694c0695dd84a4569e63d7": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_a401e3458bba49279572b3cf723ac58e", + "placeholder": "โ€‹", + "style": "IPY_MODEL_fdcec4bc637f4cd384c6e06fb05fb744", + "value": "โ€‡970/970โ€‡[00:00<00:00,โ€‡81.3kB/s]" + } + }, + "cab619bce0c74ec5b6530d01b93d9b62": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "cbb2ae4aaf384a32984e517f09717d51": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "cfc1f531d5e645f5b3a4c04b1292957f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_011ecb9cf9544dde898ae74b2b2e431d", + "placeholder": "โ€‹", + "style": "IPY_MODEL_69130a78308a4763b33cc41b199f72fc", + "value": "tokenizer.json:โ€‡100%" + } + }, + "d5d0a4b4e6ec4930b7d9a63570c5f628": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_532f8611f3874da8840129b2db849434", + "placeholder": "โ€‹", + "style": "IPY_MODEL_75a3920d9fbb4ce69c82d7188f1723fe", + "value": "config.json:โ€‡100%" + } + }, + "d8f38d77054c49ba8ef8219a46835c6a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_5606f6413bc24e98ac82738eb6b77352", + "IPY_MODEL_c72b72dd936f43ca9e7b43d8ddaf07e5", + "IPY_MODEL_10d731456a4f4191a3f5314f291e5d6a" + ], + "layout": "IPY_MODEL_0864bcd49347402fb361b936e809e8b5" + } + }, + "dacbc568ea88490289e37598beb78f08": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "db301c8a0e90423f834bba34d3e2ce6a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_fb17131bd9584b16b3fba62b717c46c9", + "max": 970, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_c35c725dc3024614bdef0d7f4140105e", + "value": 970 + } + }, + "de73f6ed034d461487fabda18e300d5a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "df3c5e3409dc4c63bf4f82b623692755": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "df6e4f2516754d8998c024914c359197": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_ec733c40e8c34b7799bf6649eb8d3204", + "placeholder": "โ€‹", + "style": "IPY_MODEL_6f4f194995a54a768cb1745e058ebdb4", + "value": "โ€‡46.7M/46.7Mโ€‡[00:01<00:00,โ€‡43.2MB/s]" + } + }, + "ec4fcdf171ca40eea96c43b81ddfa0a4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_cbb2ae4aaf384a32984e517f09717d51", + "placeholder": "โ€‹", + "style": "IPY_MODEL_c611475f56134802abd842b4460ff431", + "value": "spiece.model:โ€‡100%" + } + }, + "ec733c40e8c34b7799bf6649eb8d3204": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f0441c125ec44e558b927c3d6733e16e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "f52636bcf35c47f78d12379a53b5616e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f6586a0726704b1dbc7cca992dad4c40": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_0257e8b83d0b4b5b810d5b9dbff81087", + "placeholder": "โ€‹", + "style": "IPY_MODEL_f0441c125ec44e558b927c3d6733e16e", + "value": "special_tokens_map.json:โ€‡100%" + } + }, + "f765cb1a1fca4711a781b15c79a577b8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fb17131bd9584b16b3fba62b717c46c9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fdcec4bc637f4cd384c6e06fb05fb744": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/python/sparknlp/annotator/classifier_dl/__init__.py b/python/sparknlp/annotator/classifier_dl/__init__.py index 2b5e30fc3ff359..136149553707d7 100644 --- a/python/sparknlp/annotator/classifier_dl/__init__.py +++ b/python/sparknlp/annotator/classifier_dl/__init__.py @@ -55,3 +55,4 @@ from sparknlp.annotator.classifier_dl.albert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.camembert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.bert_for_multiple_choice import * +from sparknlp.annotator.classifier_dl.albert_for_multiple_choice import * \ No newline at end of file diff --git a/python/sparknlp/annotator/classifier_dl/albert_for_multiple_choice.py b/python/sparknlp/annotator/classifier_dl/albert_for_multiple_choice.py new file mode 100644 index 00000000000000..7dc610b256f687 --- /dev/null +++ b/python/sparknlp/annotator/classifier_dl/albert_for_multiple_choice.py @@ -0,0 +1,161 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class AlbertForMultipleChoice(AnnotatorModel, + HasCaseSensitiveProperties, + HasBatchedAnnotate, + HasEngine, + HasMaxSentenceLengthLimit): + """AlbertForMultipleChoice can load ALBERT Models with a multiple choice classification head on top + (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion + object: + + >>> spanClassifier = AlbertForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") + + The default model is ``"albert_base_uncased_multiple_choice"``, if no name is + provided. + + For available pretrained models please see the `Models Hub + `__. + + To see which models are compatible and how to import them see + `Import Transformers into Spark NLP ๐Ÿš€ + `_. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``DOCUMENT, DOCUMENT`` ``CHUNK`` + ====================== ====================== + + Parameters + ---------- + batchSize + Batch size. Large values allows faster processing but requires more + memory, by default 8 + caseSensitive + Whether to ignore case in tokens for embeddings matching, by default + False + maxSentenceLength + Max sentence length to process, by default 512 + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> documentAssembler = MultiDocumentAssembler() \\ + ... .setInputCols(["question", "context"]) \\ + ... .setOutputCols(["document_question", "document_context"]) + >>> questionAnswering = AlbertForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") \\ + ... .setCaseSensitive(False) + >>> pipeline = Pipeline().setStages([ + ... documentAssembler, + ... questionAnswering + ... ]) + >>> data = spark.createDataFrame([["The Eiffel Tower is located in which country??", "Germany, France, Italy"]]).toDF("question", "context") + >>> result = pipeline.fit(data).transform(data) + >>> result.select("answer.result").show(truncate=False) + +--------------------+ + |result | + +--------------------+ + |[France] | + +--------------------+ + """ + name = "AlbertForMultipleChoice" + + inputAnnotatorTypes = [AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT] + + outputAnnotatorType = AnnotatorType.CHUNK + + choicesDelimiter = Param(Params._dummy(), + "choicesDelimiter", + "Delimiter character use to split the choices", + TypeConverters.toString) + + def setChoicesDelimiter(self, value): + """Sets delimiter character use to split the choices + + Parameters + ---------- + value : string + Delimiter character use to split the choices + """ + return self._set(caseSensitive=value) + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.classifier.dl.AlbertForMultipleChoice", + java_model=None): + super(AlbertForMultipleChoice, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=4, + maxSentenceLength=512, + caseSensitive=False, + choicesDelimiter = "," + ) + + @staticmethod + def loadSavedModel(folder, spark_session): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + BertForQuestionAnswering + The restored model + """ + from sparknlp.internal import _AlbertMultipleChoiceLoader + jModel = _AlbertMultipleChoiceLoader(folder, spark_session._jsparkSession)._java_obj + return AlbertForMultipleChoice(java_model=jModel) + + @staticmethod + def pretrained(name="albert_base_uncased_multiple_choice", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "bert_base_uncased_multiple_choice" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + BertForQuestionAnswering + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(AlbertForMultipleChoice, name, lang, remote_loc) \ No newline at end of file diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..3b7e85234f6360 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -67,6 +67,15 @@ def __init__(self, path, jspark): ) +class _AlbertMultipleChoiceLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark): + super(_AlbertMultipleChoiceLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.classifier.dl.AlbertForMultipleChoice.loadSavedModel", + path, + jspark, + ) + + class _BertLoader(ExtendedJavaWrapper): def __init__(self, path, jspark, use_openvino=False): super(_BertLoader, self).__init__( diff --git a/python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py b/python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py new file mode 100644 index 00000000000000..35a92b13908fb5 --- /dev/null +++ b/python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py @@ -0,0 +1,79 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import pytest + +from sparknlp.annotator.classifier_dl.albert_for_multiple_choice import AlbertForMultipleChoice +from sparknlp.base import * +from test.util import SparkContextForTest + + +class AlbertForMultipleChoiceTestSetup(unittest.TestCase): + def setUp(self): + + sparkNLPModelPath = "/media/danilo/Data/Danilo/JSL/models/transformers/spark-nlp" + + self.spark = SparkContextForTest.spark + self.question = "The Eiffel Tower is located in which country?" + self.choices = "Germany, France, Italy" + + self.spark = SparkContextForTest.spark + empty_df = self.spark.createDataFrame([[""]]).toDF("text") + + document_assembler = MultiDocumentAssembler() \ + .setInputCols(["question", "context"]) \ + .setOutputCols(["document_question", "document_context"]) + + albert_for_multiple_choice = AlbertForMultipleChoice.load(sparkNLPModelPath + "/openvino/albert_multiple_choice_openvino") \ + .setInputCols(["document_question", "document_context"]) \ + .setOutputCol("answer") + + pipeline = Pipeline(stages=[document_assembler, albert_for_multiple_choice]) + + self.pipeline_model = pipeline.fit(empty_df) + + +# @pytest.mark.slow +class AlbertForMultipleChoiceTest(AlbertForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + self.data = self.spark.createDataFrame([[self.question, self.choices]]).toDF("question","context") + self.data.show(truncate=False) + + def test_run(self): + result_df = self.pipeline_model.transform(self.data) + result_df.show(truncate=False) + for row in result_df.collect(): + self.assertTrue(row["answer"][0].result != "") + + +# @pytest.mark.slow +class LightAlbertForMultipleChoiceTest(AlbertForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.pipeline_model) + annotations_result = light_pipeline.fullAnnotate(self.question,self.choices) + print(annotations_result) + for result in annotations_result: + self.assertTrue(result["answer"][0].result != "") + + result = light_pipeline.annotate(self.question,self.choices) + print(result) + self.assertTrue(result["answer"] != "") diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/AlbertClassification.scala b/src/main/scala/com/johnsnowlabs/ml/ai/AlbertClassification.scala index 24075e80801347..b6ff6167b87efa 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/AlbertClassification.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/AlbertClassification.scala @@ -359,6 +359,96 @@ private[johnsnowlabs] class AlbertClassification( (startScores, endScores) } + override def tagSpanMultipleChoice(batch: Seq[Array[Int]]): Array[Float] = { + val logits = detectedEngine match { + case ONNX.name => computeLogitsMultipleChoiceWithOnnx(batch) + case Openvino.name => computeLogitsMultipleChoiceWithOv(batch) + } + + calculateSoftmax(logits) + } + + private def computeLogitsMultipleChoiceWithOv(batch: Seq[Array[Int]]): Array[Float] = { + val (numChoices, sequenceLength) = (batch.length, batch.head.length) + // batch_size, num_choices, sequence_length + val shape = Some(Array(1, numChoices, sequenceLength)) + val (tokenTensors, maskTensors, segmentTensors) = + PrepareEmbeddings.prepareOvLongBatchTensorsWithSegment( + batch, + sequenceLength, + numChoices, + sentencePadTokenId, + shape) + + val compiledModel = openvinoWrapper.get.getCompiledModel() + val inferRequest = compiledModel.create_infer_request() + inferRequest.set_tensor("input_ids", tokenTensors) + inferRequest.set_tensor("attention_mask", maskTensors) + inferRequest.set_tensor("token_type_ids", segmentTensors) + + inferRequest.infer() + + try { + try { + val logits = inferRequest + .get_output_tensor() + .data() + + logits + } + } catch { + case e: Exception => + // Log the exception as a warning + logger.warn("Exception in computeLogitsMultipleChoiceWithOv", e) + // Rethrow the exception to propagate it further + throw e + } + } + + private def computeLogitsMultipleChoiceWithOnnx(batch: Seq[Array[Int]]): Array[Float] = { + val sequenceLength = batch.head.length + val inputIds = Array(batch.map(x => x.map(_.toLong)).toArray) + val attentionMask = Array( + batch.map(sentence => sentence.map(x => if (x == 0L) 0L else 1L)).toArray) + val tokenTypeIds = Array(batch.map(_ => Array.fill(sequenceLength)(0L)).toArray) + + val (ortSession, ortEnv) = onnxWrapper.get.getSession(onnxSessionOptions) + val tokenTensors = OnnxTensor.createTensor(ortEnv, inputIds) + val maskTensors = OnnxTensor.createTensor(ortEnv, attentionMask) + val segmentTensors = OnnxTensor.createTensor(ortEnv, tokenTypeIds) + + val inputs = + Map( + "input_ids" -> tokenTensors, + "attention_mask" -> maskTensors, + "token_type_ids" -> segmentTensors).asJava + + try { + val output = ortSession.run(inputs) + try { + + val logits = output + .get("logits") + .get() + .asInstanceOf[OnnxTensor] + .getFloatBuffer + .array() + + tokenTensors.close() + maskTensors.close() + segmentTensors.close() + + logits + } finally if (output != null) output.close() + } catch { + case e: Exception => + // Log the exception as a warning + println("Exception in computeLogitsMultipleChoiceWithOnnx: ", e) + // Rethrow the exception to propagate it further + throw e + } + } + private def computeLogitsWithTF( batch: Seq[Array[Int]], maxSentenceLength: Int): (Array[Float], Array[Float]) = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala new file mode 100644 index 00000000000000..99d76187d362cf --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala @@ -0,0 +1,356 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.ml.ai.AlbertClassification +import com.johnsnowlabs.ml.onnx.{OnnxWrapper, ReadOnnxModel, WriteOnnxModel} +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.tensorflow.sentencepiece.{ + ReadSentencePieceModel, + SentencePieceWrapper, + WriteSentencePieceModel +} +import com.johnsnowlabs.ml.tensorflow.{TensorflowWrapper, WriteTensorflowModel} +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadSentencePieceAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp._ +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntParam, Param} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +/** AlbertForMultipleChoice can load ALBERT Models with a multiple choice classification head on top + * (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val spanClassifier = AlbertForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * }}} + * The default model is `"albert_base_uncased_multiple_choice"`, if no name is provided. + * + * For available pretrained models please see the + * [[https://sparknlp.org/models?task=Multiple+Choice Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTestSpec.scala AlbertForMultipleChoiceTestSpec]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val document = new MultiDocumentAssembler() + * .setInputCols("question", "context") + * .setOutputCols("document_question", "document_context") + * + * val questionAnswering = AlbertForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * .setCaseSensitive(false) + * + * val pipeline = new Pipeline().setStages(Array( + * document, + * questionAnswering + * )) + * + * val data = Seq("The Eiffel Tower is located in which country?", "Germany, France, Italy").toDF("question", "context") + * val result = pipeline.fit(data).transform(data) + * + * result.select("answer.result").show(false) + * +---------------------+ + * |result | + * +---------------------+ + * |[France] | + * ++--------------------+ + * }}} + * + * @see + * [[AlbertForQuestionAnswering]] for Question Answering tasks + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ + +class AlbertForMultipleChoice(override val uid: String) + extends AnnotatorModel[AlbertForMultipleChoice] + with HasBatchedAnnotate[AlbertForMultipleChoice] + with WriteTensorflowModel + with WriteOnnxModel + with WriteOpenvinoModel + with WriteSentencePieceModel + with HasCaseSensitiveProperties + with HasEngine { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("AlbertForMultipleChoice")) + + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) + override val outputAnnotatorType: AnnotatorType = AnnotatorType.CHUNK + + /** Max sentence length to process (Default: `128`) + * + * @group param + */ + val maxSentenceLength = + new IntParam(this, "maxSentenceLength", "Max sentence length to process") + + /** @group setParam */ + def setMaxSentenceLength(value: Int): this.type = { + require( + value <= 512, + "ALBERT models do not support sequences longer than 512 because of trainable positional embeddings.") + require(value >= 1, "The maxSentenceLength must be at least 1") + set(maxSentenceLength, value) + this + } + + /** @group getParam */ + def getMaxSentenceLength: Int = $(maxSentenceLength) + + val choicesDelimiter = + new Param[String](this, "choicesDelimiter", "Delimiter character use to split the choices") + + def setChoicesDelimiter(value: String): this.type = set(choicesDelimiter, value) + + private var _model: Option[Broadcast[AlbertClassification]] = None + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + tensorflowWrapper: Option[TensorflowWrapper], + onnxWrapper: Option[OnnxWrapper], + openvinoWrapper: Option[OpenvinoWrapper], + spp: SentencePieceWrapper): AlbertForMultipleChoice = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new AlbertClassification( + tensorflowWrapper, + onnxWrapper, + openvinoWrapper, + spp, + tags = Map.empty[String, Int], + ) + ) + ) + } + + this + } + + /** @group getParam */ + def getModelIfNotSet: AlbertClassification = _model.get.value + + /** Whether to lowercase tokens or not (Default: `false`). + * + * @group setParam + */ + override def setCaseSensitive(value: Boolean): this.type = set(this.caseSensitive, value) + + setDefault( + batchSize -> 8, + maxSentenceLength -> 128, + caseSensitive -> false, + choicesDelimiter -> "," + ) + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + * + * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences + * that belong to the same original row !! (challenging) + */ + override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { + batchedAnnotations.map(annotations => { + if (annotations.nonEmpty) { + getModelIfNotSet.predictSpanMultipleChoice( + annotations, + $(choicesDelimiter), + $(maxSentenceLength), + $(caseSensitive)) + } else { + Seq.empty[Annotation] + } + }) + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + val suffix = "_albert_multiple_choice_classification" + + getEngine match { + case ONNX.name => + writeOnnxModel( + path, + spark, + getModelIfNotSet.onnxWrapper.get, + suffix, + AlbertForMultipleChoice.onnxFile) + + case Openvino.name => + writeOpenvinoModel( + path, + spark, + getModelIfNotSet.openvinoWrapper.get, + "openvino_model.xml", + AlbertForMultipleChoice.openvinoFile) + + } + + writeSentencePieceModel( + path, + spark, + getModelIfNotSet.spp, + "_albert", + AlbertForSequenceClassification.sppFile) + + } + +} + +trait ReadablePretrainedAlbertForMultipleChoiceModel + extends ParamsAndFeaturesReadable[AlbertForMultipleChoice] + with HasPretrained[AlbertForMultipleChoice] { + override val defaultModelName: Some[String] = Some("albert_base_uncased_multiple_choice") + + /** Java compliant-overrides */ + override def pretrained(): AlbertForMultipleChoice = super.pretrained() + + override def pretrained(name: String): AlbertForMultipleChoice = super.pretrained(name) + + override def pretrained(name: String, lang: String): AlbertForMultipleChoice = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): AlbertForMultipleChoice = + super.pretrained(name, lang, remoteLoc) +} + +trait ReadAlbertForMultipleChoiceModel + extends ReadOnnxModel + with ReadOpenvinoModel + with ReadSentencePieceModel { + this: ParamsAndFeaturesReadable[AlbertForMultipleChoice] => + + override val onnxFile: String = "albert_mc_classification_onnx" + override val openvinoFile: String = "albert_mc_classification_openvino" + override val sppFile: String = "albert_spp" + + def readModel(instance: AlbertForMultipleChoice, path: String, spark: SparkSession): Unit = { + + val spp = readSentencePieceModel(path, spark, "_albert_spp", sppFile) + + instance.getEngine match { + case ONNX.name => + val onnxWrapper = + readOnnxModel(path, spark, "albert_mc_classification_onnx") + instance.setModelIfNotSet(spark, None, Some(onnxWrapper), None, spp) + + case Openvino.name => + val openvinoWrapper = readOpenvinoModel(path, spark, "albert_mc_classification_ov") + instance.setModelIfNotSet(spark, None, None, Some(openvinoWrapper), spp) + case _ => + throw new Exception(notSupportedEngineError) + } + + } + + addReader(readModel) + + def loadSavedModel(modelPath: String, spark: SparkSession): AlbertForMultipleChoice = { + val (localModelPath, detectedEngine) = modelSanityCheck(modelPath) + + val spModel = loadSentencePieceAsset(localModelPath, "spiece.model") + + /*Universal parameters for all engines*/ + val annotatorModel = new AlbertForMultipleChoice() + + annotatorModel.set(annotatorModel.engine, detectedEngine) + + detectedEngine match { + case ONNX.name => + val onnxWrapper = OnnxWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + onnxFileSuffix = None) + annotatorModel + .setModelIfNotSet(spark, None, Some(onnxWrapper), None, spModel) + + case Openvino.name => + val ovWrapper: OpenvinoWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine) + annotatorModel + .setModelIfNotSet(spark, None, None, Some(ovWrapper), spModel) + + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } +} + +/** This is the companion object of [[AlbertForMultipleChoice]]. Please refer to that class for the + * documentation. + */ +object AlbertForMultipleChoice + extends ReadablePretrainedAlbertForMultipleChoiceModel + with ReadAlbertForMultipleChoiceModel diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala new file mode 100644 index 00000000000000..1522e4d22b632d --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala @@ -0,0 +1,56 @@ +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.nlp.MultiDocumentAssembler +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec + +class AlbertForMultipleChoiceTest extends AnyFlatSpec with SparkSessionTest { + + import spark.implicits._ + val onnxModelPath = "/media/danilo/Data/Danilo/JSL/models/transformers/onnx" + val sparkNLPModelPath = "/media/danilo/Data/Danilo/JSL/models/transformers/spark-nlp" + val openVinoModelPath = "/media/danilo/Data/Danilo/JSL/models/transformers/openvino" + + val testDataframe = + Seq(("The Eiffel Tower is located in which country?", "Germany, France, Italy")) + .toDF("question", "context") + + "AlbertForMultipleChoice" should "loadSavedModel ONNX model" in { + val albertForMultipleChoice = AlbertForMultipleChoice.loadSavedModel(s"$onnxModelPath/albert_multiple_choice", spark) + albertForMultipleChoice.write.overwrite.save(s"$sparkNLPModelPath/onnx/albert_multiple_choice_onnx") + } + + it should "loadSavedModel OpenVINO model" in { + val albertForMultipleChoice = AlbertForMultipleChoice.loadSavedModel(s"$openVinoModelPath/albert_multiple_choice_openvino", spark) + albertForMultipleChoice.write.overwrite.save(s"$sparkNLPModelPath/openvino/albert_multiple_choice_openvino") + } + + it should "work for ONNX" in { + val pipelineModel = getAlbertForMultipleChoicePipelineModel(s"$sparkNLPModelPath/onnx/albert_multiple_choice_onnx") + val resultDf = pipelineModel.transform(testDataframe) + resultDf.show(truncate = false) + } + + it should "work for OpenVINO" in { + val pipelineModel = getAlbertForMultipleChoicePipelineModel(s"$sparkNLPModelPath/openvino/albert_multiple_choice_openvino") + val resultDf = pipelineModel.transform(testDataframe) + resultDf.show(truncate = false) + } + + private def getAlbertForMultipleChoicePipelineModel(modelPath: String) = { + val documentAssembler = new MultiDocumentAssembler() + .setInputCols("question", "context") + .setOutputCols("document_question", "document_context") + + val bertForMultipleChoice = AlbertForMultipleChoice + .load(modelPath) + .setInputCols("document_question", "document_context") + .setOutputCol("answer") + + val pipeline = new Pipeline().setStages(Array(documentAssembler, bertForMultipleChoice)) + + pipeline.fit(emptyDataSet) + } + +} From 9e67d896f171f6530c724a54e42f212df67da039 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Fri, 27 Dec 2024 15:59:28 -0500 Subject: [PATCH 002/108] [SPARKNLP-1105] Addiong test tags --- .../albert_for_multiple_choice_test.py | 4 +- .../dl/AlbertForMultipleChoiceTest.scala | 53 +++++++++++-------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py b/python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py index 35a92b13908fb5..6e42465e8a2cea 100644 --- a/python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py +++ b/python/test/annotator/classifier_dl/albert_for_multiple_choice_test.py @@ -46,7 +46,7 @@ def setUp(self): self.pipeline_model = pipeline.fit(empty_df) -# @pytest.mark.slow +@pytest.mark.slow class AlbertForMultipleChoiceTest(AlbertForMultipleChoiceTestSetup, unittest.TestCase): def setUp(self): @@ -61,7 +61,7 @@ def test_run(self): self.assertTrue(row["answer"][0].result != "") -# @pytest.mark.slow +@pytest.mark.slow class LightAlbertForMultipleChoiceTest(AlbertForMultipleChoiceTestSetup, unittest.TestCase): def setUp(self): diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala index 1522e4d22b632d..1385f41d94f502 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTest.scala @@ -1,50 +1,61 @@ package com.johnsnowlabs.nlp.annotators.classifier.dl -import com.johnsnowlabs.nlp.MultiDocumentAssembler +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, MultiDocumentAssembler} import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.tags.SlowTest import org.apache.spark.ml.Pipeline import org.scalatest.flatspec.AnyFlatSpec class AlbertForMultipleChoiceTest extends AnyFlatSpec with SparkSessionTest { import spark.implicits._ - val onnxModelPath = "/media/danilo/Data/Danilo/JSL/models/transformers/onnx" - val sparkNLPModelPath = "/media/danilo/Data/Danilo/JSL/models/transformers/spark-nlp" - val openVinoModelPath = "/media/danilo/Data/Danilo/JSL/models/transformers/openvino" + + lazy val pipelineModel = getAlbertForMultipleChoicePipelineModel val testDataframe = Seq(("The Eiffel Tower is located in which country?", "Germany, France, Italy")) .toDF("question", "context") - "AlbertForMultipleChoice" should "loadSavedModel ONNX model" in { - val albertForMultipleChoice = AlbertForMultipleChoice.loadSavedModel(s"$onnxModelPath/albert_multiple_choice", spark) - albertForMultipleChoice.write.overwrite.save(s"$sparkNLPModelPath/onnx/albert_multiple_choice_onnx") - } + "AlbertForMultipleChoice" should "answer a multiple choice question" taggedAs SlowTest in { + val resultDf = pipelineModel.transform(testDataframe) + resultDf.show(truncate = false) - it should "loadSavedModel OpenVINO model" in { - val albertForMultipleChoice = AlbertForMultipleChoice.loadSavedModel(s"$openVinoModelPath/albert_multiple_choice_openvino", spark) - albertForMultipleChoice.write.overwrite.save(s"$sparkNLPModelPath/openvino/albert_multiple_choice_openvino") + val result = AssertAnnotations.getActualResult(resultDf, "answer") + result.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } } - it should "work for ONNX" in { - val pipelineModel = getAlbertForMultipleChoicePipelineModel(s"$sparkNLPModelPath/onnx/albert_multiple_choice_onnx") - val resultDf = pipelineModel.transform(testDataframe) - resultDf.show(truncate = false) + it should "work with light pipeline fullAnnotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultFullAnnotate = lightPipeline.fullAnnotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultFullAnnotate") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + assert(answerAnnotation.result.nonEmpty) } - it should "work for OpenVINO" in { - val pipelineModel = getAlbertForMultipleChoicePipelineModel(s"$sparkNLPModelPath/openvino/albert_multiple_choice_openvino") - val resultDf = pipelineModel.transform(testDataframe) - resultDf.show(truncate = false) + it should "work with light pipeline annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultAnnotate = lightPipeline.annotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.nonEmpty) } - private def getAlbertForMultipleChoicePipelineModel(modelPath: String) = { + private def getAlbertForMultipleChoicePipelineModel = { val documentAssembler = new MultiDocumentAssembler() .setInputCols("question", "context") .setOutputCols("document_question", "document_context") val bertForMultipleChoice = AlbertForMultipleChoice - .load(modelPath) + .pretrained() .setInputCols("document_question", "document_context") .setOutputCol("answer") From 3b86715e879ff63c07f9dd90c06ecd5e7fb119a4 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Tue, 31 Dec 2024 16:11:11 -0500 Subject: [PATCH 003/108] [SPARKNLP-1106] Introducing DistilBertForMultipleChoice --- .../annotator/classifier_dl/__init__.py | 1 + .../classifier_dl/bert_for_multiple_choice.py | 4 +- .../distilbert_for_multiple_choice.py | 161 +++++++++++ python/sparknlp/internal/__init__.py | 9 + .../distilbert_for_multiple_choice_test.py | 76 +++++ .../ml/ai/DistilBertClassification.scala | 88 ++++++ .../dl/DistilBertForMultipleChoice.scala | 259 ++++++++++++++++++ .../DistilBertForMultipleChoiceTestSpec.scala | 85 ++++++ 8 files changed, 681 insertions(+), 2 deletions(-) create mode 100644 python/sparknlp/annotator/classifier_dl/distilbert_for_multiple_choice.py create mode 100644 python/test/annotator/classifier_dl/distilbert_for_multiple_choice_test.py create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala diff --git a/python/sparknlp/annotator/classifier_dl/__init__.py b/python/sparknlp/annotator/classifier_dl/__init__.py index 2b5e30fc3ff359..9ae264e7a864f1 100644 --- a/python/sparknlp/annotator/classifier_dl/__init__.py +++ b/python/sparknlp/annotator/classifier_dl/__init__.py @@ -55,3 +55,4 @@ from sparknlp.annotator.classifier_dl.albert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.camembert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.bert_for_multiple_choice import * +from sparknlp.annotator.classifier_dl.distilbert_for_multiple_choice import * \ No newline at end of file diff --git a/python/sparknlp/annotator/classifier_dl/bert_for_multiple_choice.py b/python/sparknlp/annotator/classifier_dl/bert_for_multiple_choice.py index 2c27f913e56fcc..045e8d64180b53 100644 --- a/python/sparknlp/annotator/classifier_dl/bert_for_multiple_choice.py +++ b/python/sparknlp/annotator/classifier_dl/bert_for_multiple_choice.py @@ -130,7 +130,7 @@ def loadSavedModel(folder, spark_session): Returns ------- - BertForQuestionAnswering + BertForMultipleChoice The restored model """ from sparknlp.internal import _BertMultipleChoiceLoader @@ -154,7 +154,7 @@ def pretrained(name="bert_base_uncased_multiple_choice", lang="en", remote_loc=N Returns ------- - BertForQuestionAnswering + BertForMultipleChoice The restored model """ from sparknlp.pretrained import ResourceDownloader diff --git a/python/sparknlp/annotator/classifier_dl/distilbert_for_multiple_choice.py b/python/sparknlp/annotator/classifier_dl/distilbert_for_multiple_choice.py new file mode 100644 index 00000000000000..f76aa3859c307e --- /dev/null +++ b/python/sparknlp/annotator/classifier_dl/distilbert_for_multiple_choice.py @@ -0,0 +1,161 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class DistilBertForMultipleChoice(AnnotatorModel, + HasCaseSensitiveProperties, + HasBatchedAnnotate, + HasEngine, + HasMaxSentenceLengthLimit): + """DistilBertForMultipleChoice can load DistilBert Models with a multiple choice classification head on top + (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion + object: + + >>> spanClassifier = DistilBertForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") + + The default model is ``"bert_base_uncased_multiple_choice"``, if no name is + provided. + + For available pretrained models please see the `Models Hub + `__. + + To see which models are compatible and how to import them see + `Import Transformers into Spark NLP ๐Ÿš€ + `_. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``DOCUMENT, DOCUMENT`` ``CHUNK`` + ====================== ====================== + + Parameters + ---------- + batchSize + Batch size. Large values allows faster processing but requires more + memory, by default 8 + caseSensitive + Whether to ignore case in tokens for embeddings matching, by default + False + maxSentenceLength + Max sentence length to process, by default 512 + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> documentAssembler = MultiDocumentAssembler() \\ + ... .setInputCols(["question", "context"]) \\ + ... .setOutputCols(["document_question", "document_context"]) + >>> questionAnswering = DistilBertForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") \\ + ... .setCaseSensitive(False) + >>> pipeline = Pipeline().setStages([ + ... documentAssembler, + ... questionAnswering + ... ]) + >>> data = spark.createDataFrame([["The Eiffel Tower is located in which country??", "Germany, France, Italy"]]).toDF("question", "context") + >>> result = pipeline.fit(data).transform(data) + >>> result.select("answer.result").show(truncate=False) + +--------------------+ + |result | + +--------------------+ + |[France] | + +--------------------+ + """ + name = "DistilBertForMultipleChoice" + + inputAnnotatorTypes = [AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT] + + outputAnnotatorType = AnnotatorType.CHUNK + + choicesDelimiter = Param(Params._dummy(), + "choicesDelimiter", + "Delimiter character use to split the choices", + TypeConverters.toString) + + def setChoicesDelimiter(self, value): + """Sets delimiter character use to split the choices + + Parameters + ---------- + value : string + Delimiter character use to split the choices + """ + return self._set(caseSensitive=value) + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.classifier.dl.DistilBertForMultipleChoice", + java_model=None): + super(DistilBertForMultipleChoice, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=4, + maxSentenceLength=512, + caseSensitive=False, + choicesDelimiter = "," + ) + + @staticmethod + def loadSavedModel(folder, spark_session): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + DistilBertForMultipleChoice + The restored model + """ + from sparknlp.internal import _DistilBertMultipleChoiceLoader + jModel = _DistilBertMultipleChoiceLoader(folder, spark_session._jsparkSession)._java_obj + return DistilBertForMultipleChoice(java_model=jModel) + + @staticmethod + def pretrained(name="distilbert_base_uncased_multiple_choice", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "bert_base_uncased_multiple_choice" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + DistilBertForMultipleChoice + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(DistilBertForMultipleChoice, name, lang, remote_loc) \ No newline at end of file diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..f3e0152aab2574 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -211,6 +211,15 @@ def __init__(self, path, jspark): ) +class _DistilBertMultipleChoiceLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark): + super(_DistilBertMultipleChoiceLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.classifier.dl.DistilBertForMultipleChoice.loadSavedModel", + path, + jspark, + ) + + class _ElmoLoader(ExtendedJavaWrapper): def __init__(self, path, jspark): super(_ElmoLoader, self).__init__( diff --git a/python/test/annotator/classifier_dl/distilbert_for_multiple_choice_test.py b/python/test/annotator/classifier_dl/distilbert_for_multiple_choice_test.py new file mode 100644 index 00000000000000..15e3885767017b --- /dev/null +++ b/python/test/annotator/classifier_dl/distilbert_for_multiple_choice_test.py @@ -0,0 +1,76 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import pytest + +from sparknlp.annotator import * +from sparknlp.base import * +from test.util import SparkContextForTest + + +class DistilBertForMultipleChoiceTestSetup(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + self.question = "The Eiffel Tower is located in which country?" + self.choices = "Germany, France, Italy" + + self.spark = SparkContextForTest.spark + empty_df = self.spark.createDataFrame([[""]]).toDF("text") + + document_assembler = MultiDocumentAssembler() \ + .setInputCols(["question", "context"]) \ + .setOutputCols(["document_question", "document_context"]) + + DistilBert_for_multiple_choice = DistilBertForMultipleChoice.pretrained() \ + .setInputCols(["document_question", "document_context"]) \ + .setOutputCol("answer") + + pipeline = Pipeline(stages=[document_assembler, DistilBert_for_multiple_choice]) + + self.pipeline_model = pipeline.fit(empty_df) + + +@pytest.mark.slow +class DistilBertForMultipleChoiceTest(DistilBertForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + self.data = self.spark.createDataFrame([[self.question, self.choices]]).toDF("question","context") + self.data.show(truncate=False) + + def test_run(self): + result_df = self.pipeline_model.transform(self.data) + result_df.show(truncate=False) + for row in result_df.collect(): + self.assertTrue(row["answer"][0].result != "") + + +@pytest.mark.slow +class LightDistilBertForMultipleChoiceTest(DistilBertForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.pipeline_model) + annotations_result = light_pipeline.fullAnnotate(self.question,self.choices) + print(annotations_result) + for result in annotations_result: + self.assertTrue(result["answer"][0].result != "") + + result = light_pipeline.annotate(self.question,self.choices) + print(result) + self.assertTrue(result["answer"] != "") diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala b/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala index 2ae27f9510eaff..fecab2daf79372 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala @@ -484,6 +484,94 @@ private[johnsnowlabs] class DistilBertClassification( (startScores, endScores) } + override def tagSpanMultipleChoice(batch: Seq[Array[Int]]): Array[Float] = { + val logits = detectedEngine match { + case ONNX.name => computeLogitsMultipleChoiceWithOnnx(batch) + case Openvino.name => computeLogitsMultipleChoiceWithOv(batch) + } + + calculateSoftmax(logits) + } + + private def computeLogitsMultipleChoiceWithOnnx(batch: Seq[Array[Int]]): Array[Float] = { + val sequenceLength = batch.head.length + val inputIds = Array(batch.map(x => x.map(_.toLong)).toArray) + val attentionMask = Array( + batch.map(sentence => sentence.map(x => if (x == 0L) 0L else 1L)).toArray) + val tokenTypeIds = Array(batch.map(_ => Array.fill(sequenceLength)(0L)).toArray) + + val (ortSession, ortEnv) = onnxWrapper.get.getSession(onnxSessionOptions) + val tokenTensors = OnnxTensor.createTensor(ortEnv, inputIds) + val maskTensors = OnnxTensor.createTensor(ortEnv, attentionMask) + val segmentTensors = OnnxTensor.createTensor(ortEnv, tokenTypeIds) + + val inputs = + Map( + "input_ids" -> tokenTensors, + "attention_mask" -> maskTensors).asJava + + try { + val output = ortSession.run(inputs) + try { + + val logits = output + .get("logits") + .get() + .asInstanceOf[OnnxTensor] + .getFloatBuffer + .array() + + tokenTensors.close() + maskTensors.close() + segmentTensors.close() + + logits + } finally if (output != null) output.close() + } catch { + case e: Exception => + // Log the exception as a warning + println("Exception in computeLogitsMultipleChoiceWithOnnx: ", e) + // Rethrow the exception to propagate it further + throw e + } + } + + private def computeLogitsMultipleChoiceWithOv(batch: Seq[Array[Int]]): Array[Float] = { + val (numChoices, sequenceLength) = (batch.length, batch.head.length) + // batch_size, num_choices, sequence_length + val shape = Some(Array(1, numChoices, sequenceLength)) + val (tokenTensors, maskTensors, segmentTensors) = + PrepareEmbeddings.prepareOvLongBatchTensorsWithSegment( + batch, + sequenceLength, + numChoices, + sentencePadTokenId, + shape) + + val compiledModel = openvinoWrapper.get.getCompiledModel() + val inferRequest = compiledModel.create_infer_request() + inferRequest.set_tensor("input_ids", tokenTensors) + inferRequest.set_tensor("attention_mask", maskTensors) + + inferRequest.infer() + + try { + try { + val logits = inferRequest + .get_output_tensor() + .data() + + logits + } + } catch { + case e: Exception => + // Log the exception as a warning + logger.warn("Exception in computeLogitsMultipleChoiceWithOv", e) + // Rethrow the exception to propagate it further + throw e + } + } + def computeLogitsWithTF( batch: Seq[Array[Int]], maxSentenceLength: Int): (Array[Float], Array[Float]) = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala new file mode 100644 index 00000000000000..dedf00525a2360 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala @@ -0,0 +1,259 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.ml.ai.DistilBertClassification +import com.johnsnowlabs.ml.onnx.{OnnxWrapper, ReadOnnxModel, WriteOnnxModel} +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.tensorflow.TensorflowWrapper +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadTextAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.serialization.MapFeature +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntParam, Param} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +class DistilBertForMultipleChoice(override val uid: String) + extends AnnotatorModel[DistilBertForMultipleChoice] + with HasBatchedAnnotate[DistilBertForMultipleChoice] + with WriteOnnxModel + with WriteOpenvinoModel + with HasCaseSensitiveProperties + with HasEngine { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("DistilBertForMultipleChoice")) + + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) + override val outputAnnotatorType: AnnotatorType = AnnotatorType.CHUNK + + /** Vocabulary used to encode the words to ids with WordPieceEncoder + * + * @group param + */ + val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() + + /** @group setParam */ + def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value) + + /** @group setParam */ + def sentenceStartTokenId: Int = { + $$(vocabulary)("[CLS]") + } + + /** @group setParam */ + def sentenceEndTokenId: Int = { + $$(vocabulary)("[SEP]") + } + + /** Max sentence length to process (Default: `512`) + * + * @group param + */ + val maxSentenceLength = + new IntParam(this, "maxSentenceLength", "Max sentence length to process") + + /** @group setParam */ + def setMaxSentenceLength(value: Int): this.type = { + require( + value <= 512, + "DistilBERT models do not support sequences longer than 512 because of trainable positional embeddings.") + require(value >= 1, "The maxSentenceLength must be at least 1") + set(maxSentenceLength, value) + this + } + + val choicesDelimiter = + new Param[String](this, "choicesDelimiter", "Delimiter character use to split the choices") + + def setChoicesDelimiter(value: String): this.type = set(choicesDelimiter, value) + + private var _model: Option[Broadcast[DistilBertClassification]] = None + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + tensorflowWrapper: Option[TensorflowWrapper], + onnxWrapper: Option[OnnxWrapper], + openvinoWrapper: Option[OpenvinoWrapper], + ): DistilBertForMultipleChoice = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new DistilBertClassification( + tensorflowWrapper, + onnxWrapper, + openvinoWrapper, + sentenceStartTokenId, + sentenceEndTokenId, + tags = Map.empty[String, Int], + vocabulary = $$(vocabulary)))) + } + + this + } + + /** @group getParam */ + def getModelIfNotSet: DistilBertClassification = _model.get.value + + /** Whether to lowercase tokens or not (Default: `true`). + * + * @group setParam + */ + override def setCaseSensitive(value: Boolean): this.type = set(this.caseSensitive, value) + + setDefault( + batchSize -> 4, + maxSentenceLength -> 512, + caseSensitive -> false, + choicesDelimiter -> ",") + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + * + * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences + * that belong to the same original row !! (challenging) + */ + override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { + batchedAnnotations.map(annotations => { + if (annotations.nonEmpty) { + getModelIfNotSet.predictSpanMultipleChoice( + annotations, + $(choicesDelimiter), + $(maxSentenceLength), + $(caseSensitive)) + } else { + Seq.empty[Annotation] + } + }) + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + + getEngine match { + case ONNX.name => + writeOnnxModel( + path, + spark, + getModelIfNotSet.onnxWrapper.get, + "_distilbert_multiple_choice_classification", + DistilBertForMultipleChoice.onnxFile) + case Openvino.name => + writeOpenvinoModel( + path, + spark, + getModelIfNotSet.openvinoWrapper.get, + "openvino_model.xml", + DistilBertForMultipleChoice.openvinoFile) + } + } + +} + +trait ReadablePretrainedDistilBertForMultipleChoiceModel + extends ParamsAndFeaturesReadable[DistilBertForMultipleChoice] + with HasPretrained[DistilBertForMultipleChoice] { + override val defaultModelName: Some[String] = Some("distilbert_base_uncased_multiple_choice") + + /** Java compliant-overrides */ + override def pretrained(): DistilBertForMultipleChoice = super.pretrained() + + override def pretrained(name: String): DistilBertForMultipleChoice = super.pretrained(name) + + override def pretrained(name: String, lang: String): DistilBertForMultipleChoice = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): DistilBertForMultipleChoice = + super.pretrained(name, lang, remoteLoc) +} + +trait ReadDistilBertForMultipleChoiceModel extends ReadOnnxModel with ReadOpenvinoModel { + this: ParamsAndFeaturesReadable[DistilBertForMultipleChoice] => + + override val onnxFile: String = "distilbert_mc_classification_onnx" + override val openvinoFile: String = "distilbert_mc_classification_openvino" + + def readModel(instance: DistilBertForMultipleChoice, path: String, spark: SparkSession): Unit = { + instance.getEngine match { + case ONNX.name => + val onnxWrapper = + readOnnxModel(path, spark, "distilbert_mc_classification_onnx") + instance.setModelIfNotSet(spark, None, Some(onnxWrapper), None) + case Openvino.name => + val openvinoWrapper = readOpenvinoModel(path, spark, "distilbert_mc_classification_ov") + instance.setModelIfNotSet(spark, None, None, Some(openvinoWrapper)) + case _ => + throw new Exception(notSupportedEngineError) + } + } + + addReader(readModel) + + def loadSavedModel(modelPath: String, spark: SparkSession): DistilBertForMultipleChoice = { + val (localModelPath, detectedEngine) = modelSanityCheck(modelPath) + val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap + val annotatorModel = new DistilBertForMultipleChoice().setVocabulary(vocabs) + annotatorModel.set(annotatorModel.engine, detectedEngine) + + detectedEngine match { + case ONNX.name => + val onnxWrapper = + OnnxWrapper.read(spark, localModelPath, zipped = false, useBundle = true) + annotatorModel + .setModelIfNotSet(spark, None, Some(onnxWrapper), None) + case Openvino.name => + val ovWrapper: OpenvinoWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine) + annotatorModel + .setModelIfNotSet(spark, None, None, Some(ovWrapper)) + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } + +} + +/** This is the companion object of [[DistilBertForMultipleChoice]]. Please refer to that class for the + * documentation. + */ +object DistilBertForMultipleChoice + extends ReadablePretrainedDistilBertForMultipleChoiceModel + with ReadDistilBertForMultipleChoiceModel \ No newline at end of file diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala new file mode 100644 index 00000000000000..247b05d833b8dd --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala @@ -0,0 +1,85 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, MultiDocumentAssembler} +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.tags.SlowTest +import org.apache.spark.ml.{Pipeline, PipelineModel} +import org.scalatest.flatspec.AnyFlatSpec + +class DistilBertForMultipleChoiceTestSpec extends AnyFlatSpec with SparkSessionTest { + + import spark.implicits._ + + lazy val pipelineModel = getDistilBertForMultipleChoicePipelineModel + + val testDataframe = + Seq(("The Eiffel Tower is located in which country?", "Germany, France, Italy")) + .toDF("question", "context") + + + "DistilBertForMultipleChoiceTestSpec" should "answer a multiple choice question" taggedAs SlowTest in { + val resultDf = pipelineModel.transform(testDataframe) + resultDf.show(truncate = false) + + val result = AssertAnnotations.getActualResult(resultDf, "answer") + result.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + } + + it should "work with light pipeline fullAnnotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultFullAnnotate = lightPipeline.fullAnnotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultFullAnnotate") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + assert(answerAnnotation.result.nonEmpty) + } + + it should "work with light pipeline annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultAnnotate = lightPipeline.annotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.nonEmpty) + } + + private def getDistilBertForMultipleChoicePipelineModel: PipelineModel = { + val documentAssembler = new MultiDocumentAssembler() + .setInputCols("question", "context") + .setOutputCols("document_question", "document_context") + + val bertForMultipleChoice = DistilBertForMultipleChoice + .pretrained() + .setInputCols("document_question", "document_context") + .setOutputCol("answer") + + val pipeline = new Pipeline().setStages(Array(documentAssembler, bertForMultipleChoice)) + + pipeline.fit(emptyDataSet) + } + +} From 191c78bbb7ecce7b94c501350f52501cc5e6bcd5 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Thu, 2 Jan 2025 12:14:24 -0500 Subject: [PATCH 004/108] [SPARKNLP-1106] Adding notebook examples for DistilBertForMultipleChoice --- ...park_NLP_DistilBertForMultipleChoice.ipynb | 2751 ++++++++++++++++ ...park_NLP_DistilBertForMultipleChoice.ipynb | 2903 +++++++++++++++++ 2 files changed, 5654 insertions(+) create mode 100644 examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_DistilBertForMultipleChoice.ipynb create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_DistilBertForMultipleChoice.ipynb diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_DistilBertForMultipleChoice.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_DistilBertForMultipleChoice.ipynb new file mode 100644 index 00000000000000..d3ff42a5bec59c --- /dev/null +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_DistilBertForMultipleChoice.ipynb @@ -0,0 +1,2751 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "PAsu8UVGoLVf" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_DistilBertForMultipleChoice.ipynb)\n", + "\n", + "## Import ONNX DistilBertForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- ONNX support was introduced in `Spark NLP 5.0.0`, enabling high performance inference for models.\n", + "- `DistilBertForMultipleChoice` is only available since in `Spark NLP 5.6.0` and after. So please make sure you have upgraded to the latest Spark NLP release\n", + "- You can import BERT models trained/fine-tuned for question answering via `DistilBertForMultipleChoice` or `TFDistilBertForMultipleChoice`. These models are usually under `Multiple Choice` category and have `bert` in their labels\n", + "- Reference: [DistilBertForMultipleChoice](https://huggingface.co/docs/transformers/main/en/model_doc/distilbert#transformers.DistilBertForMultipleChoice)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OzijcdtQpOx9" + }, + "source": [ + "## Export and Save HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MlgoClMXpSg4" + }, + "source": [ + "- Let's install `transformers` package with the `onnx` extension and it's dependencies. You don't need `onnx` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cJWbob-kHICU", + "outputId": "b9a93019-f7a9-4f13-d727-502e8806d75c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/424.1 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r", + "\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[91mโ•ธ\u001b[0m \u001b[32m419.8/424.1 kB\u001b[0m \u001b[31m13.4 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m424.1/424.1 kB\u001b[0m \u001b[31m10.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/13.3 MB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r", + "\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[90mโ•บ\u001b[0m\u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m4.1/13.3 MB\u001b[0m \u001b[31m124.4 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r", + "\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[90mโ•บ\u001b[0m\u001b[90mโ”โ”โ”โ”โ”\u001b[0m \u001b[32m11.4/13.3 MB\u001b[0m \u001b[31m183.8 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r", + "\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[91mโ•ธ\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m195.1 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m110.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/212.7 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m212.7/212.7 kB\u001b[0m \u001b[31m20.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m4.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m480.6/480.6 kB\u001b[0m \u001b[31m42.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m84.5/84.5 kB\u001b[0m \u001b[31m9.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m65.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m455.8/455.8 kB\u001b[0m \u001b[31m38.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m11.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m179.3/179.3 kB\u001b[0m \u001b[31m18.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m9.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m14.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m102.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.5/55.5 kB\u001b[0m \u001b[31m5.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m21.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q --upgrade transformers[onnx] optimum" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XtewR2xdOa5s" + }, + "source": [ + "- HuggingFace has an extension called Optimum which offers specialized model inference, including ONNX. We can use this to import and export ONNX models with `from_pretrained` and `save_pretrained`.\n", + "- We'll use the treained model above as an example and load it as a `ORTModelForMultipleChoice`, representing an ONNX model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 313, + "referenced_widgets": [ + "3fa8c62ddf034288bbe3c585e481582a", + "b942bc8a478b40c18b3afeb2ef549b82", + "74b1760ba60d4217be2b0a0540f9dc22", + "e6bd749383ad44c49d7d2555ed8f773a", + "35e316e7eedc4829a1c421b87e25cc6b", + "d10a939a7c294d51ac0970c1e6e67400", + "c501f85ca9104291b32766594abfb177", + "35c33ddbb5e64f2eaad2cf22bd2b312e", + "8e1edff76eb74812a4a57d8f9a7c66a5", + "f76dd41f539a42d883ea85d6fc249381", + "dc07286d1da74c1b8c80edf7b22ec105", + "5c1e19066ac2428f892903b335136e05", + "41bfa9e47892418981d2105b727c6651", + "10177825cf9746fe8f142462f7723aef", + "be47affd0e774f038ba4b935a7dc0fda", + "115e14b1a23046f6bde91b91368110cd", + "4c1c4ff45bdc4658b11d783c67d08f79", + "367b13b5545a4c65a5d8a827720271f6", + "895587a2608a49e7ad3d6132abb3a833", + "8a3001347f6e47afac38e3fa860a7aee", + "341bc46cd55148759af3deb3ad08a816", + "0ae4ff0010404177ae68147ac49ea7e6", + "2f44f043a48c4b79a08c8b8fab68be5c", + "b01be358bde44be1b233b147dfaaf9ad", + "b4e498dbede94fa49d93372f238b4966", + "c05e109ef7344f92a1fb33cd3d6b45e2", + "5bef47b4061f4d638796c17779e780dc", + "45757698c34b4db9ac25e7257cd15435", + "0c3da599f3304566920f6002e90a6961", + "d36768137ea04899b36b79c738a13fa6", + "2f9901d1aaf14f2c9bbed14e85e58ca1", + "19b1c94ebf85413da435ce89cad2d666", + "b851e1cbb82f446bb6a275afed0cc3b1", + "1e5cbd2c3f46415fbbc0ce27f4c48ce9", + "65331501cffe49428c3ee28e43432b74", + "1a9b4a12cc9443059cb57a411d1c2637", + "455d7eadeb44420a883b2066628640d8", + "b39e0fb3fbf44cba8fab21ddfac7a2cc", + "bdb846fc731e487ca1a72f55d7aa8d6e", + "9dca665841224956be77804fe1f90391", + "f23cd3265e034825b8cd3dd2d393d421", + "4c958a2b95b944c7829a9d2bc0cb77ac", + "9388c8035d2c47e08ab99040dcd00cf9", + "62f926c3d9f9461596521ba34e795b3a", + "d7b0df281fcb4e95abc2ac1f33503fb2", + "11fce16817ab484f953b590045ed3d5c", + "005cdb63a2ef454f8c7f401ec377e2df", + "a5d4aa7482434d43b0d856558d27664c", + "c8d906a71eb24d83bb823bac76af29cc", + "7d6ebb345cba4bcab22f9d1f7261d5c8", + "f4942df43b0d42e8ade2b3a953e76c86", + "1fb6829e34664a0cb82c56616160a4a7", + "4e3c9f8eb4964535a1ae416600f57e93", + "540d3b239d76402d8495be7e23351f07", + "fd869a4e8ec5472aa15916213b60f1cb", + "49307ab4126b42618a7c16fa81c22117", + "9a99cd70f2ff4e41ba7e60ea0142d45b", + "ab3833fcf88747c69da9c98ca60b3443", + "67bff7bbad194bef948cb8dfd985cd1b", + "6999f459998a4032ad29f074d5134847", + "cb1bef115ba54c549acc1056c3149ebd", + "3826c29474704eca93ed9a7d97865909", + "2999fb3ce9b041839ff03250080f0501", + "09369dd140294679ac321835d8a085e2", + "176a27c38a96473c8603b69e4009daf8", + "15a0cd6793ef4c4dbcbada0adada95a9" + ] + }, + "id": "Id33annImYM8", + "outputId": "a195cc94-133d-4705-cb1f-a3cd12fff670" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3fa8c62ddf034288bbe3c585e481582a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/574 [00:00 0, chunk -> 0, score -> 0.6048505}, []}]|\n", + "|[{chunk, 0, 6, Germany, {sentence -> 0, chunk -> 0, score -> 0.39137164}, []}] |\n", + "|[{chunk, 0, 5, Tiger, {sentence -> 0, chunk -> 0, score -> 0.2897997}, []}] |\n", + "|[{chunk, 0, 3, 90ยฐC, {sentence -> 0, chunk -> 0, score -> 0.35916787}, []}] |\n", + "|[{chunk, 0, 6, Jupiter, {sentence -> 0, chunk -> 0, score -> 0.35939977}, []}] |\n", + "|[{chunk, 0, 7, English, {sentence -> 0, chunk -> 0, score -> 0.3640033}, []}] |\n", + "|[{chunk, 0, 11, The Mongols, {sentence -> 0, chunk -> 0, score -> 0.29171145}, []}] |\n", + "|[{chunk, 0, 6, Osmium, {sentence -> 0, chunk -> 0, score -> 0.4062368}, []}] |\n", + "|[{chunk, 0, 13, South America, {sentence -> 0, chunk -> 0, score -> 0.392063}, []}] |\n", + "|[{chunk, 0, 13, Pablo Picasso, {sentence -> 0, chunk -> 0, score -> 0.4129128}, []}] |\n", + "+----------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "distilbert_for_multiple_choice = DistilBertForMultipleChoice() \\\n", + " .load(\"./{}_spark_nlp_onnx\".format(MODEL_NAME)) \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, distilbert_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "A100", + "machine_shape": "hm", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "005cdb63a2ef454f8c7f401ec377e2df": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_1fb6829e34664a0cb82c56616160a4a7", + "max": 711396, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_4e3c9f8eb4964535a1ae416600f57e93", + "value": 711396 + } + }, + "09369dd140294679ac321835d8a085e2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "0ae4ff0010404177ae68147ac49ea7e6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "0c3da599f3304566920f6002e90a6961": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "10177825cf9746fe8f142462f7723aef": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_895587a2608a49e7ad3d6132abb3a833", + "max": 267829484, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_8a3001347f6e47afac38e3fa860a7aee", + "value": 267829484 + } + }, + "115e14b1a23046f6bde91b91368110cd": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "11fce16817ab484f953b590045ed3d5c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_7d6ebb345cba4bcab22f9d1f7261d5c8", + "placeholder": "โ€‹", + "style": "IPY_MODEL_f4942df43b0d42e8ade2b3a953e76c86", + "value": "tokenizer.json:โ€‡100%" + } + }, + "15a0cd6793ef4c4dbcbada0adada95a9": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "176a27c38a96473c8603b69e4009daf8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "19b1c94ebf85413da435ce89cad2d666": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "1a9b4a12cc9443059cb57a411d1c2637": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_f23cd3265e034825b8cd3dd2d393d421", + "max": 231508, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_4c958a2b95b944c7829a9d2bc0cb77ac", + "value": 231508 + } + }, + "1e5cbd2c3f46415fbbc0ce27f4c48ce9": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_65331501cffe49428c3ee28e43432b74", + "IPY_MODEL_1a9b4a12cc9443059cb57a411d1c2637", + "IPY_MODEL_455d7eadeb44420a883b2066628640d8" + ], + "layout": "IPY_MODEL_b39e0fb3fbf44cba8fab21ddfac7a2cc" + } + }, + "1fb6829e34664a0cb82c56616160a4a7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2999fb3ce9b041839ff03250080f0501": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2f44f043a48c4b79a08c8b8fab68be5c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_b01be358bde44be1b233b147dfaaf9ad", + "IPY_MODEL_b4e498dbede94fa49d93372f238b4966", + "IPY_MODEL_c05e109ef7344f92a1fb33cd3d6b45e2" + ], + "layout": "IPY_MODEL_5bef47b4061f4d638796c17779e780dc" + } + }, + "2f9901d1aaf14f2c9bbed14e85e58ca1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "341bc46cd55148759af3deb3ad08a816": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "35c33ddbb5e64f2eaad2cf22bd2b312e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "35e316e7eedc4829a1c421b87e25cc6b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "367b13b5545a4c65a5d8a827720271f6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "3826c29474704eca93ed9a7d97865909": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "3fa8c62ddf034288bbe3c585e481582a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_b942bc8a478b40c18b3afeb2ef549b82", + "IPY_MODEL_74b1760ba60d4217be2b0a0540f9dc22", + "IPY_MODEL_e6bd749383ad44c49d7d2555ed8f773a" + ], + "layout": "IPY_MODEL_35e316e7eedc4829a1c421b87e25cc6b" + } + }, + "41bfa9e47892418981d2105b727c6651": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_4c1c4ff45bdc4658b11d783c67d08f79", + "placeholder": "โ€‹", + "style": "IPY_MODEL_367b13b5545a4c65a5d8a827720271f6", + "value": "model.safetensors:โ€‡100%" + } + }, + "455d7eadeb44420a883b2066628640d8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_9388c8035d2c47e08ab99040dcd00cf9", + "placeholder": "โ€‹", + "style": "IPY_MODEL_62f926c3d9f9461596521ba34e795b3a", + "value": "โ€‡232k/232kโ€‡[00:00<00:00,โ€‡2.56MB/s]" + } + }, + "45757698c34b4db9ac25e7257cd15435": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "49307ab4126b42618a7c16fa81c22117": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_9a99cd70f2ff4e41ba7e60ea0142d45b", + "IPY_MODEL_ab3833fcf88747c69da9c98ca60b3443", + "IPY_MODEL_67bff7bbad194bef948cb8dfd985cd1b" + ], + "layout": "IPY_MODEL_6999f459998a4032ad29f074d5134847" + } + }, + "4c1c4ff45bdc4658b11d783c67d08f79": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "4c958a2b95b944c7829a9d2bc0cb77ac": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "4e3c9f8eb4964535a1ae416600f57e93": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "540d3b239d76402d8495be7e23351f07": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5bef47b4061f4d638796c17779e780dc": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5c1e19066ac2428f892903b335136e05": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_41bfa9e47892418981d2105b727c6651", + "IPY_MODEL_10177825cf9746fe8f142462f7723aef", + "IPY_MODEL_be47affd0e774f038ba4b935a7dc0fda" + ], + "layout": "IPY_MODEL_115e14b1a23046f6bde91b91368110cd" + } + }, + "62f926c3d9f9461596521ba34e795b3a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "65331501cffe49428c3ee28e43432b74": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_bdb846fc731e487ca1a72f55d7aa8d6e", + "placeholder": "โ€‹", + "style": "IPY_MODEL_9dca665841224956be77804fe1f90391", + "value": "vocab.txt:โ€‡100%" + } + }, + "67bff7bbad194bef948cb8dfd985cd1b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_176a27c38a96473c8603b69e4009daf8", + "placeholder": "โ€‹", + "style": "IPY_MODEL_15a0cd6793ef4c4dbcbada0adada95a9", + "value": "โ€‡125/125โ€‡[00:00<00:00,โ€‡10.1kB/s]" + } + }, + "6999f459998a4032ad29f074d5134847": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "74b1760ba60d4217be2b0a0540f9dc22": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_35c33ddbb5e64f2eaad2cf22bd2b312e", + "max": 574, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_8e1edff76eb74812a4a57d8f9a7c66a5", + "value": 574 + } + }, + "7d6ebb345cba4bcab22f9d1f7261d5c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "895587a2608a49e7ad3d6132abb3a833": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8a3001347f6e47afac38e3fa860a7aee": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "8e1edff76eb74812a4a57d8f9a7c66a5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "9388c8035d2c47e08ab99040dcd00cf9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "9a99cd70f2ff4e41ba7e60ea0142d45b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_cb1bef115ba54c549acc1056c3149ebd", + "placeholder": "โ€‹", + "style": "IPY_MODEL_3826c29474704eca93ed9a7d97865909", + "value": "special_tokens_map.json:โ€‡100%" + } + }, + "9dca665841224956be77804fe1f90391": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "a5d4aa7482434d43b0d856558d27664c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_540d3b239d76402d8495be7e23351f07", + "placeholder": "โ€‹", + "style": "IPY_MODEL_fd869a4e8ec5472aa15916213b60f1cb", + "value": "โ€‡711k/711kโ€‡[00:00<00:00,โ€‡15.3MB/s]" + } + }, + "ab3833fcf88747c69da9c98ca60b3443": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_2999fb3ce9b041839ff03250080f0501", + "max": 125, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_09369dd140294679ac321835d8a085e2", + "value": 125 + } + }, + "b01be358bde44be1b233b147dfaaf9ad": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_45757698c34b4db9ac25e7257cd15435", + "placeholder": "โ€‹", + "style": "IPY_MODEL_0c3da599f3304566920f6002e90a6961", + "value": "tokenizer_config.json:โ€‡100%" + } + }, + "b39e0fb3fbf44cba8fab21ddfac7a2cc": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b4e498dbede94fa49d93372f238b4966": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_d36768137ea04899b36b79c738a13fa6", + "max": 1224, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_2f9901d1aaf14f2c9bbed14e85e58ca1", + "value": 1224 + } + }, + "b851e1cbb82f446bb6a275afed0cc3b1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "b942bc8a478b40c18b3afeb2ef549b82": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_d10a939a7c294d51ac0970c1e6e67400", + "placeholder": "โ€‹", + "style": "IPY_MODEL_c501f85ca9104291b32766594abfb177", + "value": "config.json:โ€‡100%" + } + }, + "bdb846fc731e487ca1a72f55d7aa8d6e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "be47affd0e774f038ba4b935a7dc0fda": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_341bc46cd55148759af3deb3ad08a816", + "placeholder": "โ€‹", + "style": "IPY_MODEL_0ae4ff0010404177ae68147ac49ea7e6", + "value": "โ€‡268M/268Mโ€‡[00:06<00:00,โ€‡42.6MB/s]" + } + }, + "c05e109ef7344f92a1fb33cd3d6b45e2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_19b1c94ebf85413da435ce89cad2d666", + "placeholder": "โ€‹", + "style": "IPY_MODEL_b851e1cbb82f446bb6a275afed0cc3b1", + "value": "โ€‡1.22k/1.22kโ€‡[00:00<00:00,โ€‡99.0kB/s]" + } + }, + "c501f85ca9104291b32766594abfb177": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "c8d906a71eb24d83bb823bac76af29cc": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "cb1bef115ba54c549acc1056c3149ebd": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d10a939a7c294d51ac0970c1e6e67400": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d36768137ea04899b36b79c738a13fa6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d7b0df281fcb4e95abc2ac1f33503fb2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_11fce16817ab484f953b590045ed3d5c", + "IPY_MODEL_005cdb63a2ef454f8c7f401ec377e2df", + "IPY_MODEL_a5d4aa7482434d43b0d856558d27664c" + ], + "layout": "IPY_MODEL_c8d906a71eb24d83bb823bac76af29cc" + } + }, + "dc07286d1da74c1b8c80edf7b22ec105": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "e6bd749383ad44c49d7d2555ed8f773a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_f76dd41f539a42d883ea85d6fc249381", + "placeholder": "โ€‹", + "style": "IPY_MODEL_dc07286d1da74c1b8c80edf7b22ec105", + "value": "โ€‡574/574โ€‡[00:00<00:00,โ€‡48.8kB/s]" + } + }, + "f23cd3265e034825b8cd3dd2d393d421": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f4942df43b0d42e8ade2b3a953e76c86": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "f76dd41f539a42d883ea85d6fc249381": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fd869a4e8ec5472aa15916213b60f1cb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_DistilBertForMultipleChoice.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_DistilBertForMultipleChoice.ipynb new file mode 100644 index 00000000000000..018ee5807e529d --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_DistilBertForMultipleChoice.ipynb @@ -0,0 +1,2903 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "_V5XcDCnVgSi" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_DistilBertForMultipleChoice.ipynb)\n", + "\n", + "# Import OpenVINO DistilBertForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and exporting DistilBertForMultipleChoice models from HuggingFace for use in Spark NLP, leveraging the various tools provided in the [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html) ecosystem.\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance inference for models. Please make sure you have upgraded to the latest Spark NLP release.\n", + "- You can import models for DistilBertForMultipleChoice from DistilBertForMultipleChoice and they have to be in `Multiple Choice` category." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aghasVppVgSk" + }, + "source": [ + "## 1. Export and Save the HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "be4HsTDMVgSk" + }, + "source": [ + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future releases, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "-7L-2ZWUVgSl", + "outputId": "5d2d172b-5f02-4639-83fc-82b9e601dfe3" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m43.8/43.8 kB\u001b[0m \u001b[31m1.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m9.1/9.1 MB\u001b[0m \u001b[31m61.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m3.6/3.6 MB\u001b[0m \u001b[31m84.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m38.7/38.7 MB\u001b[0m \u001b[31m52.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m215.7/215.7 kB\u001b[0m \u001b[31m6.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m480.6/480.6 kB\u001b[0m \u001b[31m21.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m424.1/424.1 kB\u001b[0m \u001b[31m38.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m102.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m13.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m179.3/179.3 kB\u001b[0m \u001b[31m18.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m13.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m4.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m18.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m9.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.1/13.1 MB\u001b[0m \u001b[31m101.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m66.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "google-ai-generativelanguage 0.6.10 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-api-core 2.19.2 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0.dev0,>=3.19.5, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-aiplatform 1.74.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-bigquery-connection 1.17.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-bigquery-storage 2.27.0 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-bigtable 2.27.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-datastore 2.20.2 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-firestore 2.19.0 requires protobuf!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-functions 1.19.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-iam 2.17.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-language 2.16.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-pubsub 2.27.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-resource-manager 1.14.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "google-cloud-translate 3.19.0 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "googleapis-common-protos 1.66.0 requires protobuf!=3.20.0,!=3.20.1,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0.dev0,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "grpc-google-iam-v1 0.13.1 requires protobuf!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<6.0.0dev,>=3.20.2, but you have protobuf 3.20.1 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.1 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.1 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.1 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q --upgrade transformers==4.41.2\n", + "!pip install -q --upgrade openvino==2024.1\n", + "!pip install -q --upgrade optimum-intel==1.17.0\n", + "!pip install -q --upgrade onnx==1.12.0" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vI7uz_6hVgSl" + }, + "source": [ + "[Optimum Intel](https://github.com/huggingface/optimum-intel?tab=readme-ov-file#openvino) is the interface between the Transformers library and the various model optimization and acceleration tools provided by Intel. HuggingFace models loaded with optimum-intel are automatically optimized for OpenVINO, while being compatible with the Transformers API.\n", + "- Normally, to load a HuggingFace model directly for inference/export, just replace the `AutoModelForXxx` class with the corresponding `OVModelForXxx` class. However, ForMultipleChoice is not yet available so we will use `openvino.convert_model()` after exporting ONNX model\n", + "- We'll use [irfanamal/bert_multiple_choice](https://huggingface.co/irfanamal/bert_multiple_choice) model from HuggingFace as an example\n", + "- We also need the `vocab.txt` saved from `AutoTokenizer`. This is the same for every model, these are assets (saved in `/assets`) needed for tokenization inside Spark NLP." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TDapJ_09nqXQ", + "outputId": "3d8b2ec9-b2c9-4fe8-b3de-0654a43416ba" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pip in /usr/local/lib/python3.10/dist-packages (24.1.2)\n", + "Collecting pip\n", + " Downloading pip-24.3.1-py3-none-any.whl.metadata (3.7 kB)\n", + "Downloading pip-24.3.1-py3-none-any.whl (1.8 MB)\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m22.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: pip\n", + " Attempting uninstall: pip\n", + " Found existing installation: pip 24.1.2\n", + " Uninstalling pip-24.1.2:\n", + " Successfully uninstalled pip-24.1.2\n", + "Successfully installed pip-24.3.1\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m113.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m3.0/3.0 MB\u001b[0m \u001b[31m115.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m10.1/10.1 MB\u001b[0m \u001b[31m151.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m60.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "optimum-intel 1.17.0 requires transformers<4.42.0,>=4.36.0, but you have transformers 4.47.1 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install --upgrade pip\n", + "!pip install -q --upgrade transformers[onnx] optimum openvino==2024.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 313, + "referenced_widgets": [ + "1685190d1c0b40dc9a04bf961ed1fe9a", + "8faf63a59311431e8a4f8149b3d09be3", + "04caa1515af146588adb1552ece36dcd", + "0a41311d87454a49b28792762d1ad390", + "df80408d1d9f4995bce81c9657a16902", + "d09eb9036f7042979767eee8b8adafdd", + "e45f9f7671f84e5783ba53cdafbf0eee", + "3787315381de4f4a8f383dffe4dd1318", + "92efd8cc36b24ba7827eb8ee8d5ddd76", + "586875b1181840658f82ed3791819ea9", + "e5927cae79b2469a8c72dc30591f895c", + "cd2acd5f7e7f4811906e9efdf91cf2f3", + "497ac3bcc25b4710994554005e19ca36", + "7154097716564b4db750626f7efdffc7", + "103d60fc85664ea6852c6cf65a838f08", + "94034333eb9f4399ae56edfa1b9d4b9b", + "153a98ebce99415bbaa8f3bcf0e003a3", + "56b0be1d416b4e968a9b5380367dcd77", + "48d8e523726b426ab553e158e1738d35", + "6758258d40b34089bb9a2ff3d3008c98", + "abde155ca5604caea5996b7eb88e80a3", + "f2e9969ee9364f8b96c0ccbe17b04dd3", + "c77c1345e62441ec9f0cccefd74680ae", + "bda09c689b7e451b9f205bcd89e2892c", + "4d1ce1e3122d4685895e531179f1a6f8", + "ea563bffe9a9402797647d6e57726b07", + "11e9347df720416ab68f7254699e4bfd", + "d74e96dcba28405bb0b90863c372b9a3", + "4143428097184e239be3aa15a709a1bd", + "ebb172d5ac1c4e2381a5e02b3d009d71", + "3935021ef68448f394a93f6a6215724b", + "f73b5c70ce424707951099c81a54ec0f", + "545fcedce22f463e9745cf2785c6260f", + "5acb405e46c7407aa3d38e49d4087a0b", + "e27b262eb28f48a8b39c32a4f65bf74f", + "1da886fbe0a341059120356697806776", + "16da8069c945418f8bb156a7f6d25748", + "795ff4db8fae4852a055586f00364be4", + "1ce12659ed8748239d6755f6da042a98", + "2cba33d72df64451856a650f45f1f765", + "8774027c38a1482895e6c21e6d0e662b", + "c2a9db5e3ef54f7fb704f599e65af401", + "4f3818960d504431a7eb18cd7488e2ba", + "77b7b75457ef4c38ac2634eff41a29d4", + "ea3db61523174ad08842b86d8645fe36", + "7adfc44756d64dbd9552b3e4c7315424", + "9280dc685751491d96554819dce773f2", + "cbc7ecd928d841ee9c448fbb1643612c", + "9a6dd0ec39954a46982e711ece20d8c8", + "83f34c62dcc64d6abb92b880877c3b31", + "466e13e3b2864431962a8a0e29473063", + "dc1dc8a06d7f4b949a50e388e84f690f", + "3e93119eb321493281c6a8cbdf5bb226", + "7061bf9eac4d4f4b8f7250fc8043d912", + "783ee37e722c49d69bc856466340d9eb", + "3a4eaa1f68fd4e9cbd36d29815918c6a", + "4e57df313f4a43fab68b281d93893952", + "a089f60df2b9404290a4634deaf0654a", + "b763f3ad27f044139de5d4eb656153a1", + "ecf9f67c924e4ea69fd2a6b9fb9ef2a6", + "203467747a404ad9b34c642b1a05b711", + "620132d19704448386b7aced66a92ab6", + "c1dde60b58ce4a449f0663cd8a6040b6", + "146d64045c554e66afa4dbd4797971e8", + "26c383992f4742ca92e8132e9aee73b9", + "35c6f2532f6f44a6a90ce6c3a508791c" + ] + }, + "id": "_b89GvQKosA0", + "outputId": "afab9178-e2fd-4979-dbe5-5dcee3c3036f" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1685190d1c0b40dc9a04bf961ed1fe9a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/574 [00:00 0, chunk -> 0, score -> 0.60487646}, []}]|\n", + "|[{chunk, 0, 6, Germany, {sentence -> 0, chunk -> 0, score -> 0.39134768}, []}] |\n", + "|[{chunk, 0, 5, Tiger, {sentence -> 0, chunk -> 0, score -> 0.28978878}, []}] |\n", + "|[{chunk, 0, 3, 90ยฐC, {sentence -> 0, chunk -> 0, score -> 0.35916173}, []}] |\n", + "|[{chunk, 0, 6, Jupiter, {sentence -> 0, chunk -> 0, score -> 0.35947314}, []}] |\n", + "|[{chunk, 0, 7, English, {sentence -> 0, chunk -> 0, score -> 0.36399662}, []}] |\n", + "|[{chunk, 0, 11, The Mongols, {sentence -> 0, chunk -> 0, score -> 0.29171973}, []}] |\n", + "|[{chunk, 0, 6, Osmium, {sentence -> 0, chunk -> 0, score -> 0.40618128}, []}] |\n", + "|[{chunk, 0, 13, South America, {sentence -> 0, chunk -> 0, score -> 0.39206758}, []}] |\n", + "|[{chunk, 0, 13, Pablo Picasso, {sentence -> 0, chunk -> 0, score -> 0.4128621}, []}] |\n", + "+-----------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.ml import Pipeline, PipelineModel\n", + "\n", + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "distilbert_for_multiple_choice = DistilBertForMultipleChoice() \\\n", + " .load(f\"{MODEL_NAME}_spark_nlp_openvino\") \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, distilbert_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lpxiq1igoj6c" + }, + "source": [ + "That's it! You can now go wild and use hundreds of `DistilBertForMultipleChoice` models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "A100", + "machine_shape": "hm", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "04caa1515af146588adb1552ece36dcd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_3787315381de4f4a8f383dffe4dd1318", + "max": 574, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_92efd8cc36b24ba7827eb8ee8d5ddd76", + "value": 574 + } + }, + "0a41311d87454a49b28792762d1ad390": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_586875b1181840658f82ed3791819ea9", + "placeholder": "โ€‹", + "style": "IPY_MODEL_e5927cae79b2469a8c72dc30591f895c", + "value": "โ€‡574/574โ€‡[00:00<00:00,โ€‡44.8kB/s]" + } + }, + "103d60fc85664ea6852c6cf65a838f08": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_abde155ca5604caea5996b7eb88e80a3", + "placeholder": "โ€‹", + "style": "IPY_MODEL_f2e9969ee9364f8b96c0ccbe17b04dd3", + "value": "โ€‡268M/268Mโ€‡[00:06<00:00,โ€‡42.4MB/s]" + } + }, + "11e9347df720416ab68f7254699e4bfd": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "146d64045c554e66afa4dbd4797971e8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "153a98ebce99415bbaa8f3bcf0e003a3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "1685190d1c0b40dc9a04bf961ed1fe9a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_8faf63a59311431e8a4f8149b3d09be3", + "IPY_MODEL_04caa1515af146588adb1552ece36dcd", + "IPY_MODEL_0a41311d87454a49b28792762d1ad390" + ], + "layout": "IPY_MODEL_df80408d1d9f4995bce81c9657a16902" + } + }, + "16da8069c945418f8bb156a7f6d25748": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_4f3818960d504431a7eb18cd7488e2ba", + "placeholder": "โ€‹", + "style": "IPY_MODEL_77b7b75457ef4c38ac2634eff41a29d4", + "value": "โ€‡232k/232kโ€‡[00:00<00:00,โ€‡2.76MB/s]" + } + }, + "1ce12659ed8748239d6755f6da042a98": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "1da886fbe0a341059120356697806776": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_8774027c38a1482895e6c21e6d0e662b", + "max": 231508, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_c2a9db5e3ef54f7fb704f599e65af401", + "value": 231508 + } + }, + "203467747a404ad9b34c642b1a05b711": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "26c383992f4742ca92e8132e9aee73b9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2cba33d72df64451856a650f45f1f765": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "35c6f2532f6f44a6a90ce6c3a508791c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "3787315381de4f4a8f383dffe4dd1318": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3935021ef68448f394a93f6a6215724b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "3a4eaa1f68fd4e9cbd36d29815918c6a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_4e57df313f4a43fab68b281d93893952", + "IPY_MODEL_a089f60df2b9404290a4634deaf0654a", + "IPY_MODEL_b763f3ad27f044139de5d4eb656153a1" + ], + "layout": "IPY_MODEL_ecf9f67c924e4ea69fd2a6b9fb9ef2a6" + } + }, + "3e93119eb321493281c6a8cbdf5bb226": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "4143428097184e239be3aa15a709a1bd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "466e13e3b2864431962a8a0e29473063": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "48d8e523726b426ab553e158e1738d35": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "497ac3bcc25b4710994554005e19ca36": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_153a98ebce99415bbaa8f3bcf0e003a3", + "placeholder": "โ€‹", + "style": "IPY_MODEL_56b0be1d416b4e968a9b5380367dcd77", + "value": "model.safetensors:โ€‡100%" + } + }, + "4d1ce1e3122d4685895e531179f1a6f8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_ebb172d5ac1c4e2381a5e02b3d009d71", + "max": 1224, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_3935021ef68448f394a93f6a6215724b", + "value": 1224 + } + }, + "4e57df313f4a43fab68b281d93893952": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_203467747a404ad9b34c642b1a05b711", + "placeholder": "โ€‹", + "style": "IPY_MODEL_620132d19704448386b7aced66a92ab6", + "value": "special_tokens_map.json:โ€‡100%" + } + }, + "4f3818960d504431a7eb18cd7488e2ba": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "545fcedce22f463e9745cf2785c6260f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "56b0be1d416b4e968a9b5380367dcd77": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "586875b1181840658f82ed3791819ea9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5acb405e46c7407aa3d38e49d4087a0b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e27b262eb28f48a8b39c32a4f65bf74f", + "IPY_MODEL_1da886fbe0a341059120356697806776", + "IPY_MODEL_16da8069c945418f8bb156a7f6d25748" + ], + "layout": "IPY_MODEL_795ff4db8fae4852a055586f00364be4" + } + }, + "620132d19704448386b7aced66a92ab6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "6758258d40b34089bb9a2ff3d3008c98": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7061bf9eac4d4f4b8f7250fc8043d912": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7154097716564b4db750626f7efdffc7": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_48d8e523726b426ab553e158e1738d35", + "max": 267829484, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_6758258d40b34089bb9a2ff3d3008c98", + "value": 267829484 + } + }, + "77b7b75457ef4c38ac2634eff41a29d4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "783ee37e722c49d69bc856466340d9eb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "795ff4db8fae4852a055586f00364be4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7adfc44756d64dbd9552b3e4c7315424": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_83f34c62dcc64d6abb92b880877c3b31", + "placeholder": "โ€‹", + "style": "IPY_MODEL_466e13e3b2864431962a8a0e29473063", + "value": "tokenizer.json:โ€‡100%" + } + }, + "83f34c62dcc64d6abb92b880877c3b31": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8774027c38a1482895e6c21e6d0e662b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8faf63a59311431e8a4f8149b3d09be3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_d09eb9036f7042979767eee8b8adafdd", + "placeholder": "โ€‹", + "style": "IPY_MODEL_e45f9f7671f84e5783ba53cdafbf0eee", + "value": "config.json:โ€‡100%" + } + }, + "9280dc685751491d96554819dce773f2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dc1dc8a06d7f4b949a50e388e84f690f", + "max": 711396, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_3e93119eb321493281c6a8cbdf5bb226", + "value": 711396 + } + }, + "92efd8cc36b24ba7827eb8ee8d5ddd76": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "94034333eb9f4399ae56edfa1b9d4b9b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "9a6dd0ec39954a46982e711ece20d8c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a089f60df2b9404290a4634deaf0654a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_c1dde60b58ce4a449f0663cd8a6040b6", + "max": 125, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_146d64045c554e66afa4dbd4797971e8", + "value": 125 + } + }, + "abde155ca5604caea5996b7eb88e80a3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b763f3ad27f044139de5d4eb656153a1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_26c383992f4742ca92e8132e9aee73b9", + "placeholder": "โ€‹", + "style": "IPY_MODEL_35c6f2532f6f44a6a90ce6c3a508791c", + "value": "โ€‡125/125โ€‡[00:00<00:00,โ€‡8.24kB/s]" + } + }, + "bda09c689b7e451b9f205bcd89e2892c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_d74e96dcba28405bb0b90863c372b9a3", + "placeholder": "โ€‹", + "style": "IPY_MODEL_4143428097184e239be3aa15a709a1bd", + "value": "tokenizer_config.json:โ€‡100%" + } + }, + "c1dde60b58ce4a449f0663cd8a6040b6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c2a9db5e3ef54f7fb704f599e65af401": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "c77c1345e62441ec9f0cccefd74680ae": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_bda09c689b7e451b9f205bcd89e2892c", + "IPY_MODEL_4d1ce1e3122d4685895e531179f1a6f8", + "IPY_MODEL_ea563bffe9a9402797647d6e57726b07" + ], + "layout": "IPY_MODEL_11e9347df720416ab68f7254699e4bfd" + } + }, + "cbc7ecd928d841ee9c448fbb1643612c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_7061bf9eac4d4f4b8f7250fc8043d912", + "placeholder": "โ€‹", + "style": "IPY_MODEL_783ee37e722c49d69bc856466340d9eb", + "value": "โ€‡711k/711kโ€‡[00:00<00:00,โ€‡5.21MB/s]" + } + }, + "cd2acd5f7e7f4811906e9efdf91cf2f3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_497ac3bcc25b4710994554005e19ca36", + "IPY_MODEL_7154097716564b4db750626f7efdffc7", + "IPY_MODEL_103d60fc85664ea6852c6cf65a838f08" + ], + "layout": "IPY_MODEL_94034333eb9f4399ae56edfa1b9d4b9b" + } + }, + "d09eb9036f7042979767eee8b8adafdd": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d74e96dcba28405bb0b90863c372b9a3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dc1dc8a06d7f4b949a50e388e84f690f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "df80408d1d9f4995bce81c9657a16902": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e27b262eb28f48a8b39c32a4f65bf74f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_1ce12659ed8748239d6755f6da042a98", + "placeholder": "โ€‹", + "style": "IPY_MODEL_2cba33d72df64451856a650f45f1f765", + "value": "vocab.txt:โ€‡100%" + } + }, + "e45f9f7671f84e5783ba53cdafbf0eee": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "e5927cae79b2469a8c72dc30591f895c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "ea3db61523174ad08842b86d8645fe36": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_7adfc44756d64dbd9552b3e4c7315424", + "IPY_MODEL_9280dc685751491d96554819dce773f2", + "IPY_MODEL_cbc7ecd928d841ee9c448fbb1643612c" + ], + "layout": "IPY_MODEL_9a6dd0ec39954a46982e711ece20d8c8" + } + }, + "ea563bffe9a9402797647d6e57726b07": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_f73b5c70ce424707951099c81a54ec0f", + "placeholder": "โ€‹", + "style": "IPY_MODEL_545fcedce22f463e9745cf2785c6260f", + "value": "โ€‡1.22k/1.22kโ€‡[00:00<00:00,โ€‡107kB/s]" + } + }, + "ebb172d5ac1c4e2381a5e02b3d009d71": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ecf9f67c924e4ea69fd2a6b9fb9ef2a6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f2e9969ee9364f8b96c0ccbe17b04dd3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "f73b5c70ce424707951099c81a54ec0f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} From 52e4cd47356a7b3acdb368f967147883cee4e8ac Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Fri, 3 Jan 2025 14:59:25 -0500 Subject: [PATCH 005/108] [SPARKNLP-1107] Introducing RoBertaForMultipleChoice --- .../annotator/classifier_dl/__init__.py | 1 + .../roberta_for_multiple_choice.py | 161 +++++++++ python/sparknlp/internal/__init__.py | 9 + .../roberta_for_multiple_choice_test.py | 77 +++++ .../ml/ai/RoBertaClassification.scala | 85 +++++ .../dl/RoBertaForMultipleChoice.scala | 308 ++++++++++++++++++ .../dl/RobertaForMultipleChoiceTestSpec.scala | 83 +++++ 7 files changed, 724 insertions(+) create mode 100644 python/sparknlp/annotator/classifier_dl/roberta_for_multiple_choice.py create mode 100644 python/test/annotator/classifier_dl/roberta_for_multiple_choice_test.py create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RoBertaForMultipleChoice.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RobertaForMultipleChoiceTestSpec.scala diff --git a/python/sparknlp/annotator/classifier_dl/__init__.py b/python/sparknlp/annotator/classifier_dl/__init__.py index 2b5e30fc3ff359..09ba4b1fd62c40 100644 --- a/python/sparknlp/annotator/classifier_dl/__init__.py +++ b/python/sparknlp/annotator/classifier_dl/__init__.py @@ -55,3 +55,4 @@ from sparknlp.annotator.classifier_dl.albert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.camembert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.bert_for_multiple_choice import * +from sparknlp.annotator.classifier_dl.roberta_for_multiple_choice import * \ No newline at end of file diff --git a/python/sparknlp/annotator/classifier_dl/roberta_for_multiple_choice.py b/python/sparknlp/annotator/classifier_dl/roberta_for_multiple_choice.py new file mode 100644 index 00000000000000..7ad4df59f08e0d --- /dev/null +++ b/python/sparknlp/annotator/classifier_dl/roberta_for_multiple_choice.py @@ -0,0 +1,161 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class RoBertaForMultipleChoice(AnnotatorModel, + HasCaseSensitiveProperties, + HasBatchedAnnotate, + HasEngine, + HasMaxSentenceLengthLimit): + """RoBertaForMultipleChoice can load RoBERTa Models with a multiple choice classification head on top + (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion + object: + + >>> spanClassifier = RoBertaForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") + + The default model is ``"roberta_base_uncased_multiple_choice"``, if no name is + provided. + + For available pretrained models please see the `Models Hub + `__. + + To see which models are compatible and how to import them see + `Import Transformers into Spark NLP ๐Ÿš€ + `_. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``DOCUMENT, DOCUMENT`` ``CHUNK`` + ====================== ====================== + + Parameters + ---------- + batchSize + Batch size. Large values allows faster processing but requires more + memory, by default 8 + caseSensitive + Whether to ignore case in tokens for embeddings matching, by default + False + maxSentenceLength + Max sentence length to process, by default 512 + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> documentAssembler = MultiDocumentAssembler() \\ + ... .setInputCols(["question", "context"]) \\ + ... .setOutputCols(["document_question", "document_context"]) + >>> questionAnswering = RoBertaForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") \\ + ... .setCaseSensitive(False) + >>> pipeline = Pipeline().setStages([ + ... documentAssembler, + ... questionAnswering + ... ]) + >>> data = spark.createDataFrame([["The Eiffel Tower is located in which country??", "Germany, France, Italy"]]).toDF("question", "context") + >>> result = pipeline.fit(data).transform(data) + >>> result.select("answer.result").show(truncate=False) + +--------------------+ + |result | + +--------------------+ + |[France] | + +--------------------+ + """ + name = "RobertaForMultipleChoice" + + inputAnnotatorTypes = [AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT] + + outputAnnotatorType = AnnotatorType.CHUNK + + choicesDelimiter = Param(Params._dummy(), + "choicesDelimiter", + "Delimiter character use to split the choices", + TypeConverters.toString) + + def setChoicesDelimiter(self, value): + """Sets delimiter character use to split the choices + + Parameters + ---------- + value : string + Delimiter character use to split the choices + """ + return self._set(caseSensitive=value) + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.classifier.dl.RobertaForMultipleChoice", + java_model=None): + super(RoBertaForMultipleChoice, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=4, + maxSentenceLength=512, + caseSensitive=False, + choicesDelimiter = "," + ) + + @staticmethod + def loadSavedModel(folder, spark_session): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + RobertaForQuestionAnswering + The restored model + """ + from sparknlp.internal import _RoBertaMultipleChoiceLoader + jModel = _RoBertaMultipleChoiceLoader(folder, spark_session._jsparkSession)._java_obj + return RoBertaForMultipleChoice(java_model=jModel) + + @staticmethod + def pretrained(name="Roberta_base_uncased_multiple_choice", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "Roberta_base_uncased_multiple_choice" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + RoBertaForMultipleChoice + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(RoBertaForMultipleChoice, name, lang, remote_loc) \ No newline at end of file diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..9528d44672505c 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -409,6 +409,15 @@ def __init__(self, path, jspark): ) +class _RoBertaMultipleChoiceLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark): + super(_RoBertaMultipleChoiceLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.classifier.dl.RoBertaForMultipleChoice.loadSavedModel", + path, + jspark, + ) + + class _StarCoderLoader(ExtendedJavaWrapper): def __init__(self, path, jspark, use_openvino=False): super(_StarCoderLoader, self).__init__( diff --git a/python/test/annotator/classifier_dl/roberta_for_multiple_choice_test.py b/python/test/annotator/classifier_dl/roberta_for_multiple_choice_test.py new file mode 100644 index 00000000000000..b93c4b723d8e55 --- /dev/null +++ b/python/test/annotator/classifier_dl/roberta_for_multiple_choice_test.py @@ -0,0 +1,77 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import pytest + +from sparknlp.annotator import * +from sparknlp.base import * +from test.util import SparkContextForTest + + +class RobertaForMultipleChoiceTestSetup(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + self.question = "The Eiffel Tower is located in which country?" + self.choices = "Germany, France, Italy" + + self.spark = SparkContextForTest.spark + empty_df = self.spark.createDataFrame([[""]]).toDF("text") + + document_assembler = MultiDocumentAssembler() \ + .setInputCols(["question", "context"]) \ + .setOutputCols(["document_question", "document_context"]) + + model_path = "/media/danilo/Data/Danilo/JSL/models/transformers/spark-nlp/onnx/roberta_multiple_choice" + roberta_for_multiple_choice = RoBertaForMultipleChoice.load(model_path) \ + .setInputCols(["document_question", "document_context"]) \ + .setOutputCol("answer") + + pipeline = Pipeline(stages=[document_assembler, roberta_for_multiple_choice]) + + self.pipeline_model = pipeline.fit(empty_df) + + +@pytest.mark.slow +class RobertaForMultipleChoiceTest(RobertaForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + self.data = self.spark.createDataFrame([[self.question, self.choices]]).toDF("question","context") + self.data.show(truncate=False) + + def test_run(self): + result_df = self.pipeline_model.transform(self.data) + result_df.show(truncate=False) + for row in result_df.collect(): + self.assertTrue(row["answer"][0].result != "") + + +@pytest.mark.slow +class LightRobertaForMultipleChoiceTest(RobertaForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.pipeline_model) + annotations_result = light_pipeline.fullAnnotate(self.question,self.choices) + print(annotations_result) + for result in annotations_result: + self.assertTrue(result["answer"][0].result != "") + + result = light_pipeline.annotate(self.question,self.choices) + print(result) + self.assertTrue(result["answer"] != "") diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala b/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala index e15387f20c7410..88b6291223ad78 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala @@ -486,6 +486,91 @@ private[johnsnowlabs] class RoBertaClassification( (startScores, endScores) } + override def tagSpanMultipleChoice(batch: Seq[Array[Int]]): Array[Float] = { + val logits = detectedEngine match { + case ONNX.name => computeLogitsMultipleChoiceWithOnnx(batch) + case Openvino.name => computeLogitsMultipleChoiceWithOv(batch) + } + + calculateSoftmax(logits) + } + + private def computeLogitsMultipleChoiceWithOnnx(batch: Seq[Array[Int]]): Array[Float] = { + val sequenceLength = batch.head.length + val inputIds = Array(batch.map(x => x.map(_.toLong)).toArray) + val attentionMask = Array( + batch.map(sentence => sentence.map(x => if (x == 0L) 0L else 1L)).toArray) + + val (ortSession, ortEnv) = onnxWrapper.get.getSession(onnxSessionOptions) + val tokenTensors = OnnxTensor.createTensor(ortEnv, inputIds) + val maskTensors = OnnxTensor.createTensor(ortEnv, attentionMask) + + val inputs = + Map( + "input_ids" -> tokenTensors, + "attention_mask" -> maskTensors).asJava + + try { + val output = ortSession.run(inputs) + try { + + val logits = output + .get("logits") + .get() + .asInstanceOf[OnnxTensor] + .getFloatBuffer + .array() + + tokenTensors.close() + maskTensors.close() + + logits + } finally if (output != null) output.close() + } catch { + case e: Exception => + // Log the exception as a warning + println("Exception in computeLogitsMultipleChoiceWithOnnx: ", e) + // Rethrow the exception to propagate it further + throw e + } + } + + private def computeLogitsMultipleChoiceWithOv(batch: Seq[Array[Int]]): Array[Float] = { + val (numChoices, sequenceLength) = (batch.length, batch.head.length) + // batch_size, num_choices, sequence_length + val shape = Some(Array(1, numChoices, sequenceLength)) + val (tokenTensors, maskTensors, _) = + PrepareEmbeddings.prepareOvLongBatchTensorsWithSegment( + batch, + sequenceLength, + numChoices, + sentencePadTokenId, + shape) + + val compiledModel = openvinoWrapper.get.getCompiledModel() + val inferRequest = compiledModel.create_infer_request() + inferRequest.set_tensor("input_ids", tokenTensors) + inferRequest.set_tensor("attention_mask", maskTensors) + + inferRequest.infer() + + try { + try { + val logits = inferRequest + .get_output_tensor() + .data() + + logits + } + } catch { + case e: Exception => + // Log the exception as a warning + logger.warn("Exception in computeLogitsMultipleChoiceWithOv", e) + // Rethrow the exception to propagate it further + throw e + } + } + private def computeLogitsWithTF( batch: Seq[Array[Int]], maxSentenceLength: Int): (Array[Float], Array[Float]) = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RoBertaForMultipleChoice.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RoBertaForMultipleChoice.scala new file mode 100644 index 00000000000000..92f129fa15beaf --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RoBertaForMultipleChoice.scala @@ -0,0 +1,308 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.ml.ai.RoBertaClassification +import com.johnsnowlabs.ml.onnx.{OnnxWrapper, ReadOnnxModel, WriteOnnxModel} +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.tensorflow.TensorflowWrapper +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadTextAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp.serialization.MapFeature +import com.johnsnowlabs.nlp._ +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntParam, Param} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +class RoBertaForMultipleChoice(override val uid: String) + extends AnnotatorModel[RoBertaForMultipleChoice] + with HasBatchedAnnotate[RoBertaForMultipleChoice] + with WriteOnnxModel + with WriteOpenvinoModel + with HasCaseSensitiveProperties + with HasEngine { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("RoBertaForMultipleChoice")) + + /** Input Annotator Types: DOCUMENT, DOCUMENT + * + * @group anno + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = + Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) + + /** Output Annotator Types: CHUNK + * + * @group anno + */ + override val outputAnnotatorType: AnnotatorType = AnnotatorType.CHUNK + + def sentenceStartTokenId: Int = { + $$(vocabulary)("") + } + + def sentenceEndTokenId: Int = { + $$(vocabulary)("") + } + + def padTokenId: Int = { + $$(vocabulary)("") + } + + /** Vocabulary used to encode the words to ids with WordPieceEncoder + * + * @group param + */ + val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() + + /** @group setParam */ + def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value) + + /** Holding merges.txt coming from RoBERTa model + * + * @group param + */ + val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected() + + /** @group setParam */ + def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value) + + /** Max sentence length to process (Default: `128`) + * + * @group param + */ + val maxSentenceLength = + new IntParam(this, "maxSentenceLength", "Max sentence length to process") + + /** @group setParam */ + def setMaxSentenceLength(value: Int): this.type = { + require( + value <= 512, + "RoBERTa models do not support sequences longer than 512 because of trainable positional embeddings.") + require(value >= 1, "The maxSentenceLength must be at least 1") + set(maxSentenceLength, value) + this + } + + private var _model: Option[Broadcast[RoBertaClassification]] = None + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + tensorflowWrapper: Option[TensorflowWrapper], + onnxWrapper: Option[OnnxWrapper], + openvinoWrapper: Option[OpenvinoWrapper]): RoBertaForMultipleChoice = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new RoBertaClassification( + tensorflowWrapper, + onnxWrapper, + openvinoWrapper, + sentenceStartTokenId, + sentenceEndTokenId, + padTokenId, + tags = Map.empty[String, Int], + merges = $$(merges), + vocabulary = $$(vocabulary)))) + } + + this + } + + /** @group getParam */ + def getModelIfNotSet: RoBertaClassification = _model.get.value + + /** Whether to lowercase tokens or not (Default: `true`). + * + * @group setParam + */ + override def setCaseSensitive(value: Boolean): this.type = set(this.caseSensitive, value) + + val choicesDelimiter = + new Param[String](this, "choicesDelimiter", "Delimiter character use to split the choices") + + def setChoicesDelimiter(value: String): this.type = set(choicesDelimiter, value) + + setDefault( + batchSize -> 8, + maxSentenceLength -> 128, + caseSensitive -> true, + choicesDelimiter -> ",") + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + * + * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences + * that belong to the same original row !! (challenging) + */ + override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { + batchedAnnotations.map(annotations => { + if (annotations.nonEmpty) { + getModelIfNotSet.predictSpanMultipleChoice( + annotations, + $(choicesDelimiter), + $(maxSentenceLength), + $(caseSensitive)) + } else { + Seq.empty[Annotation] + } + }) + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + val suffix = "_roberta_classification" + + getEngine match { + case ONNX.name => + writeOnnxModel( + path, + spark, + getModelIfNotSet.onnxWrapper.get, + suffix, + RoBertaForMultipleChoice.onnxFile) + + case Openvino.name => + writeOpenvinoModel( + path, + spark, + getModelIfNotSet.openvinoWrapper.get, + "openvino_model.xml", + RoBertaForMultipleChoice.openvinoFile) + } + + } + +} + +trait ReadablePretrainedRoBertaForMCModel + extends ParamsAndFeaturesReadable[RoBertaForMultipleChoice] + with HasPretrained[RoBertaForMultipleChoice] { + override val defaultModelName: Some[String] = Some("roberta_base_qa_squad2") + + /** Java compliant-overrides */ + override def pretrained(): RoBertaForMultipleChoice = super.pretrained() + + override def pretrained(name: String): RoBertaForMultipleChoice = super.pretrained(name) + + override def pretrained(name: String, lang: String): RoBertaForMultipleChoice = + super.pretrained(name, lang) + + override def pretrained( + name: String, + lang: String, + remoteLoc: String): RoBertaForMultipleChoice = + super.pretrained(name, lang, remoteLoc) +} + +trait ReadRoBertaForMultipleChoiceDLModel extends ReadOnnxModel with ReadOpenvinoModel { + this: ParamsAndFeaturesReadable[RoBertaForMultipleChoice] => + + override val onnxFile: String = "roberta_mc_classification_onnx" + override val openvinoFile: String = "roberta_mc_classification_openvino" + + def readModel(instance: RoBertaForMultipleChoice, path: String, spark: SparkSession): Unit = { + + instance.getEngine match { + case ONNX.name => + val onnxWrapper = + readOnnxModel( + path, + spark, + "roberta_mc_classification_onnx", + zipped = true, + useBundle = false, + None) + instance.setModelIfNotSet(spark, None, Some(onnxWrapper), None) + + case Openvino.name => + val openvinoWrapper = readOpenvinoModel(path, spark, "roberta_mc_classification_openvino") + instance.setModelIfNotSet(spark, None, None, Some(openvinoWrapper)) + + } + + } + + addReader(readModel) + + def loadSavedModel(modelPath: String, spark: SparkSession): RoBertaForMultipleChoice = { + + val (localModelPath, detectedEngine) = modelSanityCheck(modelPath) + + val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap + + val bytePairs = loadTextAsset(localModelPath, "merges.txt") + .map(_.split(" ")) + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + + /*Universal parameters for all engines*/ + val annotatorModel = new RoBertaForMultipleChoice() + .setVocabulary(vocabs) + .setMerges(bytePairs) + + annotatorModel.set(annotatorModel.engine, detectedEngine) + + detectedEngine match { + case ONNX.name => + val onnxWrapper = + OnnxWrapper.read(spark, localModelPath, zipped = false, useBundle = true) + annotatorModel + .setModelIfNotSet(spark, None, Some(onnxWrapper), None) + + case Openvino.name => + val ovWrapper: OpenvinoWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine) + annotatorModel + .setModelIfNotSet(spark, None, None, Some(ovWrapper)) + + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } +} + +/** This is the companion object of [[RoBertaForMultipleChoice]]. Please refer to that class for + * the documentation. + */ +object RoBertaForMultipleChoice + extends ReadablePretrainedRoBertaForMCModel + with ReadRoBertaForMultipleChoiceDLModel diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RobertaForMultipleChoiceTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RobertaForMultipleChoiceTestSpec.scala new file mode 100644 index 00000000000000..4dec950c81bc84 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RobertaForMultipleChoiceTestSpec.scala @@ -0,0 +1,83 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, MultiDocumentAssembler} +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.tags.SlowTest +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec + +class RobertaForMultipleChoiceTestSpec extends AnyFlatSpec with SparkSessionTest { + + import spark.implicits._ + + lazy val pipelineModel = getRoBertaForMultipleChoicePipelineModel + + val testDataframe = + Seq(("The Eiffel Tower is located in which country?", "Germany, France, Italy")) + .toDF("question", "context") + + "RobertaForMultipleChoice" should "answer a multiple choice question" taggedAs SlowTest in { + val resultDf = pipelineModel.transform(testDataframe) + resultDf.show(truncate = false) + + val result = AssertAnnotations.getActualResult(resultDf, "answer") + result.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + } + + it should "work with light pipeline fullAnnotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultFullAnnotate = lightPipeline.fullAnnotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultFullAnnotate") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + assert(answerAnnotation.result.nonEmpty) + } + + it should "work with light pipeline annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultAnnotate = lightPipeline.annotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.nonEmpty) + } + + private def getRoBertaForMultipleChoicePipelineModel = { + val documentAssembler = new MultiDocumentAssembler() + .setInputCols("question", "context") + .setOutputCols("document_question", "document_context") + + val bertForMultipleChoice = RoBertaForMultipleChoice + .pretrained() + .setInputCols("document_question", "document_context") + .setOutputCol("answer") + + val pipeline = new Pipeline().setStages(Array(documentAssembler, bertForMultipleChoice)) + + pipeline.fit(emptyDataSet) + } + +} From 4d2c06e142c88e7bc44eff39736ec6a794cafe1b Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Fri, 3 Jan 2025 18:29:17 -0500 Subject: [PATCH 006/108] [SPARKNLP-1107] Adding example notebooks for RobertaForMultipleChoice --- ...n_Spark_NLP_RoBERTaForMultipleChoice.ipynb | 3137 ++++++++++++++++ ...n_Spark_NLP_RoBERTaForMultipleChoice.ipynb | 3231 +++++++++++++++++ 2 files changed, 6368 insertions(+) create mode 100644 examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb new file mode 100644 index 00000000000000..9cf14051be447e --- /dev/null +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb @@ -0,0 +1,3137 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "PAsu8UVGoLVf" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb)\n", + "\n", + "## Import ONNX RoBERTaForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- ONNX support was introduced in `Spark NLP 5.0.0`, enabling high performance inference for models.\n", + "- `RoBertaForMultipleChoice` is only available since in `Spark NLP 5.6.0` and after. So please make sure you have upgraded to the latest Spark NLP release\n", + "- You can import BERT models trained/fine-tuned for question answering via `RoBertaForMultipleChoice` or `TFRobertaForMultipleChoice`. These models are usually under `Multiple Choice` category and have `bert` in their labels\n", + "- Reference: [RoBertaForMultipleChoice](https://huggingface.co/docs/transformers/en/model_doc/roberta#transformers.RobertaForMultipleChoice)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OzijcdtQpOx9" + }, + "source": [ + "## Export and Save HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MlgoClMXpSg4" + }, + "source": [ + "- Let's install `transformers` package with the `onnx` extension and it's dependencies. You don't need `onnx` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cJWbob-kHICU", + "outputId": "a32c5445-116e-4724-cc0f-31179dd52df9" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m424.1/424.1 kB\u001b[0m \u001b[31m8.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m98.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m212.7/212.7 kB\u001b[0m \u001b[31m15.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m480.6/480.6 kB\u001b[0m \u001b[31m37.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m84.5/84.5 kB\u001b[0m \u001b[31m6.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m51.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m455.8/455.8 kB\u001b[0m \u001b[31m32.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m9.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m179.3/179.3 kB\u001b[0m \u001b[31m14.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m6.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m13.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m89.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.5/55.5 kB\u001b[0m \u001b[31m4.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m15.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q --upgrade transformers[onnx] optimum" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XtewR2xdOa5s" + }, + "source": [ + "- HuggingFace has an extension called Optimum which offers specialized model inference, including ONNX. We can use this to import and export ONNX models with `from_pretrained` and `save_pretrained`.\n", + "- We'll use the treained model above as an example and load it as a `ORTModelForMultipleChoice`, representing an ONNX model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 345, + "referenced_widgets": [ + "b81b575abe25438882c4feefa49c548c", + "fc0e47ce50d242949480bdb27cb03fb2", + "6af58f364683458ca7a126eac79f8e4e", + "f2ff9dc3293e435ab8d8711d4e342502", + "d7e0fbb281654ed5aa19722d67a3e3e2", + "32eed337efda46de976f35e70416f1f7", + "f2150b9b3ebd4823ac3dcb5667e2a805", + "2e05bd110c75401b82a4840753483aab", + "ed5a2963cb1246018d5a86c8fdd184e4", + "dd079c8f52b24568a504800fe0f5d173", + "da38ec87d6d047e2b89ab6e0dff906b0", + "449e0c145e43403d855dfea64dc21088", + "a0a8c669cdec4bb3ab9ee72a17d7aa04", + "1aa8f9eca530473a817456f381d27428", + "4d2302d612e044cd82471c524f088385", + "2b887e873088471db583b3e3fe1b434c", + "00d30d42d577493595bca3c2212b6c0b", + "c826b3ae19644a78800de8351d5eb273", + "4134a81ea32b45cd8fb156c7ff9ce81a", + "5e6e38a8b70e4b0b99645e6a6ec70d1e", + "c20ed60171494bd282f825390a3c22f4", + "69f4a07a5a2f445ea17d22a2d194265a", + "b5e3e91432ec4c618ffdaff327a7741e", + "25b2eeed37024a3ea5bc15fedccd2b6c", + "3e3ec4ef29ea429f87c08ad9a1a6dae4", + "5d6dbd8e5560416b82e68b88e4c98228", + "a274e9d62bf640069a009b106bbe6285", + "b5366c8ca6b54a43a7ebd3f1f5ed4314", + "f326f3da6db044ba8eb30fbfdebfcf1a", + "af141091f1954d61821d591decec6fb7", + "3c368fe097024cbfa4831776bf76decd", + "1bf90b3c45ba4da2937474099359145c", + "adbd551d077642c4940deb5a8bdeea1e", + "635310df3a1d446cba12efcc05ebf193", + "76d9c74da20446589200261c39fe2d3c", + "d5a03993410642f3ae2e6f93fb9ac2aa", + "631be0307e8346f9932603b83f930ace", + "5a1d396c945a48f68f64659ba1047c90", + "90d9046b9ae6424dafb432b35fa58c61", + "873e1debde344c3196b8febb043fce64", + "dbfb4dba9ce148b8afbcad9f91e2e313", + "e4bb3112696944ccbe6e4478896fa37e", + "738d08c4f4f04513ac68689a392f9d83", + "ff4d9baed90c4d088b0690206b79e9cf", + "0588f69af8254964bb99302965d4d1ee", + "e2854b15d44c42ca97e80e2ee5d85277", + "e34b0ebc070d4e2f9b5ec1f7888aaf48", + "07d3f10c53044e44aa98658511dfb833", + "39e33232acef40f29cbe5f1d6356812f", + "e44edfab662947f58b785df9ee6cc3f8", + "07688d751836418d8fec6ab31b44d09e", + "fbcfa2c5c6104b2b8dac3d8cbaae6582", + "dec95a9617d8450d990f275785b7050a", + "bd224ffc05544aaea000116468606db8", + "45fe0f6b87b941b59bd308dca280e2a3", + "e924f9c94be24a0a9a41a6c095403839", + "d5cda9d5599747f5befbff3f680621f7", + "5f13284caf47472ab58a33184227b343", + "284ea88ad9bf471c995e637d78c0415c", + "4534f75337d2443fbf72adeae69c7111", + "3b8ef54f86a04e4e8ff124183ed45c16", + "1c9db5477dd442f2b33f4c0b847ccbb3", + "202ea48a63274ce9b76efa7c6cc2f2a8", + "7f43484ba57d43f395f9366af29880ac", + "c13de8f46773462aad80071ff4bc8116", + "afa26d6167b34e8dae01e122dd9f72ed", + "2e1b3919d89c47129fafd48c11baba71", + "b0cebdeef06c4821922ac6a76280b49d", + "76d223708bb744758176a2c44163aa81", + "b18f3bc006ce4e8684e8b34a65adfd28", + "16e4af9b9428496484eb30b6a3bfe6c8", + "e5e4fcd42f524c85b2e75a4a4c9ac316", + "645f1cf979b7447fb0cd836589797f10", + "3bd998b830554952806a8aee69c9441f", + "7afb04b69be64f21ad5d4a2e6ee02baf", + "0d3326cfa7a245c191d8cc6283e06c88", + "13afc16acf4b4ac3a87b13050daadf5a" + ] + }, + "id": "Id33annImYM8", + "outputId": "b4c0f6fa-2c09-40d7-a235-37df49d7edcd" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b81b575abe25438882c4feefa49c548c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/728 [00:00 0, chunk -> 0, score -> 0.6444231}, []}]|\n", + "|[{chunk, 0, 6, France, {sentence -> 0, chunk -> 0, score -> 0.37822443}, []}] |\n", + "|[{chunk, 0, 8, Elephant, {sentence -> 0, chunk -> 0, score -> 0.3064313}, []}] |\n", + "|[{chunk, 0, 3, 90ยฐC, {sentence -> 0, chunk -> 0, score -> 0.4218395}, []}] |\n", + "|[{chunk, 0, 5, Venus, {sentence -> 0, chunk -> 0, score -> 0.47263265}, []}] |\n", + "|[{chunk, 0, 7, English, {sentence -> 0, chunk -> 0, score -> 0.38427573}, []}] |\n", + "|[{chunk, 0, 10, The Romans, {sentence -> 0, chunk -> 0, score -> 0.310014}, []}] |\n", + "|[{chunk, 0, 5, Ozone, {sentence -> 0, chunk -> 0, score -> 0.5966889}, []}] |\n", + "|[{chunk, 0, 3, Asia, {sentence -> 0, chunk -> 0, score -> 0.4309402}, []}] |\n", + "|[{chunk, 0, 15, Vincent van Gogh, {sentence -> 0, chunk -> 0, score -> 0.38662443}, []}] |\n", + "+----------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "roberta_for_multiple_choice = RoBertaForMultipleChoice() \\\n", + " .load(\"./{}_spark_nlp_onnx\".format(MODEL_NAME)) \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, roberta_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "A100", + "machine_shape": "hm", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "00d30d42d577493595bca3c2212b6c0b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "0588f69af8254964bb99302965d4d1ee": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e2854b15d44c42ca97e80e2ee5d85277", + "IPY_MODEL_e34b0ebc070d4e2f9b5ec1f7888aaf48", + "IPY_MODEL_07d3f10c53044e44aa98658511dfb833" + ], + "layout": "IPY_MODEL_39e33232acef40f29cbe5f1d6356812f" + } + }, + "07688d751836418d8fec6ab31b44d09e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "07d3f10c53044e44aa98658511dfb833": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_bd224ffc05544aaea000116468606db8", + "placeholder": "โ€‹", + "style": "IPY_MODEL_45fe0f6b87b941b59bd308dca280e2a3", + "value": "โ€‡1.15M/1.15Mโ€‡[00:00<00:00,โ€‡1.94MB/s]" + } + }, + "0d3326cfa7a245c191d8cc6283e06c88": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "13afc16acf4b4ac3a87b13050daadf5a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "16e4af9b9428496484eb30b6a3bfe6c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "1aa8f9eca530473a817456f381d27428": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_4134a81ea32b45cd8fb156c7ff9ce81a", + "max": 503987181, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_5e6e38a8b70e4b0b99645e6a6ec70d1e", + "value": 503987181 + } + }, + "1bf90b3c45ba4da2937474099359145c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "1c9db5477dd442f2b33f4c0b847ccbb3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "202ea48a63274ce9b76efa7c6cc2f2a8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "25b2eeed37024a3ea5bc15fedccd2b6c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_b5366c8ca6b54a43a7ebd3f1f5ed4314", + "placeholder": "โ€‹", + "style": "IPY_MODEL_f326f3da6db044ba8eb30fbfdebfcf1a", + "value": "tokenizer_config.json:โ€‡100%" + } + }, + "284ea88ad9bf471c995e637d78c0415c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_c13de8f46773462aad80071ff4bc8116", + "placeholder": "โ€‹", + "style": "IPY_MODEL_afa26d6167b34e8dae01e122dd9f72ed", + "value": "โ€‡3.54M/3.54Mโ€‡[00:00<00:00,โ€‡57.9MB/s]" + } + }, + "2b887e873088471db583b3e3fe1b434c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2e05bd110c75401b82a4840753483aab": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2e1b3919d89c47129fafd48c11baba71": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_b0cebdeef06c4821922ac6a76280b49d", + "IPY_MODEL_76d223708bb744758176a2c44163aa81", + "IPY_MODEL_b18f3bc006ce4e8684e8b34a65adfd28" + ], + "layout": "IPY_MODEL_16e4af9b9428496484eb30b6a3bfe6c8" + } + }, + "32eed337efda46de976f35e70416f1f7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "39e33232acef40f29cbe5f1d6356812f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3b8ef54f86a04e4e8ff124183ed45c16": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3bd998b830554952806a8aee69c9441f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3c368fe097024cbfa4831776bf76decd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "3e3ec4ef29ea429f87c08ad9a1a6dae4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_af141091f1954d61821d591decec6fb7", + "max": 1385, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_3c368fe097024cbfa4831776bf76decd", + "value": 1385 + } + }, + "4134a81ea32b45cd8fb156c7ff9ce81a": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "449e0c145e43403d855dfea64dc21088": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_a0a8c669cdec4bb3ab9ee72a17d7aa04", + "IPY_MODEL_1aa8f9eca530473a817456f381d27428", + "IPY_MODEL_4d2302d612e044cd82471c524f088385" + ], + "layout": "IPY_MODEL_2b887e873088471db583b3e3fe1b434c" + } + }, + "4534f75337d2443fbf72adeae69c7111": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "45fe0f6b87b941b59bd308dca280e2a3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "4d2302d612e044cd82471c524f088385": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_c20ed60171494bd282f825390a3c22f4", + "placeholder": "โ€‹", + "style": "IPY_MODEL_69f4a07a5a2f445ea17d22a2d194265a", + "value": "โ€‡504M/504Mโ€‡[00:02<00:00,โ€‡230MB/s]" + } + }, + "5a1d396c945a48f68f64659ba1047c90": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5d6dbd8e5560416b82e68b88e4c98228": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_1bf90b3c45ba4da2937474099359145c", + "placeholder": "โ€‹", + "style": "IPY_MODEL_adbd551d077642c4940deb5a8bdeea1e", + "value": "โ€‡1.39k/1.39kโ€‡[00:00<00:00,โ€‡92.0kB/s]" + } + }, + "5e6e38a8b70e4b0b99645e6a6ec70d1e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "5f13284caf47472ab58a33184227b343": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_202ea48a63274ce9b76efa7c6cc2f2a8", + "max": 3537507, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_7f43484ba57d43f395f9366af29880ac", + "value": 3537507 + } + }, + "631be0307e8346f9932603b83f930ace": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_738d08c4f4f04513ac68689a392f9d83", + "placeholder": "โ€‹", + "style": "IPY_MODEL_ff4d9baed90c4d088b0690206b79e9cf", + "value": "โ€‡1.50M/1.50Mโ€‡[00:00<00:00,โ€‡18.5MB/s]" + } + }, + "635310df3a1d446cba12efcc05ebf193": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_76d9c74da20446589200261c39fe2d3c", + "IPY_MODEL_d5a03993410642f3ae2e6f93fb9ac2aa", + "IPY_MODEL_631be0307e8346f9932603b83f930ace" + ], + "layout": "IPY_MODEL_5a1d396c945a48f68f64659ba1047c90" + } + }, + "645f1cf979b7447fb0cd836589797f10": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "69f4a07a5a2f445ea17d22a2d194265a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "6af58f364683458ca7a126eac79f8e4e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_2e05bd110c75401b82a4840753483aab", + "max": 728, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_ed5a2963cb1246018d5a86c8fdd184e4", + "value": 728 + } + }, + "738d08c4f4f04513ac68689a392f9d83": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "76d223708bb744758176a2c44163aa81": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_3bd998b830554952806a8aee69c9441f", + "max": 957, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_7afb04b69be64f21ad5d4a2e6ee02baf", + "value": 957 + } + }, + "76d9c74da20446589200261c39fe2d3c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_90d9046b9ae6424dafb432b35fa58c61", + "placeholder": "โ€‹", + "style": "IPY_MODEL_873e1debde344c3196b8febb043fce64", + "value": "vocab.json:โ€‡100%" + } + }, + "7afb04b69be64f21ad5d4a2e6ee02baf": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7f43484ba57d43f395f9366af29880ac": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "873e1debde344c3196b8febb043fce64": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "90d9046b9ae6424dafb432b35fa58c61": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a0a8c669cdec4bb3ab9ee72a17d7aa04": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_00d30d42d577493595bca3c2212b6c0b", + "placeholder": "โ€‹", + "style": "IPY_MODEL_c826b3ae19644a78800de8351d5eb273", + "value": "pytorch_model.bin:โ€‡100%" + } + }, + "a274e9d62bf640069a009b106bbe6285": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "adbd551d077642c4940deb5a8bdeea1e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "af141091f1954d61821d591decec6fb7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "afa26d6167b34e8dae01e122dd9f72ed": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "b0cebdeef06c4821922ac6a76280b49d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_e5e4fcd42f524c85b2e75a4a4c9ac316", + "placeholder": "โ€‹", + "style": "IPY_MODEL_645f1cf979b7447fb0cd836589797f10", + "value": "special_tokens_map.json:โ€‡100%" + } + }, + "b18f3bc006ce4e8684e8b34a65adfd28": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_0d3326cfa7a245c191d8cc6283e06c88", + "placeholder": "โ€‹", + "style": "IPY_MODEL_13afc16acf4b4ac3a87b13050daadf5a", + "value": "โ€‡957/957โ€‡[00:00<00:00,โ€‡60.0kB/s]" + } + }, + "b5366c8ca6b54a43a7ebd3f1f5ed4314": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b5e3e91432ec4c618ffdaff327a7741e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_25b2eeed37024a3ea5bc15fedccd2b6c", + "IPY_MODEL_3e3ec4ef29ea429f87c08ad9a1a6dae4", + "IPY_MODEL_5d6dbd8e5560416b82e68b88e4c98228" + ], + "layout": "IPY_MODEL_a274e9d62bf640069a009b106bbe6285" + } + }, + "b81b575abe25438882c4feefa49c548c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_fc0e47ce50d242949480bdb27cb03fb2", + "IPY_MODEL_6af58f364683458ca7a126eac79f8e4e", + "IPY_MODEL_f2ff9dc3293e435ab8d8711d4e342502" + ], + "layout": "IPY_MODEL_d7e0fbb281654ed5aa19722d67a3e3e2" + } + }, + "bd224ffc05544aaea000116468606db8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c13de8f46773462aad80071ff4bc8116": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c20ed60171494bd282f825390a3c22f4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c826b3ae19644a78800de8351d5eb273": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "d5a03993410642f3ae2e6f93fb9ac2aa": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dbfb4dba9ce148b8afbcad9f91e2e313", + "max": 1503982, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_e4bb3112696944ccbe6e4478896fa37e", + "value": 1503982 + } + }, + "d5cda9d5599747f5befbff3f680621f7": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_3b8ef54f86a04e4e8ff124183ed45c16", + "placeholder": "โ€‹", + "style": "IPY_MODEL_1c9db5477dd442f2b33f4c0b847ccbb3", + "value": "tokenizer.json:โ€‡100%" + } + }, + "d7e0fbb281654ed5aa19722d67a3e3e2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "da38ec87d6d047e2b89ab6e0dff906b0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "dbfb4dba9ce148b8afbcad9f91e2e313": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dd079c8f52b24568a504800fe0f5d173": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dec95a9617d8450d990f275785b7050a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "e2854b15d44c42ca97e80e2ee5d85277": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_e44edfab662947f58b785df9ee6cc3f8", + "placeholder": "โ€‹", + "style": "IPY_MODEL_07688d751836418d8fec6ab31b44d09e", + "value": "merges.txt:โ€‡100%" + } + }, + "e34b0ebc070d4e2f9b5ec1f7888aaf48": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_fbcfa2c5c6104b2b8dac3d8cbaae6582", + "max": 1150157, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_dec95a9617d8450d990f275785b7050a", + "value": 1150157 + } + }, + "e44edfab662947f58b785df9ee6cc3f8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e4bb3112696944ccbe6e4478896fa37e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "e5e4fcd42f524c85b2e75a4a4c9ac316": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e924f9c94be24a0a9a41a6c095403839": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_d5cda9d5599747f5befbff3f680621f7", + "IPY_MODEL_5f13284caf47472ab58a33184227b343", + "IPY_MODEL_284ea88ad9bf471c995e637d78c0415c" + ], + "layout": "IPY_MODEL_4534f75337d2443fbf72adeae69c7111" + } + }, + "ed5a2963cb1246018d5a86c8fdd184e4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "f2150b9b3ebd4823ac3dcb5667e2a805": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "f2ff9dc3293e435ab8d8711d4e342502": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dd079c8f52b24568a504800fe0f5d173", + "placeholder": "โ€‹", + "style": "IPY_MODEL_da38ec87d6d047e2b89ab6e0dff906b0", + "value": "โ€‡728/728โ€‡[00:00<00:00,โ€‡62.0kB/s]" + } + }, + "f326f3da6db044ba8eb30fbfdebfcf1a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "fbcfa2c5c6104b2b8dac3d8cbaae6582": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fc0e47ce50d242949480bdb27cb03fb2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_32eed337efda46de976f35e70416f1f7", + "placeholder": "โ€‹", + "style": "IPY_MODEL_f2150b9b3ebd4823ac3dcb5667e2a805", + "value": "config.json:โ€‡100%" + } + }, + "ff4d9baed90c4d088b0690206b79e9cf": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb new file mode 100644 index 00000000000000..88f94cd03e5629 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb @@ -0,0 +1,3231 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "_V5XcDCnVgSi" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_RoBERTaForMultipleChoice.ipynb)\n", + "\n", + "# Import OpenVINO RoBertaForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and exporting RoBertaForMultipleChoice models from HuggingFace for use in Spark NLP, leveraging the various tools provided in the [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html) ecosystem.\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance inference for models. Please make sure you have upgraded to the latest Spark NLP release.\n", + "- You can import models for RoBertaForMultipleChoice from RoBertaForMultipleChoice and they have to be in `Multiple Choice` category." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aghasVppVgSk" + }, + "source": [ + "## 1. Export and Save the HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "be4HsTDMVgSk" + }, + "source": [ + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future releases, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vI7uz_6hVgSl" + }, + "source": [ + "[Optimum Intel](https://github.com/huggingface/optimum-intel?tab=readme-ov-file#openvino) is the interface between the Transformers library and the various model optimization and acceleration tools provided by Intel. HuggingFace models loaded with optimum-intel are automatically optimized for OpenVINO, while being compatible with the Transformers API.\n", + "- Normally, to load a HuggingFace model directly for inference/export, just replace the `AutoModelForXxx` class with the corresponding `OVModelForXxx` class. However, ForMultipleChoice is not yet available so we will use `openvino.convert_model()` after exporting ONNX model\n", + "- We'll use [SyedArsal/roberta-urdu-small-finetuned-news](https://huggingface.co/SyedArsal/roberta-urdu-small-finetuned-news) model from HuggingFace as an example\n", + "- We also need the `vocab.txt` saved from `AutoTokenizer`. This is the same for every model, these are assets (saved in `/assets`) needed for tokenization inside Spark NLP." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TDapJ_09nqXQ", + "outputId": "ebd3710c-cc11-4a15-e68b-a00abe2c2b5e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pip in /usr/local/lib/python3.10/dist-packages (24.1.2)\n", + "Collecting pip\n", + " Downloading pip-24.3.1-py3-none-any.whl.metadata (3.7 kB)\n", + "Downloading pip-24.3.1-py3-none-any.whl (1.8 MB)\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m58.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: pip\n", + " Attempting uninstall: pip\n", + " Found existing installation: pip 24.1.2\n", + " Uninstalling pip-24.1.2:\n", + " Successfully uninstalled pip-24.1.2\n", + "Successfully installed pip-24.3.1\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m38.7/38.7 MB\u001b[0m \u001b[31m134.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m170.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m62.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m182.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install --upgrade pip\n", + "!pip install -q --upgrade transformers[onnx] optimum openvino==2024.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 345, + "referenced_widgets": [ + "e8de475f418a466b96ad9dc8092ec472", + "a03515b35f204995bf9d6defc615f470", + "54956d4234d94187bdb65e4579d2835c", + "57e03f6690a1432ba954fee43cc1fcd6", + "d880034ea2dd49dd82302278d8010880", + "687333d4a91642d181fb77bbc9a78804", + "9cb8c3de61104d4c9881eaad96e18026", + "8267faf0d96144bc942268a47ac8110c", + "6fa07016fc924c88b6a11ada1c29120a", + "f94465b9d7d54045a915001d31b70521", + "0fb89a9abe2c4e8da648888e3fc7e2c9", + "b3eae74cf64b4d3ab0e078fa5030b250", + "522072c6408b4d6fae0fbb2f2a6b7148", + "08f471f23ae646f180039978795e8a33", + "63f117a26eea4a7fa1ab3045e44f8cce", + "53aaf71f49bc49cfa9e095ef69e2e698", + "dcd36b74d4e944b884469f3ae05a1965", + "9bc404edcf0f4c56adb5dfbd35efeddb", + "70fd078fcbce4adca6d0b64be8bb776d", + "8ce761e53ee4462d9fcad5417b59cb0e", + "875c8f1d490b48588cfc9b0def454697", + "977f303d2bb541b8af9010ff27c6ebe5", + "3d93a8c90a764aa4aeeaa3c1fb6e0233", + "dd62c16436cc478eac789142f6777372", + "55de1efb39294f9381a212377046996c", + "fbbfa821015340e0a975594e30f39610", + "0709e7324f8140bfaef6c89dca53ad44", + "abff1dfbd61e4f47b96d159bc2f991cd", + "340409d1364548be87c0642dde478afd", + "dd45d5f6018740d8a77c22feb3a9a577", + "e9930cf218fe4a359d90acda9c151f95", + "c13a533d9f6d4504a10a4be4899c73b0", + "1227c0df3a70454ba87433b69da0f7bb", + "21a7a154169942a1a1a0d63bc90e8e4e", + "ce914dd2436848cab32e25ace7e197df", + "19258ca4d31c41a097aba309eb0e611a", + "57036535951648b2aea21e0666976fb3", + "047394b2e37a4f9dbb484202cefd68b2", + "7da322004e0744fa9b680d9db35bc482", + "6483fb6eaaee48efb60d84d9c7f9e208", + "a2e847b1391d4dfa87601e2a8fccd4d4", + "c7f1d71711e84ee3bfa78ba4c441c845", + "24fe95a1aeec4cd59b834bac38a2c01b", + "d8800789f1444604a0a8973b1f030870", + "1d03f704409c4dc088ec82e9c5051735", + "fd50ff10d8dd488cacf80b2a33eb6dbd", + "5f8534acd76b4d1ba2ffface18e74c16", + "d0a1693ed7af4e6c995492c16428475b", + "ffdc0841c8b24dd098728aafee63af12", + "68b64710efbb4b40aaa0040614c9a165", + "1170d2bfb83a4a76845f83a238e60f45", + "2a5fd903f52d4486936b7ec5d8c0392f", + "57896ba3021b408a83430ef592415e03", + "5dd793253ae647578ece380a8dea82eb", + "5380bbfd60d849028ec577222997a68e", + "ded0269796e04d91a76e501767aa7574", + "1ef03d592187402abf68d9a4c73246df", + "9c6f5f3bd27f417c9eff2f410d015136", + "e9c87a756e944c10bb5279cc676e5447", + "fe7ca09bb6604c429d8200fba84a0739", + "caa5b14ea26043e8b02d82867d492199", + "62f5d8b2dfad403a9364f13bdcc6ed18", + "c2e9a901ea354b5cb0b32f4802118a3f", + "2f19ed6e6c304f7a82bca722367f4fe5", + "4f916f48e3b14fa094fc8c8829d218d3", + "87d837fa24984ddb965ed9662a49db26", + "f8cefe5fd5ca474080e95ec751fd2d9a", + "c78290cc75f04ab996fd9f73d24a2bf1", + "af8eb3352b4c425091770aa8616b2d6a", + "a69b6cfa6ce34493ba4001bbfa172d7d", + "f9927a5ce3364365bd3eee07bb7c70a6", + "2e8b0888ec9a4dceb898d4e866350f34", + "e7bc78719c7946df8830605dc8e1fb51", + "0f7e9d2318794736b1936d3dea7d32fc", + "8a8299fa5a0b41c09d67e0f1a92d5ef5", + "60477dc2daa74dc38d82153b7913d57b", + "51d12fc137764be6bcb9222dabbf9dab" + ] + }, + "id": "_b89GvQKosA0", + "outputId": "d2db5db6-a676-4cfd-e91e-3f91628463a2" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e8de475f418a466b96ad9dc8092ec472", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/728 [00:00 0, chunk -> 0, score -> 0.60569566}, []}]|\n", + "|[{chunk, 0, 6, Germany, {sentence -> 0, chunk -> 0, score -> 0.33706638}, []}] |\n", + "|[{chunk, 0, 5, Tiger, {sentence -> 0, chunk -> 0, score -> 0.25371727}, []}] |\n", + "|[{chunk, 0, 3, 90ยฐC, {sentence -> 0, chunk -> 0, score -> 0.336369}, []}] |\n", + "|[{chunk, 0, 6, Jupiter, {sentence -> 0, chunk -> 0, score -> 0.37836587}, []}] |\n", + "|[{chunk, 0, 7, English, {sentence -> 0, chunk -> 0, score -> 0.339204}, []}] |\n", + "|[{chunk, 0, 9, The Greeks, {sentence -> 0, chunk -> 0, score -> 0.2771055}, []}] |\n", + "|[{chunk, 0, 5, Ozone, {sentence -> 0, chunk -> 0, score -> 0.58542985}, []}] |\n", + "|[{chunk, 0, 6, Africa, {sentence -> 0, chunk -> 0, score -> 0.34312767}, []}] |\n", + "|[{chunk, 0, 13, Pablo Picasso, {sentence -> 0, chunk -> 0, score -> 0.34392032}, []}] |\n", + "+-----------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.ml import Pipeline, PipelineModel\n", + "\n", + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "roberta_for_multiple_choice = RoBertaForMultipleChoice() \\\n", + " .load(f\"{MODEL_NAME}_spark_nlp_openvino\") \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, roberta_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lpxiq1igoj6c" + }, + "source": [ + "That's it! You can now go wild and use hundreds of `RoBertaForMultipleChoice` models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "A100", + "machine_shape": "hm", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "047394b2e37a4f9dbb484202cefd68b2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "0709e7324f8140bfaef6c89dca53ad44": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "08f471f23ae646f180039978795e8a33": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_70fd078fcbce4adca6d0b64be8bb776d", + "max": 503987181, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_8ce761e53ee4462d9fcad5417b59cb0e", + "value": 503987181 + } + }, + "0f7e9d2318794736b1936d3dea7d32fc": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "0fb89a9abe2c4e8da648888e3fc7e2c9": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "1170d2bfb83a4a76845f83a238e60f45": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "1227c0df3a70454ba87433b69da0f7bb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "19258ca4d31c41a097aba309eb0e611a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_a2e847b1391d4dfa87601e2a8fccd4d4", + "max": 1503982, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_c7f1d71711e84ee3bfa78ba4c441c845", + "value": 1503982 + } + }, + "1d03f704409c4dc088ec82e9c5051735": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_fd50ff10d8dd488cacf80b2a33eb6dbd", + "IPY_MODEL_5f8534acd76b4d1ba2ffface18e74c16", + "IPY_MODEL_d0a1693ed7af4e6c995492c16428475b" + ], + "layout": "IPY_MODEL_ffdc0841c8b24dd098728aafee63af12" + } + }, + "1ef03d592187402abf68d9a4c73246df": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_caa5b14ea26043e8b02d82867d492199", + "placeholder": "โ€‹", + "style": "IPY_MODEL_62f5d8b2dfad403a9364f13bdcc6ed18", + "value": "tokenizer.json:โ€‡100%" + } + }, + "21a7a154169942a1a1a0d63bc90e8e4e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_ce914dd2436848cab32e25ace7e197df", + "IPY_MODEL_19258ca4d31c41a097aba309eb0e611a", + "IPY_MODEL_57036535951648b2aea21e0666976fb3" + ], + "layout": "IPY_MODEL_047394b2e37a4f9dbb484202cefd68b2" + } + }, + "24fe95a1aeec4cd59b834bac38a2c01b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2a5fd903f52d4486936b7ec5d8c0392f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2e8b0888ec9a4dceb898d4e866350f34": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2f19ed6e6c304f7a82bca722367f4fe5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "340409d1364548be87c0642dde478afd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "3d93a8c90a764aa4aeeaa3c1fb6e0233": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_dd62c16436cc478eac789142f6777372", + "IPY_MODEL_55de1efb39294f9381a212377046996c", + "IPY_MODEL_fbbfa821015340e0a975594e30f39610" + ], + "layout": "IPY_MODEL_0709e7324f8140bfaef6c89dca53ad44" + } + }, + "4f916f48e3b14fa094fc8c8829d218d3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "51d12fc137764be6bcb9222dabbf9dab": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "522072c6408b4d6fae0fbb2f2a6b7148": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dcd36b74d4e944b884469f3ae05a1965", + "placeholder": "โ€‹", + "style": "IPY_MODEL_9bc404edcf0f4c56adb5dfbd35efeddb", + "value": "pytorch_model.bin:โ€‡100%" + } + }, + "5380bbfd60d849028ec577222997a68e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "53aaf71f49bc49cfa9e095ef69e2e698": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "54956d4234d94187bdb65e4579d2835c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_8267faf0d96144bc942268a47ac8110c", + "max": 728, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_6fa07016fc924c88b6a11ada1c29120a", + "value": 728 + } + }, + "55de1efb39294f9381a212377046996c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dd45d5f6018740d8a77c22feb3a9a577", + "max": 1385, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_e9930cf218fe4a359d90acda9c151f95", + "value": 1385 + } + }, + "57036535951648b2aea21e0666976fb3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_24fe95a1aeec4cd59b834bac38a2c01b", + "placeholder": "โ€‹", + "style": "IPY_MODEL_d8800789f1444604a0a8973b1f030870", + "value": "โ€‡1.50M/1.50Mโ€‡[00:01<00:00,โ€‡864kB/s]" + } + }, + "57896ba3021b408a83430ef592415e03": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "57e03f6690a1432ba954fee43cc1fcd6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_f94465b9d7d54045a915001d31b70521", + "placeholder": "โ€‹", + "style": "IPY_MODEL_0fb89a9abe2c4e8da648888e3fc7e2c9", + "value": "โ€‡728/728โ€‡[00:00<00:00,โ€‡60.0kB/s]" + } + }, + "5dd793253ae647578ece380a8dea82eb": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5f8534acd76b4d1ba2ffface18e74c16": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_2a5fd903f52d4486936b7ec5d8c0392f", + "max": 1150157, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_57896ba3021b408a83430ef592415e03", + "value": 1150157 + } + }, + "60477dc2daa74dc38d82153b7913d57b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "62f5d8b2dfad403a9364f13bdcc6ed18": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "63f117a26eea4a7fa1ab3045e44f8cce": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_875c8f1d490b48588cfc9b0def454697", + "placeholder": "โ€‹", + "style": "IPY_MODEL_977f303d2bb541b8af9010ff27c6ebe5", + "value": "โ€‡504M/504Mโ€‡[00:02<00:00,โ€‡244MB/s]" + } + }, + "6483fb6eaaee48efb60d84d9c7f9e208": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "687333d4a91642d181fb77bbc9a78804": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "68b64710efbb4b40aaa0040614c9a165": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6fa07016fc924c88b6a11ada1c29120a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "70fd078fcbce4adca6d0b64be8bb776d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7da322004e0744fa9b680d9db35bc482": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8267faf0d96144bc942268a47ac8110c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "875c8f1d490b48588cfc9b0def454697": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "87d837fa24984ddb965ed9662a49db26": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "8a8299fa5a0b41c09d67e0f1a92d5ef5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "8ce761e53ee4462d9fcad5417b59cb0e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "977f303d2bb541b8af9010ff27c6ebe5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "9bc404edcf0f4c56adb5dfbd35efeddb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "9c6f5f3bd27f417c9eff2f410d015136": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_c2e9a901ea354b5cb0b32f4802118a3f", + "max": 3537507, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_2f19ed6e6c304f7a82bca722367f4fe5", + "value": 3537507 + } + }, + "9cb8c3de61104d4c9881eaad96e18026": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "a03515b35f204995bf9d6defc615f470": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_687333d4a91642d181fb77bbc9a78804", + "placeholder": "โ€‹", + "style": "IPY_MODEL_9cb8c3de61104d4c9881eaad96e18026", + "value": "config.json:โ€‡100%" + } + }, + "a2e847b1391d4dfa87601e2a8fccd4d4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a69b6cfa6ce34493ba4001bbfa172d7d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_60477dc2daa74dc38d82153b7913d57b", + "placeholder": "โ€‹", + "style": "IPY_MODEL_51d12fc137764be6bcb9222dabbf9dab", + "value": "โ€‡957/957โ€‡[00:00<00:00,โ€‡84.3kB/s]" + } + }, + "abff1dfbd61e4f47b96d159bc2f991cd": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "af8eb3352b4c425091770aa8616b2d6a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_0f7e9d2318794736b1936d3dea7d32fc", + "max": 957, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_8a8299fa5a0b41c09d67e0f1a92d5ef5", + "value": 957 + } + }, + "b3eae74cf64b4d3ab0e078fa5030b250": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_522072c6408b4d6fae0fbb2f2a6b7148", + "IPY_MODEL_08f471f23ae646f180039978795e8a33", + "IPY_MODEL_63f117a26eea4a7fa1ab3045e44f8cce" + ], + "layout": "IPY_MODEL_53aaf71f49bc49cfa9e095ef69e2e698" + } + }, + "c13a533d9f6d4504a10a4be4899c73b0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c2e9a901ea354b5cb0b32f4802118a3f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c78290cc75f04ab996fd9f73d24a2bf1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_2e8b0888ec9a4dceb898d4e866350f34", + "placeholder": "โ€‹", + "style": "IPY_MODEL_e7bc78719c7946df8830605dc8e1fb51", + "value": "special_tokens_map.json:โ€‡100%" + } + }, + "c7f1d71711e84ee3bfa78ba4c441c845": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "caa5b14ea26043e8b02d82867d492199": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ce914dd2436848cab32e25ace7e197df": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_7da322004e0744fa9b680d9db35bc482", + "placeholder": "โ€‹", + "style": "IPY_MODEL_6483fb6eaaee48efb60d84d9c7f9e208", + "value": "vocab.json:โ€‡100%" + } + }, + "d0a1693ed7af4e6c995492c16428475b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_5dd793253ae647578ece380a8dea82eb", + "placeholder": "โ€‹", + "style": "IPY_MODEL_5380bbfd60d849028ec577222997a68e", + "value": "โ€‡1.15M/1.15Mโ€‡[00:00<00:00,โ€‡1.70MB/s]" + } + }, + "d880034ea2dd49dd82302278d8010880": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d8800789f1444604a0a8973b1f030870": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "dcd36b74d4e944b884469f3ae05a1965": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dd45d5f6018740d8a77c22feb3a9a577": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dd62c16436cc478eac789142f6777372": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_abff1dfbd61e4f47b96d159bc2f991cd", + "placeholder": "โ€‹", + "style": "IPY_MODEL_340409d1364548be87c0642dde478afd", + "value": "tokenizer_config.json:โ€‡100%" + } + }, + "ded0269796e04d91a76e501767aa7574": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_1ef03d592187402abf68d9a4c73246df", + "IPY_MODEL_9c6f5f3bd27f417c9eff2f410d015136", + "IPY_MODEL_e9c87a756e944c10bb5279cc676e5447" + ], + "layout": "IPY_MODEL_fe7ca09bb6604c429d8200fba84a0739" + } + }, + "e7bc78719c7946df8830605dc8e1fb51": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "e8de475f418a466b96ad9dc8092ec472": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_a03515b35f204995bf9d6defc615f470", + "IPY_MODEL_54956d4234d94187bdb65e4579d2835c", + "IPY_MODEL_57e03f6690a1432ba954fee43cc1fcd6" + ], + "layout": "IPY_MODEL_d880034ea2dd49dd82302278d8010880" + } + }, + "e9930cf218fe4a359d90acda9c151f95": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "e9c87a756e944c10bb5279cc676e5447": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_4f916f48e3b14fa094fc8c8829d218d3", + "placeholder": "โ€‹", + "style": "IPY_MODEL_87d837fa24984ddb965ed9662a49db26", + "value": "โ€‡3.54M/3.54Mโ€‡[00:01<00:00,โ€‡3.12MB/s]" + } + }, + "f8cefe5fd5ca474080e95ec751fd2d9a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_c78290cc75f04ab996fd9f73d24a2bf1", + "IPY_MODEL_af8eb3352b4c425091770aa8616b2d6a", + "IPY_MODEL_a69b6cfa6ce34493ba4001bbfa172d7d" + ], + "layout": "IPY_MODEL_f9927a5ce3364365bd3eee07bb7c70a6" + } + }, + "f94465b9d7d54045a915001d31b70521": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f9927a5ce3364365bd3eee07bb7c70a6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fbbfa821015340e0a975594e30f39610": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_c13a533d9f6d4504a10a4be4899c73b0", + "placeholder": "โ€‹", + "style": "IPY_MODEL_1227c0df3a70454ba87433b69da0f7bb", + "value": "โ€‡1.39k/1.39kโ€‡[00:00<00:00,โ€‡96.4kB/s]" + } + }, + "fd50ff10d8dd488cacf80b2a33eb6dbd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_68b64710efbb4b40aaa0040614c9a165", + "placeholder": "โ€‹", + "style": "IPY_MODEL_1170d2bfb83a4a76845f83a238e60f45", + "value": "merges.txt:โ€‡100%" + } + }, + "fe7ca09bb6604c429d8200fba84a0739": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ffdc0841c8b24dd098728aafee63af12": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} From 873e224cd25ace016b8f0619e5ef754df2075941 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 6 Jan 2025 17:35:51 -0500 Subject: [PATCH 007/108] [SPARKNLP-1108] Introducing XlmRoBertaForMultipleChoice --- .../annotator/classifier_dl/__init__.py | 1 + .../xlm_roberta_for_multiple_choice.py | 149 ++++++++ python/sparknlp/internal/__init__.py | 9 + .../xlm_roberta_for_multiple_choice_test.py | 76 ++++ .../ml/ai/XlmRoBertaClassification.scala | 86 +++++ .../dl/XlmRoBertaForMultipleChoice.scala | 351 ++++++++++++++++++ .../XlmRoBertaForMultipleChoiceTestSpec.scala | 83 +++++ 7 files changed, 755 insertions(+) create mode 100644 python/sparknlp/annotator/classifier_dl/xlm_roberta_for_multiple_choice.py create mode 100644 python/test/annotator/classifier_dl/xlm_roberta_for_multiple_choice_test.py create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoiceTestSpec.scala diff --git a/python/sparknlp/annotator/classifier_dl/__init__.py b/python/sparknlp/annotator/classifier_dl/__init__.py index 2b5e30fc3ff359..bb874297d3c998 100644 --- a/python/sparknlp/annotator/classifier_dl/__init__.py +++ b/python/sparknlp/annotator/classifier_dl/__init__.py @@ -55,3 +55,4 @@ from sparknlp.annotator.classifier_dl.albert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.camembert_for_zero_shot_classification import * from sparknlp.annotator.classifier_dl.bert_for_multiple_choice import * +from sparknlp.annotator.classifier_dl.xlm_roberta_for_multiple_choice import * diff --git a/python/sparknlp/annotator/classifier_dl/xlm_roberta_for_multiple_choice.py b/python/sparknlp/annotator/classifier_dl/xlm_roberta_for_multiple_choice.py new file mode 100644 index 00000000000000..8da691d35cc091 --- /dev/null +++ b/python/sparknlp/annotator/classifier_dl/xlm_roberta_for_multiple_choice.py @@ -0,0 +1,149 @@ +# Copyright 2017-2022 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + + +class XlmRoBertaForMultipleChoice(AnnotatorModel, + HasCaseSensitiveProperties, + HasBatchedAnnotate, + HasEngine, + HasMaxSentenceLengthLimit): + """XlmRoBertaForMultipleChoice can load XLM-RoBERTa Models with a span classification head on top for extractive + question-answering tasks like SQuAD (a linear layer on top of the hidden-states output to compute span start + logits and span end logits). + + Pretrained models can be loaded with :meth:`.pretrained` of the companion + object: + + >>> spanClassifier = XlmRoBertaForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") + + The default model is ``"xlm_roberta_base_qa_squad2"``, if no name is + provided. + + For available pretrained models please see the `Models Hub + `__. + + To see which models are compatible and how to import them see + `Import Transformers into Spark NLP ๐Ÿš€ + `_. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``DOCUMENT, DOCUMENT`` ``CHUNK`` + ====================== ====================== + + Parameters + ---------- + batchSize + Batch size. Large values allows faster processing but requires more + memory, by default 8 + caseSensitive + Whether to ignore case in tokens for embeddings matching, by default + False + configProtoBytes + ConfigProto from tensorflow, serialized into byte array. + maxSentenceLength + Max sentence length to process, by default 128 + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> documentAssembler = MultiDocumentAssembler() \\ + ... .setInputCols(["question", "context"]) \\ + ... .setOutputCol(["document_question", "document_context"]) + >>> spanClassifier = XlmRoBertaForMultipleChoice.pretrained() \\ + ... .setInputCols(["document_question", "document_context"]) \\ + ... .setOutputCol("answer") \\ + ... .setCaseSensitive(False) + >>> pipeline = Pipeline().setStages([ + ... documentAssembler, + ... spanClassifier + ... ]) + >>> data = spark.createDataFrame([["What's my name?", "My name is Clara and I live in Berkeley."]]).toDF("question", "context") + >>> result = pipeline.fit(data).transform(data) + >>> result.select("answer.result").show(truncate=False) + +--------------------+ + |result | + +--------------------+ + |[Clara] | + +--------------------+ + """ + name = "XlmRoBertaForMultipleChoice" + + inputAnnotatorTypes = [AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT] + + outputAnnotatorType = AnnotatorType.CHUNK + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.classifier.dl.XlmRoBertaForMultipleChoice", + java_model=None): + super(XlmRoBertaForMultipleChoice, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=8, + maxSentenceLength=128, + caseSensitive=False + ) + + @staticmethod + def loadSavedModel(folder, spark_session): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + XlmRoBertaForMultipleChoice + The restored model + """ + from sparknlp.internal import _XlmRoBertaMultipleChoiceLoader + jModel = _XlmRoBertaMultipleChoiceLoader(folder, spark_session._jsparkSession)._java_obj + return XlmRoBertaForMultipleChoice(java_model=jModel) + + @staticmethod + def pretrained(name="xlm_roberta_base_mc", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "xlm_roberta_base_qa_squad2" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + XlmRoBertaForMultipleChoice + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(XlmRoBertaForMultipleChoice, name, lang, remote_loc) diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..663231ecdc4f0d 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -504,6 +504,15 @@ def __init__(self, path, jspark): ) +class _XlmRoBertaMultipleChoiceLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark): + super(_XlmRoBertaMultipleChoiceLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.classifier.dl.XlmRoBertaForMultipleChoice.loadSavedModel", + path, + jspark, + ) + + class _XlnetLoader(ExtendedJavaWrapper): def __init__(self, path, jspark): super(_XlnetLoader, self).__init__( diff --git a/python/test/annotator/classifier_dl/xlm_roberta_for_multiple_choice_test.py b/python/test/annotator/classifier_dl/xlm_roberta_for_multiple_choice_test.py new file mode 100644 index 00000000000000..b26d50dfa3be1e --- /dev/null +++ b/python/test/annotator/classifier_dl/xlm_roberta_for_multiple_choice_test.py @@ -0,0 +1,76 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import pytest + +from sparknlp.annotator import * +from sparknlp.base import * +from test.util import SparkContextForTest + + +class XlmRoBertaForMultipleChoiceTestSetup(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + self.question = "The Eiffel Tower is located in which country?" + self.choices = "Germany, France, Italy" + + self.spark = SparkContextForTest.spark + empty_df = self.spark.createDataFrame([[""]]).toDF("text") + + document_assembler = MultiDocumentAssembler() \ + .setInputCols(["question", "context"]) \ + .setOutputCols(["document_question", "document_context"]) + + bert_for_multiple_choice = XlmRoBertaForMultipleChoice.pretrained() \ + .setInputCols(["document_question", "document_context"]) \ + .setOutputCol("answer") + + pipeline = Pipeline(stages=[document_assembler, bert_for_multiple_choice]) + + self.pipeline_model = pipeline.fit(empty_df) + + +@pytest.mark.slow +class XlmRoBertaForMultipleChoiceTest(XlmRoBertaForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + self.data = self.spark.createDataFrame([[self.question, self.choices]]).toDF("question","context") + self.data.show(truncate=False) + + def test_run(self): + result_df = self.pipeline_model.transform(self.data) + result_df.show(truncate=False) + for row in result_df.collect(): + self.assertTrue(row["answer"][0].result != "") + + +@pytest.mark.slow +class LightXlmRoBertaForMultipleChoiceTest(XlmRoBertaForMultipleChoiceTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.pipeline_model) + annotations_result = light_pipeline.fullAnnotate(self.question,self.choices) + print(annotations_result) + for result in annotations_result: + self.assertTrue(result["answer"][0].result != "") + + result = light_pipeline.annotate(self.question,self.choices) + print(result) + self.assertTrue(result["answer"] != "") diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala b/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala index 60b8440d48e69b..e5887a0eb1dc1e 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala @@ -465,6 +465,92 @@ private[johnsnowlabs] class XlmRoBertaClassification( (startScores, endScores) } + override def tagSpanMultipleChoice(batch: Seq[Array[Int]]): Array[Float] = { + val logits = detectedEngine match { + case ONNX.name => computeLogitsMultipleChoiceWithOnnx(batch) + case Openvino.name => computeLogitsMultipleChoiceWithOv(batch) + } + + calculateSoftmax(logits) + } + + private def computeLogitsMultipleChoiceWithOnnx(batch: Seq[Array[Int]]): Array[Float] = { + val sequenceLength = batch.head.length + val inputIds = Array(batch.map(x => x.map(_.toLong)).toArray) + val attentionMask = Array( + batch.map(sentence => sentence.map(x => if (x == 0L) 0L else 1L)).toArray) + val tokenTypeIds = Array(batch.map(_ => Array.fill(sequenceLength)(0L)).toArray) + + val (ortSession, ortEnv) = onnxWrapper.get.getSession(onnxSessionOptions) + val tokenTensors = OnnxTensor.createTensor(ortEnv, inputIds) + val maskTensors = OnnxTensor.createTensor(ortEnv, attentionMask) + + val inputs = + Map( + "input_ids" -> tokenTensors, + "attention_mask" -> maskTensors).asJava + + try { + val output = ortSession.run(inputs) + try { + + val logits = output + .get("logits") + .get() + .asInstanceOf[OnnxTensor] + .getFloatBuffer + .array() + + tokenTensors.close() + maskTensors.close() + + logits + } finally if (output != null) output.close() + } catch { + case e: Exception => + // Log the exception as a warning + println("Exception in computeLogitsMultipleChoiceWithOnnx: ", e) + // Rethrow the exception to propagate it further + throw e + } + } + + private def computeLogitsMultipleChoiceWithOv(batch: Seq[Array[Int]]): Array[Float] = { + val (numChoices, sequenceLength) = (batch.length, batch.head.length) + // batch_size, num_choices, sequence_length + val shape = Some(Array(1, numChoices, sequenceLength)) + val (tokenTensors, maskTensors, _) = + PrepareEmbeddings.prepareOvLongBatchTensorsWithSegment( + batch, + sequenceLength, + numChoices, + sentencePadTokenId, + shape) + + val compiledModel = openvinoWrapper.get.getCompiledModel() + val inferRequest = compiledModel.create_infer_request() + inferRequest.set_tensor("input_ids", tokenTensors) + inferRequest.set_tensor("attention_mask", maskTensors) + + inferRequest.infer() + + try { + try { + val logits = inferRequest + .get_output_tensor() + .data() + + logits + } + } catch { + case e: Exception => + // Log the exception as a warning + logger.warn("Exception in computeLogitsMultipleChoiceWithOv", e) + // Rethrow the exception to propagate it further + throw e + } + } + private def computeLogitsWithTF( batch: Seq[Array[Int]], maxSentenceLength: Int): (Array[Float], Array[Float]) = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala new file mode 100644 index 00000000000000..338230a35f8b81 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala @@ -0,0 +1,351 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.ml.ai.XlmRoBertaClassification +import com.johnsnowlabs.ml.onnx.{OnnxWrapper, ReadOnnxModel, WriteOnnxModel} +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.tensorflow.TensorflowWrapper +import com.johnsnowlabs.ml.tensorflow.sentencepiece.{ + ReadSentencePieceModel, + SentencePieceWrapper, + WriteSentencePieceModel +} +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadSentencePieceAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp._ +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntParam, Param} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +/** RoBertaForMultipleChoice can load BERT Models with a multiple choice classification head on top + * (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val spanClassifier = RoBertaForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * }}} + * The default model is `"bert_base_uncased_multiple_choice"`, if no name is provided. + * + * For available pretrained models please see the + * [[https://sparknlp.org/models?task=Multiple+Choice Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RoBertaForMultipleChoiceTestSpec.scala RoBertaForMultipleChoiceTestSpec]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val document = new MultiDocumentAssembler() + * .setInputCols("question", "context") + * .setOutputCols("document_question", "document_context") + * + * val questionAnswering = RoBertaForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * .setCaseSensitive(false) + * + * val pipeline = new Pipeline().setStages(Array( + * document, + * questionAnswering + * )) + * + * val data = Seq("The Eiffel Tower is located in which country?", "Germany, France, Italy").toDF("question", "context") + * val result = pipeline.fit(data).transform(data) + * + * result.select("answer.result").show(false) + * +---------------------+ + * |result | + * +---------------------+ + * |[France] | + * ++--------------------+ + * }}} + * + * @see + * [[BertForQuestionAnswering]] for Question Answering tasks + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ + +class XlmRoBertaForMultipleChoice(override val uid: String) + extends AnnotatorModel[XlmRoBertaForMultipleChoice] + with HasBatchedAnnotate[XlmRoBertaForMultipleChoice] + with WriteOnnxModel + with WriteOpenvinoModel + with WriteSentencePieceModel + with HasCaseSensitiveProperties + with HasEngine { + + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("XlmRoBertaForMultipleChoice")) + + /** Input Annotator Types: DOCUMENT, DOCUMENT + * + * @group anno + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) + + /** Output Annotator Types: CHUNK + * + * @group anno + */ + override val outputAnnotatorType: AnnotatorType = AnnotatorType.CHUNK + + /** Max sentence length to process (Default: `128`) + * + * @group param + */ + val maxSentenceLength = + new IntParam(this, "maxSentenceLength", "Max sentence length to process") + + /** @group setParam */ + def setMaxSentenceLength(value: Int): this.type = { + require( + value <= 512, + "XLM-RoBERTa models do not support sequences longer than 512 because of trainable positional embeddings.") + require(value >= 1, "The maxSentenceLength must be at least 1") + set(maxSentenceLength, value) + this + } + + val choicesDelimiter = + new Param[String](this, "choicesDelimiter", "Delimiter character use to split the choices") + + def setChoicesDelimiter(value: String): this.type = set(choicesDelimiter, value) + + private var _model: Option[Broadcast[XlmRoBertaClassification]] = None + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + tensorflowWrapper: Option[TensorflowWrapper], + onnxWrapper: Option[OnnxWrapper], + openvinoWrapper: Option[OpenvinoWrapper], + spp: SentencePieceWrapper): XlmRoBertaForMultipleChoice = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new XlmRoBertaClassification( + tensorflowWrapper, + onnxWrapper, + openvinoWrapper, + spp, + tags = Map.empty[String, Int]) + ) + ) + } + + this + } + + /** @group getParam */ + def getModelIfNotSet: XlmRoBertaClassification = _model.get.value + + /** Whether to lowercase tokens or not (Default: `true`). + * + * @group setParam + */ + override def setCaseSensitive(value: Boolean): this.type = set(this.caseSensitive, value) + + setDefault( + batchSize -> 8, + maxSentenceLength -> 128, + caseSensitive -> true, + choicesDelimiter -> "," + ) + + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + * + * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences + * that belong to the same original row !! (challenging) + */ + override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { + batchedAnnotations.map(annotations => { + if (annotations.nonEmpty) { + getModelIfNotSet.predictSpanMultipleChoice( + annotations, + $(choicesDelimiter), + $(maxSentenceLength), + $(caseSensitive)) + } else { + Seq.empty[Annotation] + } + }) + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + writeSentencePieceModel( + path, + spark, + getModelIfNotSet.spp, + "_xlmroberta", + XlmRoBertaForSequenceClassification.sppFile) + getEngine match { + case ONNX.name => + writeOnnxModel( + path, + spark, + getModelIfNotSet.onnxWrapper.get, + "_xlm_roberta_mc_classification", + XlmRoBertaForMultipleChoice.onnxFile) + case Openvino.name => + writeOpenvinoModel( + path, + spark, + getModelIfNotSet.openvinoWrapper.get, + "openvino_model.xml", + XlmRoBertaForMultipleChoice.openvinoFile) + + } + } + +} + +trait ReadablePretrainedXmlRoBertaForMultipleChoiceModel + extends ParamsAndFeaturesReadable[XlmRoBertaForMultipleChoice] + with HasPretrained[XlmRoBertaForMultipleChoice] { + override val defaultModelName: Some[String] = Some("bert_base_uncased_multiple_choice") + + /** Java compliant-overrides */ + override def pretrained(): XlmRoBertaForMultipleChoice = super.pretrained() + + override def pretrained(name: String): XlmRoBertaForMultipleChoice = super.pretrained(name) + + override def pretrained(name: String, lang: String): XlmRoBertaForMultipleChoice = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): XlmRoBertaForMultipleChoice = + super.pretrained(name, lang, remoteLoc) +} + +trait ReadRoBertaForMultipleChoiceModelDLModel + extends ReadOnnxModel + with ReadOpenvinoModel + with ReadSentencePieceModel { + this: ParamsAndFeaturesReadable[XlmRoBertaForMultipleChoice] => + + override val onnxFile: String = "xlm_roberta_mc_classification_onnx" + override val openvinoFile: String = "xlm_roberta_mc_classification_openvino" + override val sppFile: String = "xlmroberta_spp" + + def readModel(instance: XlmRoBertaForMultipleChoice, path: String, spark: SparkSession): Unit = { + val spp = readSentencePieceModel(path, spark, "_xlmroberta_spp", sppFile) + instance.getEngine match { + case ONNX.name => + val onnxWrapper = + readOnnxModel(path, spark, "xlm_roberta_qa_classification_onnx") + instance.setModelIfNotSet(spark, None, Some(onnxWrapper), None, spp) + case Openvino.name => + val openvinoWrapper = readOpenvinoModel(path, spark, "xlm_roberta_qa_classification_ov") + instance.setModelIfNotSet(spark, None, None, Some(openvinoWrapper), spp) + case _ => + throw new Exception(notSupportedEngineError) + } + } + + addReader(readModel) + + def loadSavedModel(modelPath: String, spark: SparkSession): XlmRoBertaForMultipleChoice = { + val (localModelPath, detectedEngine) = modelSanityCheck(modelPath) + + val spModel = loadSentencePieceAsset(localModelPath, "sentencepiece.bpe.model") + + /*Universal parameters for all engines*/ + val annotatorModel = new XlmRoBertaForMultipleChoice() + + annotatorModel.set(annotatorModel.engine, detectedEngine) + + detectedEngine match { + case ONNX.name => + val onnxWrapper = + OnnxWrapper.read(spark, localModelPath, zipped = false, useBundle = true) + annotatorModel + .setModelIfNotSet(spark, None, Some(onnxWrapper), None, spModel) + + case Openvino.name => + val ovWrapper: OpenvinoWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine) + annotatorModel + .setModelIfNotSet(spark, None, None, Some(ovWrapper), spModel) + + + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } + +} + +/** This is the companion object of [[XlmRoBertaForMultipleChoice]]. Please refer to that class + * for the documentation. + */ +object XlmRoBertaForMultipleChoice + extends ReadablePretrainedXmlRoBertaForMultipleChoiceModel + with ReadRoBertaForMultipleChoiceModelDLModel \ No newline at end of file diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoiceTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoiceTestSpec.scala new file mode 100644 index 00000000000000..2e571cf2662b19 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoiceTestSpec.scala @@ -0,0 +1,83 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.classifier.dl + +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, MultiDocumentAssembler} +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.tags.SlowTest +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec + +class XlmRoBertaForMultipleChoiceTestSpec extends AnyFlatSpec with SparkSessionTest { + + import spark.implicits._ + + lazy val pipelineModel = getXlmRoBertaForMultipleChoicePipelineModel + + val testDataframe = + Seq(("The Eiffel Tower is located in which country?", "Germany, France, Italy")) + .toDF("question", "context") + + "XlmRoBertaForMultipleChoice" should "answer a multiple choice question" taggedAs SlowTest in { + val resultDf = pipelineModel.transform(testDataframe) + resultDf.show(truncate = false) + + val result = AssertAnnotations.getActualResult(resultDf, "answer") + result.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + } + + it should "work with light pipeline fullAnnotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultFullAnnotate = lightPipeline.fullAnnotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultFullAnnotate") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + assert(answerAnnotation.result.nonEmpty) + } + + it should "work with light pipeline annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(pipelineModel) + val resultAnnotate = lightPipeline.annotate( + "The Eiffel Tower is located in which country?", + "Germany, France, Italy") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.nonEmpty) + } + + private def getXlmRoBertaForMultipleChoicePipelineModel = { + val documentAssembler = new MultiDocumentAssembler() + .setInputCols("question", "context") + .setOutputCols("document_question", "document_context") + + val bertForMultipleChoice = XlmRoBertaForMultipleChoice + .pretrained() + .setInputCols("document_question", "document_context") + .setOutputCol("answer") + + val pipeline = new Pipeline().setStages(Array(documentAssembler, bertForMultipleChoice)) + + pipeline.fit(emptyDataSet) + } + +} From 6c3e9cca6f9c4c85c6125d0819d00c0923a53d51 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 8 Jan 2025 11:26:46 -0500 Subject: [PATCH 008/108] [SPARKNLP-1108] Adding notebooks example for XlmRoBertaForMultipleChoice --- ...park_NLP_XlmRoBERTaForMultipleChoice.ipynb | 2752 ++++++++++++++++ ...park_NLP_XLMRoBERTaForMultipleChoice.ipynb | 2840 +++++++++++++++++ 2 files changed, 5592 insertions(+) create mode 100644 examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBERTaForMultipleChoice.ipynb create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_XLMRoBERTaForMultipleChoice.ipynb diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBERTaForMultipleChoice.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBERTaForMultipleChoice.ipynb new file mode 100644 index 00000000000000..53f3cac18526c5 --- /dev/null +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBERTaForMultipleChoice.ipynb @@ -0,0 +1,2752 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "PAsu8UVGoLVf" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBERTaForMultipleChoice.ipynb)\n", + "\n", + "## Import ONNX XlmRoBERTaForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- ONNX support was introduced in `Spark NLP 5.0.0`, enabling high performance inference for models.\n", + "- `XlmRoBertaForMultipleChoice` is only available since in `Spark NLP 5.6.0` and after. So please make sure you have upgraded to the latest Spark NLP release\n", + "- You can import BERT models trained/fine-tuned for question answering via `XlmRoBertaForMultipleChoice` or `TFXlmRobertaForMultipleChoice`. These models are usually under `Multiple Choice` category and have `bert` in their labels\n", + "- Reference: [XlmRoBertaForMultipleChoice](https://huggingface.co/docs/transformers/en/model_doc/xlm-roberta#transformers.XLMRobertaModel)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OzijcdtQpOx9" + }, + "source": [ + "## Export and Save HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MlgoClMXpSg4" + }, + "source": [ + "- Let's install `transformers` package with the `onnx` extension and it's dependencies. You don't need `onnx` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cJWbob-kHICU", + "outputId": "8fcc8341-d9a9-4a60-fc0e-d0e66724f5da" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/424.1 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m424.1/424.1 kB\u001b[0m \u001b[31m14.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/13.3 MB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r", + "\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[90mโ•บ\u001b[0m\u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m6.1/13.3 MB\u001b[0m \u001b[31m183.1 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r", + "\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[91mโ•ธ\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m213.9 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r", + "\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[91mโ•ธ\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m213.9 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m108.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/212.7 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m212.7/212.7 kB\u001b[0m \u001b[31m19.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/46.0 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m3.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m480.6/480.6 kB\u001b[0m \u001b[31m30.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m84.5/84.5 kB\u001b[0m \u001b[31m7.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m62.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m455.8/455.8 kB\u001b[0m \u001b[31m35.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m10.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m179.3/179.3 kB\u001b[0m \u001b[31m16.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m13.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m104.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.5/55.5 kB\u001b[0m \u001b[31m5.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m17.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install -q --upgrade transformers[onnx] optimum" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XtewR2xdOa5s" + }, + "source": [ + "- HuggingFace has an extension called Optimum which offers specialized model inference, including ONNX. We can use this to import and export ONNX models with `from_pretrained` and `save_pretrained`.\n", + "- We'll use the treained model above as an example and load it as a `ORTModelForMultipleChoice`, representing an ONNX model." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 313, + "referenced_widgets": [ + "5a6b55c5428a495fad1ebaf11e9cba47", + "3087b40bfe6149ac87b25ca3cbd58c27", + "56c5db9764cf4812b7501803526ab6dd", + "c33c6f4ef9f34a59a76c7e35e27fd921", + "31fb13ffe1f64352bf6d0d13715f69dc", + "5614b155b63a481b8009e928a6b51e73", + "ff89cbd6d6534f9fb96cfd69065cd1d6", + "45b4a7ea9a344191a230c4ec56d49e49", + "364a58a194e0463a9a0fc1faaab5d3c2", + "37ad21890bbe42d2a5f9e524f63e25e4", + "04121aeaee374bb2aebd0a9e9e6f4fd8", + "8ac75b3dd140441991638576845ad0f3", + "8e536244d11e4beea9b689ac243ec418", + "76580904cca24de4b1277a72d7daddb8", + "e4579cd2435e4401920668c8ef5d5322", + "7277188688b14f47bef757a654c054c4", + "64d7562ef76448e88c7a306714435f93", + "3e703a4c56ee4f54a82fe350d603bf2c", + "db6e39b8eff34362b47485ee484560d8", + "b243a50b92de46e5bf3243a7f4d9e16e", + "197a9551cefe4fcc9b2314d85eea345b", + "e40c294c089e495fa8055c16d0f6827c", + "8dec1b2a2a7442d48e15a4989e89933b", + "acd21c1df95a41e9bf90982cfab40129", + "3756efb5c9e840cd8af48155497e7270", + "37eb824221a04a0b961f108cb23a2d5e", + "a7f24884108a4babbd88ba540e3f573a", + "15f7b099fd02413ea521bfcb9fede6f3", + "908c4aaa27774616be0a7def88f439f5", + "ac09bb5b12554915985c7b617e668de3", + "bbee9192025a4e1a9fbafa75c35a873e", + "62d3d544279a454aacce6c1d868ac582", + "92901f444e044fceb0a114cd5ce7aae0", + "74d44d52ad0545a683513bc9f69eb9a7", + "3eaa9f403b3e4a8d9fef7003131a3b77", + "748ab8cd53504a86a3cf5554fe781b6d", + "ec8ff0112bbd4f3b904c494483efa473", + "879b75200c3d46b08b22f5a5d657b6e7", + "a95e5256f2574f6b8ab125ca4306f149", + "d7d50a233797471aa428e32e9f796468", + "a136ee42b30f4603b62404592c69bbe9", + "e7151d48447441e693aa5051bf24225a", + "87b4325c8fea4258af27529d1c6c0176", + "e776a0aaf785427d82337d9c279e4979", + "305b5d1b52764db3ab08dc97e56111e5", + "fd3f34046aab42129e57890b533f976c", + "e64ac7a366e94de4a14c4d8737562012", + "8041c193e6d94356b83b034f3c44bffb", + "35b6d0c146ac4293b9d5a0351f2807a7", + "88dadd458e6c4bbeb0c24593e85ca01c", + "3f103bd3734c46eead675d345091b009", + "b7b96de11eb542aba27c181b80e56505", + "041a2ee1d1c249ecbac68256c2a0caad", + "666bc5c554c046468ef76ce7ad441e71", + "3de3fe32225f460ebed6ee66bb89af79", + "83dc9a8e7f4f4d81b731a31b8e074016", + "bb8f25d6c60d42bc939f62c3faab42c1", + "3bc2e0cd17044bcaa167616c8c3c8d70", + "9031defef8e84e48bdc62ef5993ff74a", + "37f1ddd9de8643418e9b3ae4130a7196", + "70082d1423dd4544a9fdadb86993aee5", + "120471595cb9489db1dec89359b8a870", + "dc364663e67d43e59231ef681664e229", + "a91a509b63f2491a99ff27b58faca8c3", + "ad80b6c1cfef4b24a6f105c51bd32950", + "4040643bd80349bc964be44fccf38d40" + ] + }, + "id": "Id33annImYM8", + "outputId": "162790f0-6ed1-43ef-d0ce-d09fa9f65418" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5a6b55c5428a495fad1ebaf11e9cba47", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/714 [00:00 0, chunk -> 0, score -> 0.5}, []}]|\n", + "|[{chunk, 0, 6, Germany, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 8, Elephant, {sentence -> 0, chunk -> 0, score -> 0.25000012}, []}] |\n", + "|[{chunk, 0, 5, 100ยฐC, {sentence -> 0, chunk -> 0, score -> 0.33333355}, []}] |\n", + "|[{chunk, 0, 6, Jupiter, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 6, Spanish, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 9, The Greeks, {sentence -> 0, chunk -> 0, score -> 0.25}, []}] |\n", + "|[{chunk, 0, 6, Oxygenm, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 3, Asia, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 15, Vincent van Gogh, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "+----------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "xlm_roberta_for_multiple_choice = XlmRoBertaForMultipleChoice() \\\n", + " .load(\"./{}_spark_nlp_onnx\".format(MODEL_NAME)) \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, xlm_roberta_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "L4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "04121aeaee374bb2aebd0a9e9e6f4fd8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "041a2ee1d1c249ecbac68256c2a0caad": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "120471595cb9489db1dec89359b8a870": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "15f7b099fd02413ea521bfcb9fede6f3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "197a9551cefe4fcc9b2314d85eea345b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "305b5d1b52764db3ab08dc97e56111e5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_fd3f34046aab42129e57890b533f976c", + "IPY_MODEL_e64ac7a366e94de4a14c4d8737562012", + "IPY_MODEL_8041c193e6d94356b83b034f3c44bffb" + ], + "layout": "IPY_MODEL_35b6d0c146ac4293b9d5a0351f2807a7" + } + }, + "3087b40bfe6149ac87b25ca3cbd58c27": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_5614b155b63a481b8009e928a6b51e73", + "placeholder": "โ€‹", + "style": "IPY_MODEL_ff89cbd6d6534f9fb96cfd69065cd1d6", + "value": "config.json:โ€‡100%" + } + }, + "31fb13ffe1f64352bf6d0d13715f69dc": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "35b6d0c146ac4293b9d5a0351f2807a7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "364a58a194e0463a9a0fc1faaab5d3c2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "3756efb5c9e840cd8af48155497e7270": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_ac09bb5b12554915985c7b617e668de3", + "max": 1147, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_bbee9192025a4e1a9fbafa75c35a873e", + "value": 1147 + } + }, + "37ad21890bbe42d2a5f9e524f63e25e4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "37eb824221a04a0b961f108cb23a2d5e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_62d3d544279a454aacce6c1d868ac582", + "placeholder": "โ€‹", + "style": "IPY_MODEL_92901f444e044fceb0a114cd5ce7aae0", + "value": "โ€‡1.15k/1.15kโ€‡[00:00<00:00,โ€‡103kB/s]" + } + }, + "37f1ddd9de8643418e9b3ae4130a7196": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3bc2e0cd17044bcaa167616c8c3c8d70": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dc364663e67d43e59231ef681664e229", + "max": 280, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_a91a509b63f2491a99ff27b58faca8c3", + "value": 280 + } + }, + "3de3fe32225f460ebed6ee66bb89af79": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "3e703a4c56ee4f54a82fe350d603bf2c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "3eaa9f403b3e4a8d9fef7003131a3b77": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_a95e5256f2574f6b8ab125ca4306f149", + "placeholder": "โ€‹", + "style": "IPY_MODEL_d7d50a233797471aa428e32e9f796468", + "value": "sentencepiece.bpe.model:โ€‡100%" + } + }, + "3f103bd3734c46eead675d345091b009": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "4040643bd80349bc964be44fccf38d40": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "45b4a7ea9a344191a230c4ec56d49e49": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5614b155b63a481b8009e928a6b51e73": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "56c5db9764cf4812b7501803526ab6dd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_45b4a7ea9a344191a230c4ec56d49e49", + "max": 714, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_364a58a194e0463a9a0fc1faaab5d3c2", + "value": 714 + } + }, + "5a6b55c5428a495fad1ebaf11e9cba47": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_3087b40bfe6149ac87b25ca3cbd58c27", + "IPY_MODEL_56c5db9764cf4812b7501803526ab6dd", + "IPY_MODEL_c33c6f4ef9f34a59a76c7e35e27fd921" + ], + "layout": "IPY_MODEL_31fb13ffe1f64352bf6d0d13715f69dc" + } + }, + "62d3d544279a454aacce6c1d868ac582": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "64d7562ef76448e88c7a306714435f93": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "666bc5c554c046468ef76ce7ad441e71": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "70082d1423dd4544a9fdadb86993aee5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7277188688b14f47bef757a654c054c4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "748ab8cd53504a86a3cf5554fe781b6d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_a136ee42b30f4603b62404592c69bbe9", + "max": 5069051, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_e7151d48447441e693aa5051bf24225a", + "value": 5069051 + } + }, + "74d44d52ad0545a683513bc9f69eb9a7": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_3eaa9f403b3e4a8d9fef7003131a3b77", + "IPY_MODEL_748ab8cd53504a86a3cf5554fe781b6d", + "IPY_MODEL_ec8ff0112bbd4f3b904c494483efa473" + ], + "layout": "IPY_MODEL_879b75200c3d46b08b22f5a5d657b6e7" + } + }, + "76580904cca24de4b1277a72d7daddb8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_db6e39b8eff34362b47485ee484560d8", + "max": 1112201908, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_b243a50b92de46e5bf3243a7f4d9e16e", + "value": 1112201908 + } + }, + "8041c193e6d94356b83b034f3c44bffb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_666bc5c554c046468ef76ce7ad441e71", + "placeholder": "โ€‹", + "style": "IPY_MODEL_3de3fe32225f460ebed6ee66bb89af79", + "value": "โ€‡17.1M/17.1Mโ€‡[00:00<00:00,โ€‡37.1MB/s]" + } + }, + "83dc9a8e7f4f4d81b731a31b8e074016": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_bb8f25d6c60d42bc939f62c3faab42c1", + "IPY_MODEL_3bc2e0cd17044bcaa167616c8c3c8d70", + "IPY_MODEL_9031defef8e84e48bdc62ef5993ff74a" + ], + "layout": "IPY_MODEL_37f1ddd9de8643418e9b3ae4130a7196" + } + }, + "879b75200c3d46b08b22f5a5d657b6e7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "87b4325c8fea4258af27529d1c6c0176": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "88dadd458e6c4bbeb0c24593e85ca01c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8ac75b3dd140441991638576845ad0f3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_8e536244d11e4beea9b689ac243ec418", + "IPY_MODEL_76580904cca24de4b1277a72d7daddb8", + "IPY_MODEL_e4579cd2435e4401920668c8ef5d5322" + ], + "layout": "IPY_MODEL_7277188688b14f47bef757a654c054c4" + } + }, + "8dec1b2a2a7442d48e15a4989e89933b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_acd21c1df95a41e9bf90982cfab40129", + "IPY_MODEL_3756efb5c9e840cd8af48155497e7270", + "IPY_MODEL_37eb824221a04a0b961f108cb23a2d5e" + ], + "layout": "IPY_MODEL_a7f24884108a4babbd88ba540e3f573a" + } + }, + "8e536244d11e4beea9b689ac243ec418": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_64d7562ef76448e88c7a306714435f93", + "placeholder": "โ€‹", + "style": "IPY_MODEL_3e703a4c56ee4f54a82fe350d603bf2c", + "value": "model.safetensors:โ€‡100%" + } + }, + "9031defef8e84e48bdc62ef5993ff74a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_ad80b6c1cfef4b24a6f105c51bd32950", + "placeholder": "โ€‹", + "style": "IPY_MODEL_4040643bd80349bc964be44fccf38d40", + "value": "โ€‡280/280โ€‡[00:00<00:00,โ€‡23.1kB/s]" + } + }, + "908c4aaa27774616be0a7def88f439f5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "92901f444e044fceb0a114cd5ce7aae0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "a136ee42b30f4603b62404592c69bbe9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a7f24884108a4babbd88ba540e3f573a": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a91a509b63f2491a99ff27b58faca8c3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "a95e5256f2574f6b8ab125ca4306f149": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ac09bb5b12554915985c7b617e668de3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "acd21c1df95a41e9bf90982cfab40129": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_15f7b099fd02413ea521bfcb9fede6f3", + "placeholder": "โ€‹", + "style": "IPY_MODEL_908c4aaa27774616be0a7def88f439f5", + "value": "tokenizer_config.json:โ€‡100%" + } + }, + "ad80b6c1cfef4b24a6f105c51bd32950": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b243a50b92de46e5bf3243a7f4d9e16e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "b7b96de11eb542aba27c181b80e56505": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "bb8f25d6c60d42bc939f62c3faab42c1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_70082d1423dd4544a9fdadb86993aee5", + "placeholder": "โ€‹", + "style": "IPY_MODEL_120471595cb9489db1dec89359b8a870", + "value": "special_tokens_map.json:โ€‡100%" + } + }, + "bbee9192025a4e1a9fbafa75c35a873e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "c33c6f4ef9f34a59a76c7e35e27fd921": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_37ad21890bbe42d2a5f9e524f63e25e4", + "placeholder": "โ€‹", + "style": "IPY_MODEL_04121aeaee374bb2aebd0a9e9e6f4fd8", + "value": "โ€‡714/714โ€‡[00:00<00:00,โ€‡62.4kB/s]" + } + }, + "d7d50a233797471aa428e32e9f796468": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "db6e39b8eff34362b47485ee484560d8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dc364663e67d43e59231ef681664e229": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e40c294c089e495fa8055c16d0f6827c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "e4579cd2435e4401920668c8ef5d5322": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_197a9551cefe4fcc9b2314d85eea345b", + "placeholder": "โ€‹", + "style": "IPY_MODEL_e40c294c089e495fa8055c16d0f6827c", + "value": "โ€‡1.11G/1.11Gโ€‡[00:26<00:00,โ€‡42.9MB/s]" + } + }, + "e64ac7a366e94de4a14c4d8737562012": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_b7b96de11eb542aba27c181b80e56505", + "max": 17082832, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_041a2ee1d1c249ecbac68256c2a0caad", + "value": 17082832 + } + }, + "e7151d48447441e693aa5051bf24225a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "e776a0aaf785427d82337d9c279e4979": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "ec8ff0112bbd4f3b904c494483efa473": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_87b4325c8fea4258af27529d1c6c0176", + "placeholder": "โ€‹", + "style": "IPY_MODEL_e776a0aaf785427d82337d9c279e4979", + "value": "โ€‡5.07M/5.07Mโ€‡[00:00<00:00,โ€‡15.7MB/s]" + } + }, + "fd3f34046aab42129e57890b533f976c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_88dadd458e6c4bbeb0c24593e85ca01c", + "placeholder": "โ€‹", + "style": "IPY_MODEL_3f103bd3734c46eead675d345091b009", + "value": "tokenizer.json:โ€‡100%" + } + }, + "ff89cbd6d6534f9fb96cfd69065cd1d6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_XLMRoBERTaForMultipleChoice.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_XLMRoBERTaForMultipleChoice.ipynb new file mode 100644 index 00000000000000..a853de122ef287 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_XLMRoBERTaForMultipleChoice.ipynb @@ -0,0 +1,2840 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "_V5XcDCnVgSi" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_XLMRoBERTaForMultipleChoice.ipynb)\n", + "\n", + "# Import OpenVINO XlmRoBertaForMultipleChoice models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and exporting XlmRoBertaForMultipleChoice models from HuggingFace for use in Spark NLP, leveraging the various tools provided in the [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html) ecosystem.\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance inference for models. Please make sure you have upgraded to the latest Spark NLP release.\n", + "- You can import models for XlmRoBertaForMultipleChoice from XlmRoBertaForMultipleChoice and they have to be in `Multiple Choice` category." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aghasVppVgSk" + }, + "source": [ + "## 1. Export and Save the HuggingFace model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "be4HsTDMVgSk" + }, + "source": [ + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future releases, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vI7uz_6hVgSl" + }, + "source": [ + "[Optimum Intel](https://github.com/huggingface/optimum-intel?tab=readme-ov-file#openvino) is the interface between the Transformers library and the various model optimization and acceleration tools provided by Intel. HuggingFace models loaded with optimum-intel are automatically optimized for OpenVINO, while being compatible with the Transformers API.\n", + "- Normally, to load a HuggingFace model directly for inference/export, just replace the `AutoModelForXxx` class with the corresponding `OVModelForXxx` class. However, ForMultipleChoice is not yet available so we will use `openvino.convert_model()` after exporting ONNX model\n", + "- We'll use [lenatr99/fine_tuned_copa_XLMroberta](https://huggingface.co/lenatr99/fine_tuned_copa_XLMroberta) model from HuggingFace as an example\n", + "- We also need the `sentencepiece.bpe.model`. This is the same for every model, these are assets (saved in `/assets`) needed for tokenization inside Spark NLP." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TDapJ_09nqXQ", + "outputId": "afae95f6-3beb-40aa-947e-37219bcfead4" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pip in /usr/local/lib/python3.10/dist-packages (24.1.2)\n", + "Collecting pip\n", + " Downloading pip-24.3.1-py3-none-any.whl.metadata (3.7 kB)\n", + "Downloading pip-24.3.1-py3-none-any.whl (1.8 MB)\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m60.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: pip\n", + " Attempting uninstall: pip\n", + " Found existing installation: pip 24.1.2\n", + " Uninstalling pip-24.1.2:\n", + " Successfully uninstalled pip-24.1.2\n", + "Successfully installed pip-24.3.1\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m38.7/38.7 MB\u001b[0m \u001b[31m1.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m27.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m9.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m16.0/16.0 MB\u001b[0m \u001b[31m51.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", + "gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.\n", + "grpcio-status 1.62.3 requires protobuf>=4.21.6, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow 2.17.1 requires protobuf!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5,<5.0.0dev,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\n", + "tensorflow-metadata 1.13.1 requires protobuf<5,>=3.20.3, but you have protobuf 3.20.2 which is incompatible.\u001b[0m\u001b[31m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install --upgrade pip\n", + "!pip install -q --upgrade transformers[onnx] optimum openvino==2024.1" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 313, + "referenced_widgets": [ + "97195a37295742dcb1b15eefa7b44560", + "8e8d24929a274d9fbc9a7d22004d8213", + "d7310ef446fd4897800463fed902154c", + "4a377a786202461f8fe4ec0111724e34", + "e004ccdca9b1475787c8fc487702eeb5", + "2827ebeb3bd741aeaa051701b56e4deb", + "85f519a2de764e63854f7909b624f045", + "37ed9cd7ded147e799f6189674e2bf04", + "701fe16af2334f24b0118a14337c1aec", + "4b5326e1ba734dc390614efe2a9b8749", + "aeba7e7c8bcc4cbeb8aec2247405ed5b", + "30590aca6f214f3c93d12a7e9f59de87", + "c7f0c5b70e9c4b9892176803037ab78a", + "f61b12c8e7ae4496a177e6a23c67a8ce", + "c9f3daa829be4893ad565003eb75a951", + "bd9b2d2e0ef94961a909a7b9d87de7a1", + "76da4d5463c44066aaeacc5df4de11c1", + "bd6fcd2a072c41188703e049129cae05", + "ff7fb85730024477bdfbc1b0ff6bddba", + "4f901bcfbfb9472c840c2c0baf165999", + "3859cdb6c83e4a2ca51c94dd0e3f1daa", + "f176f44c803e4dbcafba8d2825d109e9", + "a437067aefae4533b34348e62d50f90c", + "c2bc4ed51b934e3facd8afa4c4426303", + "d36b9c913da8412f80b9af477eef7268", + "24d1dc375a624d14bc78444c3355bbaf", + "ee91ca47564d4c7f9bbc409496e37e24", + "2d5e76d1457a4fd0bea5e1527463fdc7", + "3b19fd18c04b41e4ae05b56d79bef587", + "e1788e4cb5af484c9c8f0ffa7fee35b9", + "cf82075e7b3842d9bde89017100b586e", + "daa78c3807ff4114853b874edead049d", + "59c6f2d3ff3743d98411e31736f0bdd6", + "70f36d5b6be14f99a91c05d2f67ab611", + "f8bf9a06d65246ed80ff4b2292fff85c", + "0b0e3911eb0c48bc82e7608591c5b89b", + "b7d97ae23d314f84a5f992a9d408c49b", + "6796196831b24a0689c3ad9f63976050", + "e988036ff3d4430889e0ac17ee63a8d7", + "df671ae0aa504726b37f05aa847a32f3", + "88e486e6064941c08790a3b98dad510f", + "a74ba613bffb489ebd05e7e120cf74d8", + "4b65083dcf7a415d8e349434f46e8c3a", + "66eeed186cc74b119c823b70dbf65f3c", + "3286831c191f41679a4a37fa80f95ca0", + "0630bbae098d4d808ea95646b758efa4", + "73000faa2fe84ae2924199ff1739d6bc", + "de00fcd2ab0d41f6bd1c26b35a890216", + "789617a387b544279cc45291830026e0", + "dca3f945a8554ea1bcc0619dd8532c00", + "a2b9c64ccafe41558a15ceaaf6521f91", + "9d08ee3285754d5abf452c9bbbf8efd3", + "4f014d7ebc60458983c135ab7de464cd", + "425f6f30d4f240d0a6bd13286672a8b0", + "1c945394dcfc4bb9a784a8422737bf2d", + "d1fcb19afb064a5eab483c52b5fe8f58", + "002ec56f607c4121ae6afeaf5eed3886", + "5e34c29fceee4d60a93c1d0bbe4df33a", + "e02949629ba9482a97521cae4f997145", + "7f4dd96c7c7848eeaa1e7a7a0f87cb4a", + "6f4a062bf41d422cbf0bedd5dd05a038", + "4709b2e45b4d457d84baca06c5344794", + "2acecd6cbb6248e481db26ad181de982", + "8fb190b88e984e94be552bbec6da85ad", + "a3e4299d9f29480184e5e8fe394ed2e6", + "1072ff4c7c3b46e684b786d7da8b9cf2" + ] + }, + "id": "_b89GvQKosA0", + "outputId": "95837a5d-4d3b-4516-d208-d209eba3657f" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", + "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", + "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", + "You will be able to reuse this secret in all of your notebooks.\n", + "Please note that authentication is recommended but still optional to access public models or datasets.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "97195a37295742dcb1b15eefa7b44560", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/714 [00:00 0, chunk -> 0, score -> 0.5}, []}]|\n", + "|[{chunk, 0, 6, Germany, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 3, Lion, {sentence -> 0, chunk -> 0, score -> 0.25}, []}] |\n", + "|[{chunk, 0, 3, 90ยฐC, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 6, Jupiter, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 6, Spanish, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 9, The Greeks, {sentence -> 0, chunk -> 0, score -> 0.25}, []}] |\n", + "|[{chunk, 0, 6, Oxygenm, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 3, Asia, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "|[{chunk, 0, 15, Vincent van Gogh, {sentence -> 0, chunk -> 0, score -> 0.33333334}, []}] |\n", + "+----------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.ml import Pipeline, PipelineModel\n", + "\n", + "document_assembler = MultiDocumentAssembler() \\\n", + " .setInputCols([\"question\", \"choices\"]) \\\n", + " .setOutputCols([\"document_question\", \"document_choices\"])\n", + "\n", + "xlm_roberta_for_multiple_choice = XlmRoBertaForMultipleChoice() \\\n", + " .load(f\"{MODEL_NAME}_spark_nlp_openvino\") \\\n", + " .setInputCols([\"document_question\", \"document_choices\"])\\\n", + " .setOutputCol(\"answer\") \\\n", + " .setBatchSize(4)\n", + "\n", + "pipeline = Pipeline(stages=[document_assembler, xlm_roberta_for_multiple_choice])\n", + "pipeline_model = pipeline.fit(testing_df)\n", + "\n", + "pipeline_df = pipeline_model.transform(testing_df)\n", + "\n", + "pipeline_df.select(\"answer\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lpxiq1igoj6c" + }, + "source": [ + "That's it! You can now go wild and use hundreds of `XlmRoBertaForMultipleChoice` models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "L4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "002ec56f607c4121ae6afeaf5eed3886": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_6f4a062bf41d422cbf0bedd5dd05a038", + "placeholder": "โ€‹", + "style": "IPY_MODEL_4709b2e45b4d457d84baca06c5344794", + "value": "special_tokens_map.json:โ€‡100%" + } + }, + "0630bbae098d4d808ea95646b758efa4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dca3f945a8554ea1bcc0619dd8532c00", + "placeholder": "โ€‹", + "style": "IPY_MODEL_a2b9c64ccafe41558a15ceaaf6521f91", + "value": "tokenizer.json:โ€‡100%" + } + }, + "0b0e3911eb0c48bc82e7608591c5b89b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_88e486e6064941c08790a3b98dad510f", + "max": 5069051, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_a74ba613bffb489ebd05e7e120cf74d8", + "value": 5069051 + } + }, + "1072ff4c7c3b46e684b786d7da8b9cf2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "1c945394dcfc4bb9a784a8422737bf2d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "24d1dc375a624d14bc78444c3355bbaf": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_daa78c3807ff4114853b874edead049d", + "placeholder": "โ€‹", + "style": "IPY_MODEL_59c6f2d3ff3743d98411e31736f0bdd6", + "value": "โ€‡1.15k/1.15kโ€‡[00:00<00:00,โ€‡97.7kB/s]" + } + }, + "2827ebeb3bd741aeaa051701b56e4deb": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2acecd6cbb6248e481db26ad181de982": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2d5e76d1457a4fd0bea5e1527463fdc7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "30590aca6f214f3c93d12a7e9f59de87": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_c7f0c5b70e9c4b9892176803037ab78a", + "IPY_MODEL_f61b12c8e7ae4496a177e6a23c67a8ce", + "IPY_MODEL_c9f3daa829be4893ad565003eb75a951" + ], + "layout": "IPY_MODEL_bd9b2d2e0ef94961a909a7b9d87de7a1" + } + }, + "3286831c191f41679a4a37fa80f95ca0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_0630bbae098d4d808ea95646b758efa4", + "IPY_MODEL_73000faa2fe84ae2924199ff1739d6bc", + "IPY_MODEL_de00fcd2ab0d41f6bd1c26b35a890216" + ], + "layout": "IPY_MODEL_789617a387b544279cc45291830026e0" + } + }, + "37ed9cd7ded147e799f6189674e2bf04": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3859cdb6c83e4a2ca51c94dd0e3f1daa": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3b19fd18c04b41e4ae05b56d79bef587": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "425f6f30d4f240d0a6bd13286672a8b0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "4709b2e45b4d457d84baca06c5344794": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "4a377a786202461f8fe4ec0111724e34": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_4b5326e1ba734dc390614efe2a9b8749", + "placeholder": "โ€‹", + "style": "IPY_MODEL_aeba7e7c8bcc4cbeb8aec2247405ed5b", + "value": "โ€‡714/714โ€‡[00:00<00:00,โ€‡56.6kB/s]" + } + }, + "4b5326e1ba734dc390614efe2a9b8749": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "4b65083dcf7a415d8e349434f46e8c3a": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "4f014d7ebc60458983c135ab7de464cd": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "4f901bcfbfb9472c840c2c0baf165999": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "59c6f2d3ff3743d98411e31736f0bdd6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "5e34c29fceee4d60a93c1d0bbe4df33a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_2acecd6cbb6248e481db26ad181de982", + "max": 280, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_8fb190b88e984e94be552bbec6da85ad", + "value": 280 + } + }, + "66eeed186cc74b119c823b70dbf65f3c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "6796196831b24a0689c3ad9f63976050": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6f4a062bf41d422cbf0bedd5dd05a038": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "701fe16af2334f24b0118a14337c1aec": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "70f36d5b6be14f99a91c05d2f67ab611": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_f8bf9a06d65246ed80ff4b2292fff85c", + "IPY_MODEL_0b0e3911eb0c48bc82e7608591c5b89b", + "IPY_MODEL_b7d97ae23d314f84a5f992a9d408c49b" + ], + "layout": "IPY_MODEL_6796196831b24a0689c3ad9f63976050" + } + }, + "73000faa2fe84ae2924199ff1739d6bc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_9d08ee3285754d5abf452c9bbbf8efd3", + "max": 17082832, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_4f014d7ebc60458983c135ab7de464cd", + "value": 17082832 + } + }, + "76da4d5463c44066aaeacc5df4de11c1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "789617a387b544279cc45291830026e0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7f4dd96c7c7848eeaa1e7a7a0f87cb4a": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "85f519a2de764e63854f7909b624f045": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "88e486e6064941c08790a3b98dad510f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8e8d24929a274d9fbc9a7d22004d8213": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_2827ebeb3bd741aeaa051701b56e4deb", + "placeholder": "โ€‹", + "style": "IPY_MODEL_85f519a2de764e63854f7909b624f045", + "value": "config.json:โ€‡100%" + } + }, + "8fb190b88e984e94be552bbec6da85ad": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "97195a37295742dcb1b15eefa7b44560": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_8e8d24929a274d9fbc9a7d22004d8213", + "IPY_MODEL_d7310ef446fd4897800463fed902154c", + "IPY_MODEL_4a377a786202461f8fe4ec0111724e34" + ], + "layout": "IPY_MODEL_e004ccdca9b1475787c8fc487702eeb5" + } + }, + "9d08ee3285754d5abf452c9bbbf8efd3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a2b9c64ccafe41558a15ceaaf6521f91": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "a3e4299d9f29480184e5e8fe394ed2e6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a437067aefae4533b34348e62d50f90c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_c2bc4ed51b934e3facd8afa4c4426303", + "IPY_MODEL_d36b9c913da8412f80b9af477eef7268", + "IPY_MODEL_24d1dc375a624d14bc78444c3355bbaf" + ], + "layout": "IPY_MODEL_ee91ca47564d4c7f9bbc409496e37e24" + } + }, + "a74ba613bffb489ebd05e7e120cf74d8": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "aeba7e7c8bcc4cbeb8aec2247405ed5b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "b7d97ae23d314f84a5f992a9d408c49b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_4b65083dcf7a415d8e349434f46e8c3a", + "placeholder": "โ€‹", + "style": "IPY_MODEL_66eeed186cc74b119c823b70dbf65f3c", + "value": "โ€‡5.07M/5.07Mโ€‡[00:00<00:00,โ€‡27.7MB/s]" + } + }, + "bd6fcd2a072c41188703e049129cae05": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "bd9b2d2e0ef94961a909a7b9d87de7a1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c2bc4ed51b934e3facd8afa4c4426303": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_2d5e76d1457a4fd0bea5e1527463fdc7", + "placeholder": "โ€‹", + "style": "IPY_MODEL_3b19fd18c04b41e4ae05b56d79bef587", + "value": "tokenizer_config.json:โ€‡100%" + } + }, + "c7f0c5b70e9c4b9892176803037ab78a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_76da4d5463c44066aaeacc5df4de11c1", + "placeholder": "โ€‹", + "style": "IPY_MODEL_bd6fcd2a072c41188703e049129cae05", + "value": "model.safetensors:โ€‡100%" + } + }, + "c9f3daa829be4893ad565003eb75a951": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_3859cdb6c83e4a2ca51c94dd0e3f1daa", + "placeholder": "โ€‹", + "style": "IPY_MODEL_f176f44c803e4dbcafba8d2825d109e9", + "value": "โ€‡1.11G/1.11Gโ€‡[00:26<00:00,โ€‡42.7MB/s]" + } + }, + "cf82075e7b3842d9bde89017100b586e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "d1fcb19afb064a5eab483c52b5fe8f58": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_002ec56f607c4121ae6afeaf5eed3886", + "IPY_MODEL_5e34c29fceee4d60a93c1d0bbe4df33a", + "IPY_MODEL_e02949629ba9482a97521cae4f997145" + ], + "layout": "IPY_MODEL_7f4dd96c7c7848eeaa1e7a7a0f87cb4a" + } + }, + "d36b9c913da8412f80b9af477eef7268": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_e1788e4cb5af484c9c8f0ffa7fee35b9", + "max": 1147, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_cf82075e7b3842d9bde89017100b586e", + "value": 1147 + } + }, + "d7310ef446fd4897800463fed902154c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_37ed9cd7ded147e799f6189674e2bf04", + "max": 714, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_701fe16af2334f24b0118a14337c1aec", + "value": 714 + } + }, + "daa78c3807ff4114853b874edead049d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dca3f945a8554ea1bcc0619dd8532c00": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "de00fcd2ab0d41f6bd1c26b35a890216": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_425f6f30d4f240d0a6bd13286672a8b0", + "placeholder": "โ€‹", + "style": "IPY_MODEL_1c945394dcfc4bb9a784a8422737bf2d", + "value": "โ€‡17.1M/17.1Mโ€‡[00:01<00:00,โ€‡14.2MB/s]" + } + }, + "df671ae0aa504726b37f05aa847a32f3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "e004ccdca9b1475787c8fc487702eeb5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e02949629ba9482a97521cae4f997145": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_a3e4299d9f29480184e5e8fe394ed2e6", + "placeholder": "โ€‹", + "style": "IPY_MODEL_1072ff4c7c3b46e684b786d7da8b9cf2", + "value": "โ€‡280/280โ€‡[00:00<00:00,โ€‡25.2kB/s]" + } + }, + "e1788e4cb5af484c9c8f0ffa7fee35b9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e988036ff3d4430889e0ac17ee63a8d7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ee91ca47564d4c7f9bbc409496e37e24": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f176f44c803e4dbcafba8d2825d109e9": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "f61b12c8e7ae4496a177e6a23c67a8ce": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_ff7fb85730024477bdfbc1b0ff6bddba", + "max": 1112201908, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_4f901bcfbfb9472c840c2c0baf165999", + "value": 1112201908 + } + }, + "f8bf9a06d65246ed80ff4b2292fff85c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_e988036ff3d4430889e0ac17ee63a8d7", + "placeholder": "โ€‹", + "style": "IPY_MODEL_df671ae0aa504726b37f05aa847a32f3", + "value": "sentencepiece.bpe.model:โ€‡100%" + } + }, + "ff7fb85730024477bdfbc1b0ff6bddba": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} From 4cd472d171e72983e06cf5b99a1cc005e40ec3bc Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 8 Jan 2025 11:19:15 -0500 Subject: [PATCH 009/108] [SPARKNLP-1098] Adding PDF reader support --- build.sbt | 3 +- project/Dependencies.scala | 3 + python/sparknlp/reader/pdf_to_text.py | 65 ++++++ python/sparknlp/reader/sparknlp_reader.py | 7 + python/test/reader/pdf_to_text_test.py | 48 +++++ .../com/johnsnowlabs/reader/PdfToText.scala | 193 ++++++++++++++++++ .../johnsnowlabs/reader/SparkNLPReader.scala | 100 +++++---- .../reader/util/pdf/BinaryFile.scala | 24 +++ .../reader/util/pdf/HasInputValidator.scala | 59 ++++++ .../reader/util/pdf/HasLocalProcess.scala | 25 +++ .../reader/util/pdf/OcrText.scala | 31 +++ .../reader/util/pdf/PageNum.scala | 22 ++ .../reader/util/pdf/PdfUtils.scala | 29 +++ .../resources/reader/pdf/image_3_pages.pdf | Bin 0 -> 15629 bytes src/test/resources/reader/pdf/pdf-title.pdf | Bin 0 -> 25803 bytes .../resources/reader/pdf/text_3_pages.pdf | Bin 0 -> 9487 bytes .../johnsnowlabs/reader/PdfToTextTest.scala | 42 ++++ .../reader/SparkNLPReaderTest.scala | 52 +++++ 18 files changed, 657 insertions(+), 46 deletions(-) create mode 100644 python/sparknlp/reader/pdf_to_text.py create mode 100644 python/test/reader/pdf_to_text_test.py create mode 100644 src/main/scala/com/johnsnowlabs/reader/PdfToText.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/pdf/BinaryFile.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/pdf/HasInputValidator.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/pdf/HasLocalProcess.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/pdf/OcrText.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/pdf/PageNum.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/pdf/PdfUtils.scala create mode 100644 src/test/resources/reader/pdf/image_3_pages.pdf create mode 100644 src/test/resources/reader/pdf/pdf-title.pdf create mode 100644 src/test/resources/reader/pdf/text_3_pages.pdf create mode 100644 src/test/scala/com/johnsnowlabs/reader/PdfToTextTest.scala create mode 100644 src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala diff --git a/build.sbt b/build.sbt index 8eecdc3efb7e27..3f2bfddc412481 100644 --- a/build.sbt +++ b/build.sbt @@ -163,7 +163,8 @@ lazy val utilDependencies = Seq( poiDocx exclude ("org.apache.logging.log4j", "log4j-api"), scratchpad - exclude ("org.apache.logging.log4j", "log4j-api") + exclude ("org.apache.logging.log4j", "log4j-api"), + pdfBox ) lazy val typedDependencyParserDependencies = Seq(junit) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fae6267df57f21..6e47ce2e726613 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -135,6 +135,7 @@ object Dependencies { val llamaCppAarch64 = "com.johnsnowlabs.nlp" %% "jsl-llamacpp-aarch64" % llamaCppVersion val jsoupVersion = "1.18.2" + val jsoup = "org.jsoup" % "jsoup" % jsoupVersion val jakartaMailVersion = "2.1.3" @@ -146,5 +147,7 @@ object Dependencies { val poiDocx = "org.apache.poi" % "poi-ooxml" % poiVersion val scratchpad = "org.apache.poi" % "poi-scratchpad" % poiVersion + val pdfBoxVersion = "2.0.28" + val pdfBox = "org.apache.pdfbox" % "pdfbox" % pdfBoxVersion /** ------- Dependencies end ------- */ } diff --git a/python/sparknlp/reader/pdf_to_text.py b/python/sparknlp/reader/pdf_to_text.py new file mode 100644 index 00000000000000..70b31e757482f0 --- /dev/null +++ b/python/sparknlp/reader/pdf_to_text.py @@ -0,0 +1,65 @@ +from pyspark import keyword_only +from pyspark.ml.param import Param, Params, TypeConverters +from pyspark.ml.param.shared import HasInputCol, HasOutputCol +from pyspark.ml.util import JavaMLReadable, JavaMLWritable +from pyspark.ml.wrapper import JavaTransformer + + +class PdfToText(JavaTransformer, HasInputCol, HasOutputCol, + JavaMLReadable, JavaMLWritable): + """ + Extract text from Pdf document to single string or to several strings per each page. + Input is a column with binary representation of PDF document. + As output generate column with text and page number. + Explode each page as separate row if split to page enabled. + """ + pageNumCol = Param(Params._dummy(), "pageNumCol", + "Page number output column name.", + typeConverter=TypeConverters.toString) + + partitionNum = Param(Params._dummy(), "partitionNum", + "Number of partitions.", + typeConverter=TypeConverters.toInt) + + storeSplittedPdf = Param(Params._dummy(), "storeSplittedPdf", + "Force to store splitted pdf.", + typeConverter=TypeConverters.toBoolean) + + @keyword_only + def __init__(self): + """ + __init__(self) + """ + super(PdfToText, self).__init__() + self._java_obj = self._new_java_obj("com.johnsnowlabs.reader.PdfToText", self.uid) + + + def setInputCol(self, value): + """ + Sets the value of :py:attr:`inputCol`. + """ + return self._set(inputCol=value) + + def setOutputCol(self, value): + """ + Sets the value of :py:attr:`outputCol`. + """ + return self._set(outputCol=value) + + def setPageNumCol(self, value): + """ + Sets the value of :py:attr:`pageNumCol`. + """ + return self._set(pageNumCol=value) + + def setPartitionNum(self, value): + """ + Sets the value of :py:attr:`partitionNum`. + """ + return self._set(partitionNum=value) + + def setStoreSplittedPdf(self, value): + """ + Sets the value of :py:attr:`storeSplittedPdf`. + """ + return self._set(storeSplittedPdf=value) diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 71e3596c25aaa4..13ad4787739b2c 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -118,4 +118,11 @@ def doc(self, docPath): raise TypeError("docPath must be a string") jdf = self._java_obj.doc(docPath) dataframe = self.getDataFrame(self.spark, jdf) + return dataframe + + def pdf(self, pdfPath): + if not isinstance(pdfPath, str): + raise TypeError("docPath must be a string") + jdf = self._java_obj.pdf(pdfPath) + dataframe = self.getDataFrame(self.spark, jdf) return dataframe \ No newline at end of file diff --git a/python/test/reader/pdf_to_text_test.py b/python/test/reader/pdf_to_text_test.py new file mode 100644 index 00000000000000..771b0c8f01bd07 --- /dev/null +++ b/python/test/reader/pdf_to_text_test.py @@ -0,0 +1,48 @@ + +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import pytest +import os + +from sparknlp.reader.pdf_to_text import PdfToText +from test.util import SparkContextForTest +from pyspark.ml import Pipeline + + +class PdfToTextTestSetup(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + self.spark.conf.set("spark.sql.legacy.allowUntypedScalaUDF", "true") + +@pytest.mark.slow +class PdfToTextTest(PdfToTextTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + self.pdf_to_text = PdfToText().setStoreSplittedPdf(True) + pdf_path = os.getcwd() + "/../src/test/resources/reader/pdf" + self.data_frame = self.spark.read.format("binaryFile").load(pdf_path) + + def test_run(self): + pipeline = Pipeline(stages=[self.pdf_to_text]) + pipeline_model = pipeline.fit(self.data_frame) + pdf_df = pipeline_model.transform(self.data_frame) + pdf_df.show() + + self.assertTrue(pdf_df.count() > 0) + + diff --git a/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala b/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala new file mode 100644 index 00000000000000..102ccebd8e628f --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala @@ -0,0 +1,193 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.nlp.IAnnotation +import com.johnsnowlabs.reader.util.pdf._ +import org.apache.pdfbox.pdmodel.PDDocument +import org.apache.pdfbox.text.PDFTextStripper +import org.apache.spark.internal.Logging +import org.apache.spark.ml.Transformer +import org.apache.spark.ml.param.shared.{HasInputCol, HasOutputCol} +import org.apache.spark.ml.param.{BooleanParam, IntParam, Param, ParamMap} +import org.apache.spark.ml.util.{DefaultParamsWritable, Identifiable} +import org.apache.spark.sql.expressions.UserDefinedFunction +import org.apache.spark.sql.functions.{col, posexplode_outer, udf} +import org.apache.spark.sql.types._ +import org.apache.spark.sql.{DataFrame, Dataset} + +import scala.util.{Failure, Success, Try} + +class PdfToText(override val uid: String) + extends Transformer + with DefaultParamsWritable + with HasInputValidator + with HasInputCol + with HasOutputCol + with HasLocalProcess + with PdfToTextTrait { + + def this() = this(Identifiable.randomUID("PDF_TO_TEXT_TRANSFORMER")) + + override def copy(extra: ParamMap): Transformer = defaultCopy(extra) + + protected def outputDataType: StructType = new StructType() + .add($(outputCol), StringType) + .add("height_dimension", IntegerType) + .add("width_dimension", IntegerType) + .add($(inputCol), BinaryType) + .add("exception", StringType) + .add($(pageNumCol), IntegerType) + + override def transformSchema(schema: StructType): StructType = { + // Add the return fields + validateInputCol(schema, $(inputCol), BinaryType) + validateInputCol(schema, $(originCol), StringType) + schema + .add(StructField($(outputCol), StringType, nullable = false)) + .add(StructField($(pageNumCol), IntegerType, nullable = false)) + } + + final val pageNumCol = new Param[String](this, "pageNumCol", "Page number output column name.") + final val originCol = + new Param[String](this, "originCol", "Input column name with original path of file.") + final val partitionNum = new IntParam(this, "partitionNum", "Number of partitions.") + final val storeSplittedPdf = + new BooleanParam(this, "storeSplittedPdf", "Force to store splitted pdf.") + + /** @group getParam */ + def setOriginCol(value: String): this.type = set(originCol, value) + + /** @group setParam */ + def setInputCol(value: String): this.type = set(inputCol, value) + + /** @group setParam */ + def setOutputCol(value: String): this.type = set(outputCol, value) + + /** @group getParam */ + def setPartitionNum(value: Int): this.type = set(partitionNum, value) + + /** @group setParam */ + def setStoreSplittedPdf(value: Boolean): this.type = set(storeSplittedPdf, value) + + setDefault( + inputCol -> "content", + outputCol -> "text", + pageNumCol -> "pagenum", + originCol -> "path", + partitionNum -> 0, + storeSplittedPdf -> false) + + private def transformUDF: UserDefinedFunction = udf( + (path: String, content: Array[Byte]) => { + doProcess(content) + }, + ArrayType(outputDataType)) + + private def doProcess( + content: Array[Byte]): Seq[(String, Int, Int, Array[Byte], String, Int)] = { + val pagesTry = Try(pdfToText(content, $(storeSplittedPdf))) + + pagesTry match { + case Failure(_) => + Seq() + case Success(content) => + content + } + } + + override def transform(df: Dataset[_]): DataFrame = { + transformSchema(df.schema) + + val selCols1 = df.columns + .filterNot(_ == $(inputCol)) + .map(col) :+ posexplode_outer(transformUDF(df.col($(originCol)), df.col($(inputCol)))) + .as(Seq("tmp_num", "tmp_result")) + val selCols = df.columns + .filterNot(_ == $(inputCol)) + .map(col) :+ col("tmp_result.*") + + var result = df.select(selCols1: _*) + result = result + .select(selCols: _*) + $(partitionNum) match { + case 0 => result + case _ => result.repartition($(partitionNum)) + } + } + + override def localProcess( + input: Array[Map[String, Seq[IAnnotation]]]): Array[Map[String, Seq[IAnnotation]]] = { + input.flatMap { case lightRecord => + val pdfs = lightRecord.getOrElse( + getOrDefault(inputCol), + throw new RuntimeException(s"Column not found ${getOrDefault(inputCol)}")) + + pdfs flatMap { case BinaryFile(bytes, path) => + doProcess(bytes).zipWithIndex.map { case ((text, _, _, content, exception, _), pageNum) => + val metadata = + Map("exception" -> exception, "sourcePath" -> path, "pageNum" -> pageNum.toString) + + val result = lightRecord ++ Map( + getOutputCol -> Seq(OcrText(text, metadata, content)), + getOrDefault(pageNumCol) -> Seq(PageNum(pageNum))) + result + } + } + } + } + +} + +trait PdfToTextTrait extends Logging with PdfUtils { + + /* + * extracts a text layer from a PDF. + */ + private def extractText(document: => PDDocument, startPage: Int, endPage: Int): Seq[String] = { + val pdfTextStripper = new PDFTextStripper + pdfTextStripper.setStartPage(startPage + 1) + pdfTextStripper.setEndPage(endPage + 1) + Seq(pdfTextStripper.getText(document)) + } + + def pdfToText( + content: Array[Byte], + storeSplittedPdf: Boolean): Seq[(String, Int, Int, Array[Byte], String, Int)] = { + val validPdf = checkAndFixPdf(content) + val pdfDoc = PDDocument.load(validPdf) + val numPages = pdfDoc.getNumberOfPages + log.info(s"Number of pages ${numPages}") + require(numPages >= 1, "pdf input stream cannot be empty") + + val result = pdfboxMethod(pdfDoc, 0, numPages - 1, content, storeSplittedPdf) + pdfDoc.close() + log.info("Close pdf") + result + } + + private def pdfboxMethod( + pdfDoc: => PDDocument, + startPage: Int, + endPage: Int, + content: Array[Byte], + storeSplittedPdf: Boolean): Seq[(String, Int, Int, Array[Byte], String, Int)] = { + val text = extractText(pdfDoc, startPage, endPage).mkString(System.lineSeparator()) + val heightDimension = pdfDoc.getPage(startPage).getMediaBox.getHeight.toInt + val widthDimension = pdfDoc.getPage(startPage).getMediaBox.getWidth.toInt + Seq((text, heightDimension, widthDimension, if (storeSplittedPdf) content else null, null, 0)) + } +} diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index edc27f66b6424e..3b2ad518204147 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -15,6 +15,8 @@ */ package com.johnsnowlabs.reader +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import org.apache.spark.ml.Pipeline import org.apache.spark.sql.DataFrame import scala.collection.JavaConverters._ @@ -94,51 +96,47 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM } /** Instantiates class to read email files. - * - * - * emailPath: this is a path to a directory of HTML files or a path to an HTML file E.g. - * "path/html/emails" - * - * ==Example== - * {{{ - * val emailsPath = "home/user/emails-directory" - * val sparkNLPReader = new SparkNLPReader() - * val emailDf = sparkNLPReader.email(emailsPath) - * }}} - * - * - * ==Example 2== - * You can use SparkNLP for one line of code - * {{{ - * val emailDf = SparkNLP.read.email(emailsPath) - * }}} - * - * {{{ - * emailDf.select("email").show(false) - * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - * |email | - * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - * |[{Title, Email Text Attachments, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano }}, {NarrativeText, Email test with two text attachments\r\n\r\nCheers,\r\n\r\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}, {NarrativeText, \r\n\r\n\r\n\r\n\r\n\r\nEmail  test with two text attachments\r\n
\r\n
\r\n
\r\n
\r\nCheers,
\r\n
\r\n
\r\n
\r\n\r\n\r\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/html}}, {Attachment, filename.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name="filename.txt"}}, {NarrativeText, This is the content of the file.\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}, {Attachment, filename2.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name="filename2.txt"}}, {NarrativeText, This is an additional content file.\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}]| - * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - * - * emailDf.printSchema() - * root - * |-- path: string (nullable = true) - * |-- content: binary (nullable = true) - * |-- email: array (nullable = true) - * | |-- element: struct (containsNull = true) - * | | |-- elementType: string (nullable = true) - * | | |-- content: string (nullable = true) - * | | |-- metadata: map (nullable = true) - * | | | |-- key: string - * | | | |-- value: string (valueContainsNull = true) - * }}} - * - * - * - * @param params - * Parameter with custom configuration - */ + * + * emailPath: this is a path to a directory of HTML files or a path to an HTML file E.g. + * "path/html/emails" + * + * ==Example== + * {{{ + * val emailsPath = "home/user/emails-directory" + * val sparkNLPReader = new SparkNLPReader() + * val emailDf = sparkNLPReader.email(emailsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val emailDf = SparkNLP.read.email(emailsPath) + * }}} + * + * {{{ + * emailDf.select("email").show(false) + * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |email | + * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[{Title, Email Text Attachments, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano }}, {NarrativeText, Email test with two text attachments\r\n\r\nCheers,\r\n\r\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}, {NarrativeText, \r\n\r\n\r\n\r\n\r\n\r\nEmail  test with two text attachments\r\n
\r\n
\r\n
\r\n
\r\nCheers,
\r\n
\r\n
\r\n
\r\n\r\n\r\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/html}}, {Attachment, filename.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name="filename.txt"}}, {NarrativeText, This is the content of the file.\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}, {Attachment, filename2.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name="filename2.txt"}}, {NarrativeText, This is an additional content file.\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}]| + * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * + * emailDf.printSchema() + * root + * |-- path: string (nullable = true) + * |-- content: binary (nullable = true) + * |-- email: array (nullable = true) + * | |-- element: struct (containsNull = true) + * | | |-- elementType: string (nullable = true) + * | | |-- content: string (nullable = true) + * | | |-- metadata: map (nullable = true) + * | | | |-- key: string + * | | | |-- value: string (valueContainsNull = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ def email(emailPath: String): DataFrame = { val emailReader = new EmailReader(getAddAttachmentContent) @@ -160,4 +158,16 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM wordReader.doc(docPath) } + def pdf(pdfPath: String): DataFrame = { + val spark = ResourceHelper.spark + spark.conf.set("spark.sql.legacy.allowUntypedScalaUDF", "true") + val pdfToText = new PdfToText().setStoreSplittedPdf(true) + val binaryPdfDF = spark.read.format("binaryFile").load(pdfPath) + val pipelineModel = new Pipeline() + .setStages(Array(pdfToText)) + .fit(binaryPdfDF) + + pipelineModel.transform(binaryPdfDF) + } + } diff --git a/src/main/scala/com/johnsnowlabs/reader/util/pdf/BinaryFile.scala b/src/main/scala/com/johnsnowlabs/reader/util/pdf/BinaryFile.scala new file mode 100644 index 00000000000000..58b784615830b2 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/pdf/BinaryFile.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader.util.pdf + +import com.johnsnowlabs.nlp.IAnnotation + +case class BinaryFile(bytes: Array[Byte], path: String) extends IAnnotation { + + override def annotatorType: String = "binary_source_file" + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/util/pdf/HasInputValidator.scala b/src/main/scala/com/johnsnowlabs/reader/util/pdf/HasInputValidator.scala new file mode 100644 index 00000000000000..2c32210c07e810 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/pdf/HasInputValidator.scala @@ -0,0 +1,59 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader.util.pdf + +import org.apache.spark.sql.types.{ArrayType, DataType, MapType, StructType} + +trait HasInputValidator { + val uid: String + + def compareDataTypes(dtype1: DataType, dtype2: DataType): Boolean = { + if (dtype1.getClass != dtype2.getClass) { + return false + } + + (dtype1, dtype2) match { + case (a1: ArrayType, a2: ArrayType) => + compareDataTypes(a1.elementType, a2.elementType) + + case (s1: StructType, s2: StructType) => + if (s1.fields.length != s2.fields.length) { + return false + } + s1.fields.zip(s2.fields).forall { case (field1, field2) => + field1.name == field2.name && compareDataTypes(field1.dataType, field2.dataType) + } + + case (m1: MapType, m2: MapType) => + compareDataTypes(m1.keyType, m2.keyType) && compareDataTypes(m1.valueType, m2.valueType) + + case _ => + dtype1 == dtype2 + } + } + + def validateInputCol(schema: StructType, colName: String, colType: DataType) { + require( + schema.exists(_.name == colName), + s"Missing input column in $uid: Column '${colName}' is not present." + + s"Make sure such transformer exist in your pipeline, " + + s"with the right output names.") + require( + compareDataTypes(schema.find(_.name == colName).map(_.dataType).get, colType), + s"Column '${colName}' is not a valid ${colType} in $uid") + } + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/util/pdf/HasLocalProcess.scala b/src/main/scala/com/johnsnowlabs/reader/util/pdf/HasLocalProcess.scala new file mode 100644 index 00000000000000..840751100afcd4 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/pdf/HasLocalProcess.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader.util.pdf + +import com.johnsnowlabs.nlp.IAnnotation + +trait HasLocalProcess { + + def localProcess( + input: Array[Map[String, Seq[IAnnotation]]]): Array[Map[String, Seq[IAnnotation]]] + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/util/pdf/OcrText.scala b/src/main/scala/com/johnsnowlabs/reader/util/pdf/OcrText.scala new file mode 100644 index 00000000000000..7b06f5eadb07ec --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/pdf/OcrText.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader.util.pdf + +import com.johnsnowlabs.nlp.IAnnotation + +case class OcrText( + text: String, + metadata: Map[String, String], + content: Array[Byte] = Array.empty[Byte]) + extends IAnnotation { + + override def annotatorType: String = "image_to_text" + def begin = 0 + def end = text.length + def result = text + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/util/pdf/PageNum.scala b/src/main/scala/com/johnsnowlabs/reader/util/pdf/PageNum.scala new file mode 100644 index 00000000000000..4c30d679c03b82 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/pdf/PageNum.scala @@ -0,0 +1,22 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader.util.pdf + +import com.johnsnowlabs.nlp.IAnnotation + +case class PageNum(value: Int) extends IAnnotation { + override def annotatorType: String = "pagenum" +} diff --git a/src/main/scala/com/johnsnowlabs/reader/util/pdf/PdfUtils.scala b/src/main/scala/com/johnsnowlabs/reader/util/pdf/PdfUtils.scala new file mode 100644 index 00000000000000..1ecc96f7160823 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/pdf/PdfUtils.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader.util.pdf + +trait PdfUtils { + val MAX_CHARACTER_BEFORE_HEADER = 1000 + + def checkAndFixPdf(content: Array[Byte]): Array[Byte] = { + val pdfStartIndex = new String( + content.slice(0, Math.min(MAX_CHARACTER_BEFORE_HEADER, content.length))).indexOf("%PDF") + if (pdfStartIndex == -1) throw new RuntimeException("Pdf document is not valid") + val validContent = content.slice(pdfStartIndex, content.length) + validContent + } + +} diff --git a/src/test/resources/reader/pdf/image_3_pages.pdf b/src/test/resources/reader/pdf/image_3_pages.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3ef6bcf538cae67cfa355882e4f6701b2d02cb19 GIT binary patch literal 15629 zcmd6OXIK;4+I9k=gN9xN4821V0t5u|pbdT*hFbWnQlReBQ?lqRAA(m{}_2!d3> z1_)aPLB9#;*1gYu-}ijib$*<=AepS0S_zH22zM4j3Oct`_GednvlEzBEXe~K1;ult2{5f7>d?$WxRZ=O5Cb$`Ra zM{MVA`@m2q30Y_o4o}{6EgDDajOzdU_}&8|Cj^Otp5lU8K{(`KTypUD zUeGxZ7=#OW3kLmp;NcTM2yt+Mi?rAazg+=t0RtCafJkw{ARH(z6!`szT7!C$ufoy0 zM`aIZRq(9GPV)&~o-f8b_pB%cH*JJ^g2;Cvb#murACjRcD(Bn+Pe!2o!Jvir`*S&$ z^Cyg7Z4XohJs3$E@3~Yu5}K!7bT5Na2Ad7e8BBr3y@JQ+iMiRZ5*nOvY~ty09&nEx z3c>*#0ZInx;iA|?%|sYD zKLoKe>ZL5OyAhpm>1Byn)12)-3Cu5)-9}Kn7PrJivtQ_o;6P`eI7bzy>Xw>cJVRpi zo#L=IpiFqlfhxiQTG-Em0_v9>M;DGj3d>qymT{plmTnoj^roGYk^yR78eAi-WWf1B ziU#)zoBB)_(GJ0NVr8++8Wp}zyg9U@|rE$Jai(1QT3{=!t z;5~SZiz_}xCf9nwK4nxL(iS(MwHW50DVK&XDbo33c77kLE|d%Bia)@vE`K{P$*%-y=*bp#G4J?|Z5+}9xe^n3#chT~p}Lga@S%-Xr@0=( z>LBw}oa}qPRn3u>#T)ruvS%9KWnI`o8#~Nwk!o3ewn1hmxOILkBpO^|NYYO6>Ktyf z_%b3#zd<#r`^Bfatd>Zxg`1Q*a7ysWeO`!6Bw972YqtK6F~ut^d)I?aZl5t<7mD>M ze1G#{b?Ryo4{5EFnIIK66e&hc6XfB~uFdbFb}z-@;_i~1T!oicX5_VpqLZ@k4HHeb zwKP`;Uk%!MqhCcWB^DY^;(n}9j>BvAoJ-zvtiM6d7WAlb_15TmB*oNryHN%E2mSA$ zK*N5`;qRb*cV>Ov@-hcpIpqZUrProw?aU!L217mpyHAg=viC2Kd7)$)_u`^IP#)hu zx|tG#a+*nIXbIC2)<2iUpie@<=&!blCQ{u>YCABTrEIJ!)31DW{hpYYug}g*15ewq zuWPgjM+TFX6Wu~MCcjO)#jLokdD8evM=J@DH=9+(D(79}A>sr?D+hj*pdeeETBF!P zwMUWU=m@0Je4mPq{LIJ)ma*J}X&Mf`%h%`BjJ%6>^{R(jhuf8cBy^bu-(%3BHZLtw zc@^jpkjm2j_9Q0szzXwtSkdK2+1^K*@+l0EIbk$|8BuZrWPA19N@K{#9 zLSbWSA;(LVChSUg7U+^_tc^CTUqu|?e+RWluC@k#jASc8-oK=0-o0&6c;1gJgyv

`(WWNd;~*3B>$-KEs- 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/reader/pdf/pdf-title.pdf b/src/test/resources/reader/pdf/pdf-title.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3e3ee79597c26f10e6594d55d4913274899ce275 GIT binary patch literal 25803 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/reader/pdf/text_3_pages.pdf b/src/test/resources/reader/pdf/text_3_pages.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fd54f072e9d7376f3d61a268c315ddc4cc67cf8a GIT binary patch literal 9487 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 duowz1P6R*R-3sUa6G})V5)R 0) + } + +} diff --git a/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala new file mode 100644 index 00000000000000..37df1c77f059d9 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala @@ -0,0 +1,52 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.sql.functions.col +import org.scalatest.flatspec.AnyFlatSpec + +class SparkNLPReaderTest extends AnyFlatSpec { + + "pdf" should "read a PDF file and return a structured Dataframe" taggedAs FastTest in { + val pdfPath = "src/test/resources/reader/pdf" + val sparkNLPReader = new SparkNLPReader() + val pdfDf = sparkNLPReader.pdf(pdfPath) + + assert(!pdfDf.select(col("pdf").getItem(0)).isEmpty) + } + + it should "throw an IllegalArgumentException for an invalid file path" taggedAs FastTest in { + val pdfPath = "src/test/resources/reader/pdf/invalid" + val sparkNLPReader = new SparkNLPReader() + + assertThrows[IllegalArgumentException] { + sparkNLPReader.pdf(pdfPath) + } + } + + it should "read a PDF file and return a structured Dataframe with spark-ocr schema" taggedAs FastTest in { + val pdfPath = "src/test/resources/reader/pdf" + val params = new java.util.HashMap[String, String]() + params.put("schemaOutput", "spark-ocr") + + val sparkNLPReader = new SparkNLPReader(params) + val pdfDf = sparkNLPReader.pdf(pdfPath) + + assert(pdfDf.count() > 0) + } + +} From f3583d1206365ae80ca8e34e7b8967320b31dcdf Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 15 Jan 2025 16:21:48 -0500 Subject: [PATCH 010/108] [SPARKNLP-1098] Adding docs and notebook example for PDF reader --- .../reader/SparkNLP_PDF_Reader_Demo.ipynb | 211 ++++++++++++++++++ python/sparknlp/reader/sparknlp_reader.py | 34 +++ .../johnsnowlabs/reader/SparkNLPReader.scala | 43 ++++ .../reader/SparkNLPReaderTest.scala | 21 -- 4 files changed, 288 insertions(+), 21 deletions(-) create mode 100644 examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb diff --git a/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb new file mode 100644 index 00000000000000..34e96c227cd328 --- /dev/null +++ b/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Introducing PDF reader in SparkNLP\n", + "This notebook showcases the newly added `sparknlp.read().pdf()` method in Spark NLP that parses PDF content from both local files and distributed file systems into a Spark DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "DczWop6QeE8F", + "outputId": "ceb0e598-4c62-475d-fe65-74eb7d737652" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Apache Spark version: 3.5.3\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n", + "\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading pdf files was introduced in Spark NLP 5.6.0 Please make sure you have upgraded to the latest Spark NLP release." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For local files example we will download a couple of PDF files from Spark NLP Github repo:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "id": "ya8qZe00dalC" + }, + "outputs": [], + "source": [ + "!mkdir pdf-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/pdf/pdf-title.pdf -P pdf-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/pdf/text_3_pages.pdf -P pdf-files" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EoFI66NAdalE" + }, + "source": [ + "## Parsing PDFs from Local Files\n", + "Use the `pdf()` method to parse Excel content from local directories." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bAkMjJ1vdalE", + "outputId": "db995ee4-16fc-483a-eb89-c05b7cb5c863" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", + "| path| modificationTime|length| text|height_dimension|width_dimension| content|exception|pagenum|\n", + "+--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", + "|file:/content/pdf...|2025-01-15 20:48:...| 25803|This is a Title \\...| 842| 596|[25 50 44 46 2D 3...| NULL| 0|\n", + "|file:/content/pdf...|2025-01-15 20:48:...| 9487|This is a page.\\n...| 841| 595|[25 50 44 46 2D 3...| NULL| 0|\n", + "+--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", + "\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "pdf_df = sparknlp.read().pdf(\"./pdf-examples\")\n", + "\n", + "pdf_df.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "VWbUgoVQrO8m", + "outputId": "7bbc1f6e-9188-4c42-c3fb-126198e812a5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- path: string (nullable = true)\n", + " |-- modificationTime: timestamp (nullable = true)\n", + " |-- length: long (nullable = true)\n", + " |-- text: string (nullable = true)\n", + " |-- height_dimension: integer (nullable = true)\n", + " |-- width_dimension: integer (nullable = true)\n", + " |-- content: binary (nullable = true)\n", + " |-- exception: string (nullable = true)\n", + " |-- pagenum: integer (nullable = true)\n", + "\n" + ] + } + ], + "source": [ + "pdf_df.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BB2FEfegGuxl" + }, + "source": [ + "You can also use DFS file systems like:\n", + "- Databricks: `dbfs://`\n", + "- HDFS: `hdfs://`\n", + "- Microsoft Fabric OneLake: `abfss://`" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 13ad4787739b2c..183845ffdf0110 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -91,6 +91,40 @@ class SparkNLPReader(ExtendedJavaWrapper): | | | |-- key: string | | | |-- value: string (valueContainsNull = true) + + Instantiates class to read PDF files. + + pdfPath: this is a path to a directory of PDF files or a path to an PDF file E.g. + "path/pdfs/" + + Examples + -------- + >>> from sparknlp.reader import SparkNLPReader + >>> pdf_df = SparkNLPReader().pdf(spark, "home/user/pdfs-directory") + + You can use SparkNLP for one line of code + >>> import sparknlp + >>> pdf_df = sparknlp.read().pdf("home/user/pdfs-directory") + >>> pdf_df.show(truncate=False) + + +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + | path| modificationTime|length| text|height_dimension|width_dimension| content|exception|pagenum| + +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + |file:/content/pdf...|2025-01-15 20:48:...| 25803|This is a Title \...| 842| 596|[25 50 44 46 2D 3...| NULL| 0| + |file:/content/pdf...|2025-01-15 20:48:...| 9487|This is a page.\n...| 841| 595|[25 50 44 46 2D 3...| NULL| 0| + +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + + pdf_df.printSchema() + root + |-- path: string (nullable = true) + |-- modificationTime: timestamp (nullable = true) + |-- length: long (nullable = true) + |-- text: string (nullable = true) + |-- height_dimension: integer (nullable = true) + |-- width_dimension: integer (nullable = true) + |-- content: binary (nullable = true) + |-- exception: string (nullable = true) + |-- pagenum: integer (nullable = true) """ def __init__(self, spark, params=None): diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 3b2ad518204147..ab376eb36dad03 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -158,6 +158,49 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM wordReader.doc(docPath) } + /** Instantiates class to read PDF files. + * + * pdfPath: this is a path to a directory of PDF files or a path to an PDF file E.g. + * "path/pdfs/" + * + * ==Example== + * {{{ + * val pdfsPath = "home/user/pdfs-directory" + * val sparkNLPReader = new SparkNLPReader() + * val pdfDf = sparkNLPReader.pdf(pdfsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val pdfDf = SparkNLP.read.pdf(pdfsPath) + * }}} + * + * {{{ + * pdfDf.show(false) + * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + * | path| modificationTime|length| text|height_dimension|width_dimension| content|exception|pagenum| + * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + * |file:/content/pdf...|2025-01-15 20:48:...| 25803|This is a Title \...| 842| 596|[25 50 44 46 2D 3...| NULL| 0| + * |file:/content/pdf...|2025-01-15 20:48:...| 9487|This is a page.\n...| 841| 595|[25 50 44 46 2D 3...| NULL| 0| + * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + * + * pdf_df.printSchema() + * root + * |-- path: string (nullable = true) + * |-- modificationTime: timestamp (nullable = true) + * |-- length: long (nullable = true) + * |-- text: string (nullable = true) + * |-- height_dimension: integer (nullable = true) + * |-- width_dimension: integer (nullable = true) + * |-- content: binary (nullable = true) + * |-- exception: string (nullable = true) + * |-- pagenum: integer (nullable = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ def pdf(pdfPath: String): DataFrame = { val spark = ResourceHelper.spark spark.conf.set("spark.sql.legacy.allowUntypedScalaUDF", "true") diff --git a/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala index 37df1c77f059d9..5bb09acc4519f7 100644 --- a/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala @@ -16,7 +16,6 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.tags.FastTest -import org.apache.spark.sql.functions.col import org.scalatest.flatspec.AnyFlatSpec class SparkNLPReaderTest extends AnyFlatSpec { @@ -26,26 +25,6 @@ class SparkNLPReaderTest extends AnyFlatSpec { val sparkNLPReader = new SparkNLPReader() val pdfDf = sparkNLPReader.pdf(pdfPath) - assert(!pdfDf.select(col("pdf").getItem(0)).isEmpty) - } - - it should "throw an IllegalArgumentException for an invalid file path" taggedAs FastTest in { - val pdfPath = "src/test/resources/reader/pdf/invalid" - val sparkNLPReader = new SparkNLPReader() - - assertThrows[IllegalArgumentException] { - sparkNLPReader.pdf(pdfPath) - } - } - - it should "read a PDF file and return a structured Dataframe with spark-ocr schema" taggedAs FastTest in { - val pdfPath = "src/test/resources/reader/pdf" - val params = new java.util.HashMap[String, String]() - params.put("schemaOutput", "spark-ocr") - - val sparkNLPReader = new SparkNLPReader(params) - val pdfDf = sparkNLPReader.pdf(pdfPath) - assert(pdfDf.count() > 0) } From d7be7f067afe714598a2a4f6821140bbffdd173f Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Sat, 18 Jan 2025 16:43:26 +0100 Subject: [PATCH 011/108] Merge release/600 --- .../com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala index 3caf4bdc0e8be2..79bb1000685515 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala @@ -120,6 +120,8 @@ class AutoGGUFModel(override val uid: String) with HasLlamaCppInferenceProperties with HasProtectedParams { + private val logger = LoggerFactory.getLogger(this.getClass) + override val outputAnnotatorType: AnnotatorType = AnnotatorType.DOCUMENT override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT) From 208bb754dcf2f3e185d95dd45f905e09434b47a1 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 25 Oct 2024 17:25:48 +0200 Subject: [PATCH 012/108] Refactor automatic gpu support --- .../com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala index 79bb1000685515..3caf4bdc0e8be2 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala @@ -120,8 +120,6 @@ class AutoGGUFModel(override val uid: String) with HasLlamaCppInferenceProperties with HasProtectedParams { - private val logger = LoggerFactory.getLogger(this.getClass) - override val outputAnnotatorType: AnnotatorType = AnnotatorType.DOCUMENT override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT) From e5c24f53798f3331aacb6b363a97758f3c9029e3 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Sat, 14 Dec 2024 17:05:24 +0100 Subject: [PATCH 013/108] [SPARKNLP-1079] AutoGGUFVisionModel Scala Side --- .../johnsnowlabs/ml/gguf/GGUFWrapper.scala | 4 + .../ml/gguf/GGUFWrapperMultiModal.scala | 149 ++++++++ .../com/johnsnowlabs/nlp/AnnotatorModel.scala | 17 + .../nlp/HasBatchedAnnotateTextImage.scala | 98 ++++++ .../com/johnsnowlabs/nlp/ImageAssembler.scala | 50 ++- .../com/johnsnowlabs/nlp/annotator.scala | 4 + .../annotators/cv/util/io/ImageIOUtils.scala | 9 +- .../seq2seq/AutoGGUFVisionModel.scala | 325 ++++++++++++++++++ .../nlp/pretrained/ResourceDownloader.scala | 5 +- .../seq2seq/AutoGGUFVisionModelTestSpec.scala | 124 +++++++ 10 files changed, 776 insertions(+), 9 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapperMultiModal.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/HasBatchedAnnotateTextImage.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala diff --git a/src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapper.scala index ef7091c3b5cd12..6f68ead3a51ef0 100644 --- a/src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapper.scala +++ b/src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapper.scala @@ -77,6 +77,7 @@ object GGUFWrapper { new LlamaModel(modelParameters) } + /** Reads the GGUF model from file during loadSavedModel. */ def read(sparkSession: SparkSession, modelPath: String): GGUFWrapper = { // TODO Better Sanity Check val modelFile = new File(modelPath) @@ -92,6 +93,9 @@ object GGUFWrapper { new GGUFWrapper(modelFile.getName, modelFile.getParent) } + /** Reads the GGUF model from the folder passed by the Spark Reader during loading of a + * serialized model. + */ def readModel(modelFolderPath: String, spark: SparkSession): GGUFWrapper = { def findGGUFModelInFolder(folderPath: String): String = { val folder = new File(folderPath) diff --git a/src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapperMultiModal.scala b/src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapperMultiModal.scala new file mode 100644 index 00000000000000..89eb8f517360f2 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/ml/gguf/GGUFWrapperMultiModal.scala @@ -0,0 +1,149 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.ml.gguf + +import com.johnsnowlabs.nlp.llama.{LlamaModel, ModelParameters} +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import org.apache.hadoop.fs.{FileSystem, Path} +import org.apache.spark.SparkFiles +import org.apache.spark.sql.SparkSession + +import java.io.File +import java.nio.file.{Files, Paths} + +class GGUFWrapperMultiModal(var modelFileName: String, var mmprojFileName: String) + extends Serializable { + + /** For Deserialization */ + def this() = { + this(null, null) + } + + // Important for serialization on none-kryo serializers + @transient private var llamaModel: LlamaModel = _ + + def getSession(modelParameters: ModelParameters): LlamaModel = + this.synchronized { + if (llamaModel == null) { + val modelFilePath = SparkFiles.get(modelFileName) + val mmprojFilePath = SparkFiles.get(mmprojFileName) + val filesExist = + Paths.get(modelFilePath).toFile.exists() && Paths.get(mmprojFilePath).toFile.exists() + + if (filesExist) { + modelParameters.setModelFilePath(modelFilePath) + modelParameters.setMMProj(mmprojFilePath) + llamaModel = GGUFWrapperMultiModal.withSafeGGUFModelLoader(modelParameters) + } else + throw new IllegalStateException( + s"Model file $modelFileName does not exist in SparkFiles.") + } + // TODO: if the model is already loaded then the model parameters will not apply. perhaps output a logline here. + llamaModel + } + + def saveToFile(folder: String): Unit = { + val modelFilePath = SparkFiles.get(modelFileName) + val mmprojFilePath = SparkFiles.get(mmprojFileName) + val modelOutputPath = Paths.get(folder, modelFileName) + val mmprojOutputPath = Paths.get(folder, mmprojFileName) + Files.copy(Paths.get(modelFilePath), modelOutputPath) + Files.copy(Paths.get(mmprojFilePath), mmprojOutputPath) + } + + // Destructor to free the model when this object is garbage collected + override def finalize(): Unit = { + if (llamaModel != null) { + llamaModel.close() + } + } + +} + +/** Companion object */ +object GGUFWrapperMultiModal { + private def withSafeGGUFModelLoader(modelParameters: ModelParameters): LlamaModel = + this.synchronized { + new LlamaModel(modelParameters) + } + + /** Reads the GGUF model from file during loadSavedModel. */ + def read( + sparkSession: SparkSession, + modelPath: String, + mmprojPath: String): GGUFWrapperMultiModal = { + val modelFile = new File(modelPath) + val mmprojFile = new File(mmprojPath) + + if (!modelFile.getName.endsWith(".gguf")) + throw new IllegalArgumentException(s"Model file $modelPath is not a GGUF model file") + + if (!mmprojFile.getName.endsWith(".gguf")) + throw new IllegalArgumentException(s"mmproj file $mmprojPath is not a GGUF model file") + + if (!mmprojFile.getName.contains("mmproj")) + throw new IllegalArgumentException( + s"mmproj file $mmprojPath is not a GGUF mmproj file (should contain 'mmproj' in its name)") + + if (modelFile.exists() && mmprojFile.exists()) { + sparkSession.sparkContext.addFile(modelPath) + sparkSession.sparkContext.addFile(mmprojPath) + } else + throw new IllegalArgumentException( + s"Model file $modelPath or mmproj file $mmprojPath does not exist") + + new GGUFWrapperMultiModal(modelFile.getName, mmprojFile.getName) + } + + /** Reads the GGUF model from the folder passed by the Spark Reader during loading of a + * serialized model. + */ + def readModel(modelFolderPath: String, spark: SparkSession): GGUFWrapperMultiModal = { + def findGGUFModelsInFolder(folderPath: String): (String, String) = { + val folder = new File(folderPath) + if (folder.exists && folder.isDirectory) { + val ggufFiles: Array[String] = folder.listFiles + .filter(_.isFile) + .filter(_.getName.endsWith(".gguf")) + .map(_.getAbsolutePath) + + val (ggufMainPath, ggufMmprojPath) = + if (ggufFiles.length == 2 && ggufFiles.exists(_.contains("mmproj"))) { + val Array(firstModel, secondModel) = ggufFiles + if (firstModel.contains("mmproj")) (secondModel, firstModel) + else (firstModel, secondModel) + } else + throw new IllegalArgumentException( + s"Could not determine main GGUF model or mmproj GGUF model in $folderPath." + + s" The folder should contain exactly two files:" + + s" One main GGUF model and one mmproj GGUF model." + + s" The mmproj model should have 'mmproj' in its name.") + + (ggufMainPath, ggufMmprojPath) + } else { + throw new IllegalArgumentException(s"Path $folderPath is not a directory") + } + } + + val uri = new java.net.URI(modelFolderPath.replaceAllLiterally("\\", "/")) + // In case the path belongs to a different file system but doesn't have the scheme prepended (e.g. dbfs) + val fileSystem: FileSystem = FileSystem.get(uri, spark.sparkContext.hadoopConfiguration) + val actualFolderPath = fileSystem.resolvePath(new Path(modelFolderPath)).toString + val localFolder = ResourceHelper.copyToLocal(actualFolderPath) + val (ggufMainPath, ggufMmprojPath) = findGGUFModelsInFolder(localFolder) + read(spark, ggufMainPath, ggufMmprojPath) + } +} diff --git a/src/main/scala/com/johnsnowlabs/nlp/AnnotatorModel.scala b/src/main/scala/com/johnsnowlabs/nlp/AnnotatorModel.scala index 1a350c750fc958..e1e75926a89ffa 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/AnnotatorModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/AnnotatorModel.scala @@ -111,6 +111,23 @@ abstract class AnnotatorModel[M <: Model[M]] extends RawAnnotator[M] with CanBeL }) .withColumn(getOutputCol, wrapColumnMetadata(col(getOutputCol))) dfWithMetadata + + case withBatchAnnotateTextImage: HasBatchedAnnotateTextImage[M] => + implicit val encoder: ExpressionEncoder[Row] = + SparkNlpConfig.getEncoder(inputDataset, newStructType) + val processedDataFrame = inputDataset.mapPartitions(partition => { + withBatchAnnotateTextImage.batchProcess(partition) + }) + + // TODO: Do we really need to repeat this in every case? + /** Put back column metadata from `inputDataset` after destructive mapPartitions */ + val dfWithMetadata = inputDataset.schema.fields + .foldLeft(processedDataFrame)((dataFrame, field) => { + dataFrame + .withColumn(field.name, dataFrame.col(field.name).as(field.name, field.metadata)) + }) + .withColumn(getOutputCol, wrapColumnMetadata(col(getOutputCol))) + dfWithMetadata } } diff --git a/src/main/scala/com/johnsnowlabs/nlp/HasBatchedAnnotateTextImage.scala b/src/main/scala/com/johnsnowlabs/nlp/HasBatchedAnnotateTextImage.scala new file mode 100644 index 00000000000000..6881e74dd12510 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/HasBatchedAnnotateTextImage.scala @@ -0,0 +1,98 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp + +import org.apache.spark.ml.Model +import org.apache.spark.ml.param.IntParam +import org.apache.spark.sql.Row + +trait HasBatchedAnnotateTextImage[M <: Model[M]] { + + this: RawAnnotator[M] => + + /** Size of every batch (Default depends on model). + * + * @group param + */ + val batchSize = new IntParam(this, "batchSize", "Size of every batch.") + + /** Size of every batch. + * + * @group setParam + */ + def setBatchSize(size: Int): this.type = { + val recommended = size + require(recommended > 0, "batchSize must be greater than 0") + set(this.batchSize, recommended) + } + + /** Size of every batch. + * + * @group getParam + */ + def getBatchSize: Int = $(batchSize) + + private def getCaptionImageAnnotations(row: Row): (Annotation, AnnotationImage) = { + require( + getInputCols.length == 2, + "Only two input columns are allowed for this annotator:" + + " One for text caption and one for image.") + + // Assuming we only have one annotation per field + val inputAnnotations: Array[Row] = + getInputCols.map(row.fieldIndex).map(i => row.getAs[Seq[Row]](i).head) + + val (documentStruct: Row, imageStruct: Row) = + if (inputAnnotations.head.getString(0) == AnnotatorType.DOCUMENT) { + (inputAnnotations.head, inputAnnotations.last) + } else { + (inputAnnotations.last, inputAnnotations.head) + } + + val document = Annotation(documentStruct) + val image = AnnotationImage(imageStruct) + (document, image) + } + + def batchProcess(rows: Iterator[_]): Iterator[Row] = { + rows + .grouped(getBatchSize) + .flatMap { case batchedRows: Seq[Row] => + val inputAnnotations: Seq[(Annotation, AnnotationImage)] = + batchedRows.map(getCaptionImageAnnotations) + val outputAnnotations = batchAnnotate(inputAnnotations) + + batchedRows.zip(outputAnnotations).map { case (row, annotations) => + row.toSeq ++ Array(annotations.map(a => Row(a.productIterator.toSeq: _*))) + } + } + .map(Row.fromSeq) + } + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + */ + def batchAnnotate(batchedAnnotations: Seq[(Annotation, AnnotationImage)]): Seq[Seq[Annotation]] + +} diff --git a/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala b/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala index 73b08bae40d695..3789a0e4299d4d 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala @@ -22,9 +22,9 @@ import org.apache.spark.ml.Transformer import org.apache.spark.ml.param.{Param, ParamMap} import org.apache.spark.ml.util.{DefaultParamsReadable, DefaultParamsWritable, Identifiable} import org.apache.spark.sql.expressions.UserDefinedFunction -import org.apache.spark.sql.functions.udf +import org.apache.spark.sql.functions.{col, regexp_replace, udf} import org.apache.spark.sql.types._ -import org.apache.spark.sql.{DataFrame, Dataset} +import org.apache.spark.sql.{DataFrame, Dataset, SparkSession} /** Prepares images read by Spark into a format that is processable by Spark NLP. This component * is needed to process images. @@ -213,4 +213,48 @@ private[nlp] case class ImageFields( /** This is the companion object of [[ImageAssembler]]. Please refer to that class for the * documentation. */ -object ImageAssembler extends DefaultParamsReadable[ImageAssembler] +object ImageAssembler extends DefaultParamsReadable[ImageAssembler] { + + /** Helper function that loads images from a path and returns them as raw bytes, instead of the + * default OpenCV compatible format. + * + * Supported image types are JPEG, PNG, GIF, BMP (limited to images supported by stb_image.h). + * + * Multimodal inference with llama.cpp requires raw bytes as input. + * + * @param spark + * The SparkSession + * @param path + * The path to the images. Supported image types are JPEG, PNG, GIF, BMP. + * @return + * A dataframe with the images as raw bytes, as well as their metadata. + */ + def loadImagesAsBytes(spark: SparkSession, path: String): DataFrame = { + val data: DataFrame = + spark.read + .format("image") + .option("dropInvalid", value = true) + .load(path) + + val imageBytes: DataFrame = + spark.read + .format("binaryFile") + .option("pathGlobFilter", "*.{jpeg,jpg,png,gif,bmp,JPEG,JPG,PNG,GIF,BMP}") + .option("dropInvalid", value = true) + .load(path) + .withColumn( + "path", + regexp_replace(col("path"), ":/", ":///") + ) // Paths are different for binary and image + + // Join on path + val dfJoined = + data.join(imageBytes, data("image.origin") === imageBytes("path"), "inner") + + // Replace image column data with image bytes + val dfImageReplaced = + dfJoined.withColumn("image", dfJoined("image").withField("data", dfJoined("content"))) + + dfImageReplaced + } +} diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotator.scala b/src/main/scala/com/johnsnowlabs/nlp/annotator.scala index efbd3a288896c1..e88f5feaa9fb01 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotator.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotator.scala @@ -833,4 +833,8 @@ package object annotator { extends ReadablePretrainedAutoGGUFEmbeddings with ReadAutoGGUFEmbeddings + type AutoGGUFVisionModel = com.johnsnowlabs.nlp.annotators.seq2seq.AutoGGUFVisionModel + object AutoGGUFVisionModel + extends ReadablePretrainedAutoGGUFVisionModel + with ReadAutoGGUFVisionModel } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala index ca5be6ba37dfdb..5bdafeca1b29e3 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala @@ -67,20 +67,18 @@ private[johnsnowlabs] object ImageIOUtils { def readImage(file: File): Option[BufferedImage] = { Try(ImageIO.read(file)) match { case Success(bufferedImage) => Some(bufferedImage) - case Failure(_) => { + case Failure(_) => logger.warn(s"Error in ImageIOUtils.readImage while reading file: ${file.getPath}") None - } } } def readImage(inputStream: InputStream): Option[BufferedImage] = { Try(ImageIO.read(inputStream)) match { case Success(bufferedImage) => Some(bufferedImage) - case Failure(_) => { + case Failure(_) => logger.warn(s"Error in ImageIOUtils.readImage while reading inputStream") None - } } } @@ -203,4 +201,7 @@ private[johnsnowlabs] object ImageIOUtils { } + def encodeImageBase64(image: Array[Byte]): String = + java.util.Base64.getEncoder.encodeToString(image) + } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala new file mode 100644 index 00000000000000..dbce009f356397 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala @@ -0,0 +1,325 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.seq2seq + +import com.johnsnowlabs.ml.gguf.GGUFWrapperMultiModal +import com.johnsnowlabs.ml.util.LlamaCPP +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils +import com.johnsnowlabs.nlp.llama.{LlamaException, LlamaModel} +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +/** Multimodal annotator that uses the llama.cpp library to generate text completions with large + * language models. It supports ingesting images for captioning. + * + * For settable parameters, and their explanations, see [[HasLlamaCppInferenceProperties]], + * [[HasLlamaCppModelProperties]] and refer to the llama.cpp documentation of + * [[https://github.com/ggerganov/llama.cpp/tree/7d5e8777ae1d21af99d4f95be10db4870720da91/examples/server server.cpp]] + * for more information. + * + * If the parameters are not set, the annotator will default to use the parameters provided by + * the model. + * + * This annotator expects a column of annotator type [[AnnotationImage]] for the image and + * [[Annotation]] for the caption. Note that the image bytes in the image annotation need to be + * raw image bytes without preprocessing. We provide the helper function + * [[ImageAssembler.loadImagesAsBytes]] to load the image bytes from a directory. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val autoGGUFModel = AutoGGUFModel.pretrained() + * .setInputCols("image', "document") + * .setOutputCol("completions") + * }}} + * The default model is `"llava_v1.5_7b_Q4_0_gguf"`, if no name is provided. + * + * For available pretrained models please see the [[https://sparknlp.org/models Models Hub]]. + * + * For extended examples of usage, see the + * [[https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTest.scala AutoGGUFVisionModelTest]] + * and the + * [[https://github.com/JohnSnowLabs/spark-nlp/tree/master/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFModel.ipynb example notebook]]. + * + * ==Note== + * To use GPU inference with this annotator, make sure to use the Spark NLP GPU package and set + * the number of GPU layers with the `setNGpuLayers` method. + * + * When using larger models, we recommend adjusting GPU usage with `setNCtx` and `setNGpuLayers` + * according to your hardware to avoid out-of-memory errors. + * + * ==Example== + * + * {{{ + * import com.johnsnowlabs.nlp.ImageAssembler + * import com.johnsnowlabs.nlp.annotator._ + * import com.johnsnowlabs.nlp.base._ + * import org.apache.spark.ml.Pipeline + * import org.apache.spark.sql.DataFrame + * import org.apache.spark.sql.functions.lit + * + * val documentAssembler = new DocumentAssembler() + * .setInputCol("caption") + * .setOutputCol("caption_document") + * + * val imageAssembler = new ImageAssembler() + * .setInputCol("image") + * .setOutputCol("image_assembler") + * + * val imagesPath = "src/test/resources/image/" + * val data: DataFrame = ImageAssembler + * .loadImagesAsBytes(ResourceHelper.spark, imagesPath) + * .withColumn("caption", lit("Caption this image.")) // Add a caption to each image. + * + * val nPredict = 40 + * val model = AutoGGUFVisionModel.pretrained() + * .setInputCols("caption_document", "image_assembler") + * .setOutputCol("completions") + * .setChatTemplate("vicuna") // llava uses vicuna as default + * .setBatchSize(4) + * .setNGpuLayers(99) + * .setNCtx(4096) + * .setMinKeep(0) + * .setMinP(0.05f) + * .setNPredict(nPredict) + * .setNProbs(0) + * .setPenalizeNl(false) + * .setRepeatLastN(256) + * .setRepeatPenalty(1.18f) + * .setStopStrings(Array("", "Llama:", "User:")) + * .setTemperature(0.05f) + * .setTfsZ(1) + * .setTypicalP(1) + * .setTopK(40) + * .setTopP(0.95f) + * + * val pipeline = new Pipeline().setStages(Array(documentAssembler, imageAssembler, model)) + * pipeline + * .fit(data) + * .transform(data) + * .selectExpr("reverse(split(image.origin, '/'))[0] as image_name", "completions.result") + * .show(truncate = false) + * +-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |image_name |result | + * +-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |palace.JPEG |[ The image depicts a large, ornate room with high ceilings and beautifully decorated walls. There are several chairs placed throughout the space, some of which have cushions] | + * |egyptian_cat.jpeg|[ The image features two cats lying on a pink surface, possibly a bed or sofa. One cat is positioned towards the left side of the scene and appears to be sleeping while holding] | + * |hippopotamus.JPEG|[ A large brown hippo is swimming in a body of water, possibly an aquarium. The hippo appears to be enjoying its time in the water and seems relaxed as it floats] | + * |hen.JPEG |[ The image features a large chicken standing next to several baby chickens. In total, there are five birds in the scene: one adult and four young ones. They appear to be gathered together] | + * |ostrich.JPEG |[ The image features a large, long-necked bird standing in the grass. It appears to be an ostrich or similar species with its head held high and looking around. In addition to] | + * |junco.JPEG |[ A small bird with a black head and white chest is standing on the snow. It appears to be looking at something, possibly food or another animal in its vicinity. The scene takes place out] | + * |bluetick.jpg |[ A dog with a red collar is sitting on the floor, looking at something. The dog appears to be staring into the distance or focusing its attention on an object in front of it.] | + * |chihuahua.jpg |[ A small brown dog wearing a sweater is sitting on the floor. The dog appears to be looking at something, possibly its owner or another animal in the room. It seems comfortable and relaxed]| + * |tractor.JPEG |[ A man is sitting in the driver's seat of a green tractor, which has yellow wheels and tires. The tractor appears to be parked on top of an empty field with] | + * |ox.JPEG |[ A large bull with horns is standing in a grassy field.] | + * +-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * }}} + * + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ +class AutoGGUFVisionModel(override val uid: String) + extends AnnotatorModel[AutoGGUFVisionModel] + with HasBatchedAnnotateTextImage[AutoGGUFVisionModel] + with HasEngine + with HasLlamaCppModelProperties + with HasLlamaCppInferenceProperties + with HasProtectedParams { + + override val inputAnnotatorTypes: Array[AnnotatorType] = + Array(AnnotatorType.IMAGE, AnnotatorType.DOCUMENT) + override val outputAnnotatorType: AnnotatorType = AnnotatorType.DOCUMENT + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("AutoGGUFVisionModel")) + + private var _model: Option[Broadcast[GGUFWrapperMultiModal]] = None + + /** @group getParam */ + def getModelIfNotSet: GGUFWrapperMultiModal = _model.get.value + + /** @group setParam */ + def setModelIfNotSet(spark: SparkSession, wrapper: GGUFWrapperMultiModal): this.type = { + if (_model.isEmpty) { + _model = Some(spark.sparkContext.broadcast(wrapper)) + } + + // Entrypoint for models. Automatically set GPU support if detected. + setGpuSupportIfAvailable(spark) + this + } + + private[johnsnowlabs] def setEngine(engineName: String): this.type = set(engine, engineName) + + setDefault( + engine -> LlamaCPP.name, + useChatTemplate -> true, + nCtx -> 4096, + nBatch -> 512, + embedding -> false, + nPredict -> 100) + +// val mmproj = new Param[String]( +// this, +// "mmproj", +// "Name of the file for the multi-modal projection (mmproj) model, that encodes the images.") +// +// /** Sets the path to the multi-modal projection (mmproj) model, that encodes the images. +// * +// * Should only be used by this class and not by the user. +// * +// * @param value +// * Name of the file for the multi-modal projection (mmproj) model +// * @return +// */ +// private def setMmproj(value: String): this.type = set(mmproj, value) +// +// private def getMmproj: String = $(mmproj) + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + getModelIfNotSet.saveToFile(path) + } + + /** Completes the batch of annotations. + * + * @param batchedAnnotations + * The single batch of annotations + * @return + * Completed text sequences + * + * sentences that belong to the same original row !! (challenging) + */ + override def batchAnnotate( + batchedAnnotations: Seq[(Annotation, AnnotationImage)]): Seq[Seq[Annotation]] = { + if (batchedAnnotations.nonEmpty) { + + val modelParams = + // set parallel decoding to batch size + // TODO: might need to set this to 1 for now, as otherwise there are issues + getModelParameters.setNParallel(1) + val inferenceParams = getInferenceParameters + val model: LlamaModel = getModelIfNotSet.getSession(modelParams) + + val (prompts, base64EncodedImages) = batchedAnnotations.unzip match { + case (promptAnnotations, imageAnnotations) => + ( + promptAnnotations.map(_.result).toArray, + imageAnnotations + .map(imgAnno => ImageIOUtils.encodeImageBase64(imgAnno.result)) + .toArray) + } + + val (completedTexts: Array[String], metadata: Map[String, String]) = + try { + ( + model.requestBatchImageCompletion(prompts, base64EncodedImages, inferenceParams), + Map.empty) + } catch { + case e: LlamaException => + logger.error("Error in llama.cpp image batch completion", e) + (Array[String](), Map("LlamaException" -> e.getMessage)) + } + + val result: Seq[Seq[Annotation]] = + batchedAnnotations.zip(completedTexts).map { + case ((textAnnotation: Annotation, imageAnnotation: AnnotationImage), text) => + val totalMetadata = + textAnnotation.metadata ++ imageAnnotation.metadata ++ metadata + Seq(new Annotation(outputAnnotatorType, 0, text.length - 1, text, totalMetadata)) + } + result + } else Seq(Seq.empty[Annotation]) + } +} + +trait ReadablePretrainedAutoGGUFVisionModel + extends ParamsAndFeaturesReadable[AutoGGUFVisionModel] + with HasPretrained[AutoGGUFVisionModel] { + override val defaultModelName: Some[String] = Some("llava_v1.5_7b_Q4_0_gguf") + override val defaultLang: String = "en" + + /** Java compliant-overrides */ + override def pretrained(): AutoGGUFVisionModel = super.pretrained() + + override def pretrained(name: String): AutoGGUFVisionModel = super.pretrained(name) + + override def pretrained(name: String, lang: String): AutoGGUFVisionModel = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): AutoGGUFVisionModel = + super.pretrained(name, lang, remoteLoc) +} + +trait ReadAutoGGUFVisionModel { + this: ParamsAndFeaturesReadable[AutoGGUFVisionModel] => + + def readModel(instance: AutoGGUFVisionModel, path: String, spark: SparkSession): Unit = { + val model: GGUFWrapperMultiModal = GGUFWrapperMultiModal.readModel(path, spark) + + instance.setModelIfNotSet(spark, model) + } + + addReader(readModel) + + def loadSavedModel( + modelPath: String, + mmprojPath: String, + spark: SparkSession): AutoGGUFVisionModel = { + // TODO potentially enable download from HF-URLS + val localPathModel: String = ResourceHelper.copyToLocal(modelPath) + val localPathMmproj: String = ResourceHelper.copyToLocal(mmprojPath) + + val annotatorModel = new AutoGGUFVisionModel() + val wrapper = GGUFWrapperMultiModal.read(spark, localPathModel, localPathMmproj) + + annotatorModel + .setModelIfNotSet(spark, wrapper) + .setEngine(LlamaCPP.name) + + // TODO mmproj metadata necessary? + val metadata = LlamaModel.getMetadataFromFile(localPathModel) + if (metadata.nonEmpty) annotatorModel.setMetadata(metadata) + annotatorModel + } +} + +/** This is the companion object of [[AutoGGUFVisionModel]]. Please refer to that class for the + * documentation. + */ +object AutoGGUFVisionModel + extends ReadablePretrainedAutoGGUFVisionModel + with ReadAutoGGUFVisionModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala index 3a6e69b79c5cd1..1f17c8d711ea0c 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala @@ -691,12 +691,13 @@ object PythonResourceDownloader { "CamemBertForZeroShotClassification" -> CamemBertForZeroShotClassification, "BertForMultipleChoice" -> BertForMultipleChoice, "PromptAssembler" -> PromptAssembler, - "CPMTransformer"-> CPMTransformer, + "CPMTransformer" -> CPMTransformer, "NomicEmbeddings" -> NomicEmbeddings, "NLLBTransformer" -> NLLBTransformer, "Phi3Transformer" -> Phi3Transformer, "QwenTransformer" -> QwenTransformer, - "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings) + "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings, + "AutoGGUFVisionModel" -> AutoGGUFVisionModel) // List pairs of types such as the one with key type can load a pretrained model from the value type val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering") diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala new file mode 100644 index 00000000000000..41a600afc15255 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala @@ -0,0 +1,124 @@ +package com.johnsnowlabs.nlp.annotators.seq2seq + +import com.johnsnowlabs.nlp.base.DocumentAssembler +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.{Annotation, AnnotationImage, ImageAssembler} +import com.johnsnowlabs.tags.SlowTest +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.functions.lit +import org.apache.spark.sql.{DataFrame, Row} +import org.scalatest.flatspec.AnyFlatSpec + +import scala.collection.mutable + +class AutoGGUFVisionModelTestSpec extends AnyFlatSpec { + + behavior of "AutoGGUFVisionModel" + + lazy val documentAssembler = new DocumentAssembler() + .setInputCol("caption") + .setOutputCol("caption_document") + + lazy val imageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + + lazy val imagesPath = "src/test/resources/image/" + lazy val data: DataFrame = ImageAssembler + .loadImagesAsBytes(ResourceHelper.spark, imagesPath) + .withColumn("caption", lit("Caption this image.")) // Add a caption to each image. + + lazy val expectedWords: Map[String, String] = Map( + "bluetick.jpg" -> "dog", + "chihuahua.jpg" -> "dog", + "egyptian_cat.jpeg" -> "cat", + "hen.JPEG" -> "chick", + "hippopotamus.JPEG" -> "hippo", + "junco.JPEG" -> "bird", + "ostrich.JPEG" -> "ostrich", + "ox.JPEG" -> "bull", + "palace.JPEG" -> "room", + "tractor.JPEG" -> "tractor") + + lazy val nPredict = 40 + lazy val model = AutoGGUFVisionModel + .loadSavedModel( + "models/llava-v1.5-7b-Q4_0.gguf", + "models/llava-v1.5-7b-mmproj-model-f16.gguf", + ResourceHelper.spark) + .setInputCols("caption_document", "image_assembler") + .setOutputCol("completions") + .setChatTemplate("vicuna") // llava uses vicuna as default + .setBatchSize(4) + .setNGpuLayers(99) + .setNCtx(4096) + .setMinKeep(0) + .setMinP(0.05f) + .setNPredict(nPredict) + .setNProbs(0) + .setPenalizeNl(false) + .setRepeatLastN(256) + .setRepeatPenalty(1.18f) + .setStopStrings(Array("", "Llama:", "User:")) + .setTemperature(0.05f) + .setTfsZ(1) + .setTypicalP(1) + .setTopK(40) + .setTopP(0.95f) + + lazy val pipeline = new Pipeline().setStages(Array(documentAssembler, imageAssembler, model)) + + def checkBinaryContents(): Unit = { + val imageData = data.select("image.data").limit(1).collect()(0).getAs[Array[Byte]](0) + val byteContent = data.select("content").limit(1).collect()(0).getAs[Array[Byte]](0) + + assert(imageData.length == byteContent.length) + assert(imageData sameElements byteContent) + } + + it should "replace image data with bytes" taggedAs SlowTest in { + checkBinaryContents() + } + + it should "caption the images correctly" taggedAs SlowTest in { + import java.lang.management.ManagementFactory + val pid = ManagementFactory.getRuntimeMXBean.getName.split("@")(0) + println(s"Current PID: $pid") + + val result = pipeline.fit(data).transform(data.repartition(1)) + + val imageWithCompletions: Array[(AnnotationImage, Annotation)] = + result.select("image_assembler", "completions").collect().map { row => + val image = AnnotationImage(row.getAs[mutable.WrappedArray[Row]](0).head) + val annotation = Annotation(row.getAs[mutable.WrappedArray[Row]](1).head) + (image, annotation) + } + + imageWithCompletions.foreach { case (image, completion) => + val fileName = image.origin.split("/").last + val expectedWord = expectedWords(fileName) + val wordFound = completion.result.contains(expectedWord) + assert(wordFound, s"Expected word $expectedWord not found in $result") + } + } + + it should "be serializable" taggedAs SlowTest in { + val pipelineModel = pipeline.fit(data) + val savePath = "./tmp_autogguf_vision_model" + pipelineModel.stages.last + .asInstanceOf[AutoGGUFVisionModel] + .write + .overwrite() + .save(savePath) + + val loadedModel = AutoGGUFVisionModel.load(savePath) + val newPipeline: Pipeline = + new Pipeline().setStages(Array(documentAssembler, imageAssembler, loadedModel)) + + newPipeline + .fit(data) + .transform(data.limit(1)) + .select("completions") + .show(truncate = false) + } +} From 544f7228e314470a95a29abe67e5e55f76a17176 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Sat, 18 Jan 2025 12:08:30 +0100 Subject: [PATCH 014/108] [SPARKNLP-1079] AutoGGUFVisionModel Python Side --- python/sparknlp/annotator/seq2seq/__init__.py | 1 + .../annotator/seq2seq/auto_gguf_model.py | 511 +------------ .../seq2seq/auto_gguf_vision_model.py | 332 +++++++++ python/sparknlp/base/image_assembler.py | 50 ++ python/sparknlp/common/properties.py | 697 +++++++++++++++--- python/sparknlp/internal/__init__.py | 8 +- .../annotator/seq2seq/auto_gguf_model_test.py | 5 +- .../seq2seq/auto_gguf_vision_model_test.py | 90 +++ 8 files changed, 1091 insertions(+), 603 deletions(-) create mode 100755 python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py create mode 100644 python/test/annotator/seq2seq/auto_gguf_vision_model_test.py diff --git a/python/sparknlp/annotator/seq2seq/__init__.py b/python/sparknlp/annotator/seq2seq/__init__.py index e9c3984c21ecc1..e946fbc0e472e8 100644 --- a/python/sparknlp/annotator/seq2seq/__init__.py +++ b/python/sparknlp/annotator/seq2seq/__init__.py @@ -22,6 +22,7 @@ from sparknlp.annotator.seq2seq.phi2_transformer import * from sparknlp.annotator.seq2seq.mistral_transformer import * from sparknlp.annotator.seq2seq.auto_gguf_model import * +from sparknlp.annotator.seq2seq.auto_gguf_vision_model import * from sparknlp.annotator.seq2seq.phi3_transformer import * from sparknlp.annotator.seq2seq.nllb_transformer import * from sparknlp.annotator.seq2seq.cpm_transformer import * diff --git a/python/sparknlp/annotator/seq2seq/auto_gguf_model.py b/python/sparknlp/annotator/seq2seq/auto_gguf_model.py index d28ac006c9da22..37c96319564782 100755 --- a/python/sparknlp/annotator/seq2seq/auto_gguf_model.py +++ b/python/sparknlp/annotator/seq2seq/auto_gguf_model.py @@ -17,7 +17,7 @@ from sparknlp.common import * -class AutoGGUFModel(AnnotatorModel, HasBatchedAnnotate): +class AutoGGUFModel(AnnotatorModel, HasBatchedAnnotate, HasLlamaCppProperties): """ Annotator that uses the llama.cpp library to generate text completions with large language models. @@ -241,507 +241,6 @@ class AutoGGUFModel(AnnotatorModel, HasBatchedAnnotate): inputAnnotatorTypes = [AnnotatorType.DOCUMENT] outputAnnotatorType = AnnotatorType.DOCUMENT - # -------- MODEl PARAMETERS -------- - nThreads = Param(Params._dummy(), "nThreads", "Set the number of threads to use during generation", - typeConverter=TypeConverters.toInt) - nThreadsDraft = Param(Params._dummy(), "nThreadsDraft", "Set the number of threads to use during draft generation", - typeConverter=TypeConverters.toInt) - nThreadsBatch = Param(Params._dummy(), "nThreadsBatch", - "Set the number of threads to use during batch and prompt processing", - typeConverter=TypeConverters.toInt) - nThreadsBatchDraft = Param(Params._dummy(), "nThreadsBatchDraft", - "Set the number of threads to use during batch and prompt processing", - typeConverter=TypeConverters.toInt) - nCtx = Param(Params._dummy(), "nCtx", "Set the size of the prompt context", typeConverter=TypeConverters.toInt) - nBatch = Param(Params._dummy(), "nBatch", - "Set the logical batch size for prompt processing (must be >=32 to use BLAS)", - typeConverter=TypeConverters.toInt) - nUbatch = Param(Params._dummy(), "nUbatch", - "Set the physical batch size for prompt processing (must be >=32 to use BLAS)", - typeConverter=TypeConverters.toInt) - nDraft = Param(Params._dummy(), "nDraft", "Set the number of tokens to draft for speculative decoding", - typeConverter=TypeConverters.toInt) - nChunks = Param(Params._dummy(), "nChunks", "Set the maximal number of chunks to process", - typeConverter=TypeConverters.toInt) - nSequences = Param(Params._dummy(), "nSequences", "Set the number of sequences to decode", - typeConverter=TypeConverters.toInt) - pSplit = Param(Params._dummy(), "pSplit", "Set the speculative decoding split probability", - typeConverter=TypeConverters.toFloat) - nGpuLayers = Param(Params._dummy(), "nGpuLayers", "Set the number of layers to store in VRAM (-1 - use default)", - typeConverter=TypeConverters.toInt) - nGpuLayersDraft = Param(Params._dummy(), "nGpuLayersDraft", - "Set the number of layers to store in VRAM for the draft model (-1 - use default)", - typeConverter=TypeConverters.toInt) - # Set how to split the model across GPUs - # - # - NONE: No GPU split - # - LAYER: Split the model across GPUs by layer - # - ROW: Split the model across GPUs by rows - gpuSplitMode = Param(Params._dummy(), "gpuSplitMode", "Set how to split the model across GPUs", - typeConverter=TypeConverters.toString) - mainGpu = Param(Params._dummy(), "mainGpu", "Set the main GPU that is used for scratch and small tensors.", - typeConverter=TypeConverters.toInt) - tensorSplit = Param(Params._dummy(), "tensorSplit", "Set how split tensors should be distributed across GPUs", - typeConverter=TypeConverters.toListFloat) - grpAttnN = Param(Params._dummy(), "grpAttnN", "Set the group-attention factor", typeConverter=TypeConverters.toInt) - grpAttnW = Param(Params._dummy(), "grpAttnW", "Set the group-attention width", typeConverter=TypeConverters.toInt) - ropeFreqBase = Param(Params._dummy(), "ropeFreqBase", "Set the RoPE base frequency, used by NTK-aware scaling", - typeConverter=TypeConverters.toFloat) - ropeFreqScale = Param(Params._dummy(), "ropeFreqScale", - "Set the RoPE frequency scaling factor, expands context by a factor of 1/N", - typeConverter=TypeConverters.toFloat) - yarnExtFactor = Param(Params._dummy(), "yarnExtFactor", "Set the YaRN extrapolation mix factor", - typeConverter=TypeConverters.toFloat) - yarnAttnFactor = Param(Params._dummy(), "yarnAttnFactor", "Set the YaRN scale sqrt(t) or attention magnitude", - typeConverter=TypeConverters.toFloat) - yarnBetaFast = Param(Params._dummy(), "yarnBetaFast", "Set the YaRN low correction dim or beta", - typeConverter=TypeConverters.toFloat) - yarnBetaSlow = Param(Params._dummy(), "yarnBetaSlow", "Set the YaRN high correction dim or alpha", - typeConverter=TypeConverters.toFloat) - yarnOrigCtx = Param(Params._dummy(), "yarnOrigCtx", "Set the YaRN original context size of model", - typeConverter=TypeConverters.toInt) - defragmentationThreshold = Param(Params._dummy(), "defragmentationThreshold", - "Set the KV cache defragmentation threshold", typeConverter=TypeConverters.toFloat) - # Set optimization strategies that help on some NUMA systems (if available) - # - # Available Strategies: - # - # - DISABLED: No NUMA optimizations - # - DISTRIBUTE: Spread execution evenly over all - # - ISOLATE: Only spawn threads on CPUs on the node that execution started on - # - NUMA_CTL: Use the CPU map provided by numactl - # - MIRROR: Mirrors the model across NUMA nodes - numaStrategy = Param(Params._dummy(), "numaStrategy", - "Set optimization strategies that help on some NUMA systems (if available)", - typeConverter=TypeConverters.toString) - # Set the RoPE frequency scaling method, defaults to linear unless specified by the model. - # - # - UNSPECIFIED: Don't use any scaling - # - LINEAR: Linear scaling - # - YARN: YaRN RoPE scaling - ropeScalingType = Param(Params._dummy(), "ropeScalingType", - "Set the RoPE frequency scaling method, defaults to linear unless specified by the model", - typeConverter=TypeConverters.toString) - # Set the pooling type for embeddings, use model default if unspecified - # - # - 0 UNSPECIFIED: Don't use any pooling - # - 1 MEAN: Mean Pooling - # - 2 CLS: CLS Pooling - poolingType = Param(Params._dummy(), "poolingType", - "Set the pooling type for embeddings, use model default if unspecified", - typeConverter=TypeConverters.toString) - modelDraft = Param(Params._dummy(), "modelDraft", "Set the draft model for speculative decoding", - typeConverter=TypeConverters.toString) - modelAlias = Param(Params._dummy(), "modelAlias", "Set a model alias", typeConverter=TypeConverters.toString) - lookupCacheStaticFilePath = Param(Params._dummy(), "lookupCacheStaticFilePath", - "Set path to static lookup cache to use for lookup decoding (not updated by generation)", - typeConverter=TypeConverters.toString) - lookupCacheDynamicFilePath = Param(Params._dummy(), "lookupCacheDynamicFilePath", - "Set path to dynamic lookup cache to use for lookup decoding (updated by generation)", - typeConverter=TypeConverters.toString) - # loraAdapters = new StructFeature[Map[String, Float]](this, "loraAdapters") - embedding = Param(Params._dummy(), "embedding", "Whether to load model with embedding support", - typeConverter=TypeConverters.toBoolean) - flashAttention = Param(Params._dummy(), "flashAttention", "Whether to enable Flash Attention", - typeConverter=TypeConverters.toBoolean) - inputPrefixBos = Param(Params._dummy(), "inputPrefixBos", - "Whether to add prefix BOS to user inputs, preceding the `--in-prefix` string", - typeConverter=TypeConverters.toBoolean) - useMmap = Param(Params._dummy(), "useMmap", - "Whether to use memory-map model (faster load but may increase pageouts if not using mlock)", - typeConverter=TypeConverters.toBoolean) - useMlock = Param(Params._dummy(), "useMlock", - "Whether to force the system to keep model in RAM rather than swapping or compressing", - typeConverter=TypeConverters.toBoolean) - noKvOffload = Param(Params._dummy(), "noKvOffload", "Whether to disable KV offload", - typeConverter=TypeConverters.toBoolean) - systemPrompt = Param(Params._dummy(), "systemPrompt", "Set a system prompt to use", - typeConverter=TypeConverters.toString) - chatTemplate = Param(Params._dummy(), "chatTemplate", "The chat template to use", - typeConverter=TypeConverters.toString) - - # -------- INFERENCE PARAMETERS -------- - inputPrefix = Param(Params._dummy(), "inputPrefix", "Set the prompt to start generation with", - typeConverter=TypeConverters.toString) - inputSuffix = Param(Params._dummy(), "inputSuffix", "Set a suffix for infilling", - typeConverter=TypeConverters.toString) - cachePrompt = Param(Params._dummy(), "cachePrompt", "Whether to remember the prompt to avoid reprocessing it", - typeConverter=TypeConverters.toBoolean) - nPredict = Param(Params._dummy(), "nPredict", "Set the number of tokens to predict", - typeConverter=TypeConverters.toInt) - topK = Param(Params._dummy(), "topK", "Set top-k sampling", typeConverter=TypeConverters.toInt) - topP = Param(Params._dummy(), "topP", "Set top-p sampling", typeConverter=TypeConverters.toFloat) - minP = Param(Params._dummy(), "minP", "Set min-p sampling", typeConverter=TypeConverters.toFloat) - tfsZ = Param(Params._dummy(), "tfsZ", "Set tail free sampling, parameter z", typeConverter=TypeConverters.toFloat) - typicalP = Param(Params._dummy(), "typicalP", "Set locally typical sampling, parameter p", - typeConverter=TypeConverters.toFloat) - temperature = Param(Params._dummy(), "temperature", "Set the temperature", typeConverter=TypeConverters.toFloat) - dynamicTemperatureRange = Param(Params._dummy(), "dynatempRange", "Set the dynamic temperature range", - typeConverter=TypeConverters.toFloat) - dynamicTemperatureExponent = Param(Params._dummy(), "dynatempExponent", "Set the dynamic temperature exponent", - typeConverter=TypeConverters.toFloat) - repeatLastN = Param(Params._dummy(), "repeatLastN", "Set the last n tokens to consider for penalties", - typeConverter=TypeConverters.toInt) - repeatPenalty = Param(Params._dummy(), "repeatPenalty", "Set the penalty of repeated sequences of tokens", - typeConverter=TypeConverters.toFloat) - frequencyPenalty = Param(Params._dummy(), "frequencyPenalty", "Set the repetition alpha frequency penalty", - typeConverter=TypeConverters.toFloat) - presencePenalty = Param(Params._dummy(), "presencePenalty", "Set the repetition alpha presence penalty", - typeConverter=TypeConverters.toFloat) - miroStat = Param(Params._dummy(), "miroStat", "Set MiroStat sampling strategies.", - typeConverter=TypeConverters.toString) - miroStatTau = Param(Params._dummy(), "mirostatTau", "Set the MiroStat target entropy, parameter tau", - typeConverter=TypeConverters.toFloat) - miroStatEta = Param(Params._dummy(), "mirostatEta", "Set the MiroStat learning rate, parameter eta", - typeConverter=TypeConverters.toFloat) - penalizeNl = Param(Params._dummy(), "penalizeNl", "Whether to penalize newline tokens", - typeConverter=TypeConverters.toBoolean) - nKeep = Param(Params._dummy(), "nKeep", "Set the number of tokens to keep from the initial prompt", - typeConverter=TypeConverters.toInt) - seed = Param(Params._dummy(), "seed", "Set the RNG seed", typeConverter=TypeConverters.toInt) - nProbs = Param(Params._dummy(), "nProbs", "Set the amount top tokens probabilities to output if greater than 0.", - typeConverter=TypeConverters.toInt) - minKeep = Param(Params._dummy(), "minKeep", - "Set the amount of tokens the samplers should return at least (0 = disabled)", - typeConverter=TypeConverters.toInt) - grammar = Param(Params._dummy(), "grammar", "Set BNF-like grammar to constrain generations", - typeConverter=TypeConverters.toString) - penaltyPrompt = Param(Params._dummy(), "penaltyPrompt", - "Override which part of the prompt is penalized for repetition.", - typeConverter=TypeConverters.toString) - ignoreEos = Param(Params._dummy(), "ignoreEos", - "Set whether to ignore end of stream token and continue generating (implies --logit-bias 2-inf)", - typeConverter=TypeConverters.toBoolean) - disableTokenIds = Param(Params._dummy(), "disableTokenIds", "Set the token ids to disable in the completion", - typeConverter=TypeConverters.toListInt) - stopStrings = Param(Params._dummy(), "stopStrings", "Set strings upon seeing which token generation is stopped", - typeConverter=TypeConverters.toListString) - samplers = Param(Params._dummy(), "samplers", "Set which samplers to use for token generation in the given order", - typeConverter=TypeConverters.toListString) - useChatTemplate = Param(Params._dummy(), "useChatTemplate", - "Set whether or not generate should apply a chat template", - typeConverter=TypeConverters.toBoolean) - - # -------- MODEL SETTERS -------- - def setNThreads(self, nThreads: int): - """Set the number of threads to use during generation""" - return self._set(nThreads=nThreads) - - def setNThreadsDraft(self, nThreadsDraft: int): - """Set the number of threads to use during draft generation""" - return self._set(nThreadsDraft=nThreadsDraft) - - def setNThreadsBatch(self, nThreadsBatch: int): - """Set the number of threads to use during batch and prompt processing""" - return self._set(nThreadsBatch=nThreadsBatch) - - def setNThreadsBatchDraft(self, nThreadsBatchDraft: int): - """Set the number of threads to use during batch and prompt processing""" - return self._set(nThreadsBatchDraft=nThreadsBatchDraft) - - def setNCtx(self, nCtx: int): - """Set the size of the prompt context""" - return self._set(nCtx=nCtx) - - def setNBatch(self, nBatch: int): - """Set the logical batch size for prompt processing (must be >=32 to use BLAS)""" - return self._set(nBatch=nBatch) - - def setNUbatch(self, nUbatch: int): - """Set the physical batch size for prompt processing (must be >=32 to use BLAS)""" - return self._set(nUbatch=nUbatch) - - def setNDraft(self, nDraft: int): - """Set the number of tokens to draft for speculative decoding""" - return self._set(nDraft=nDraft) - - def setNChunks(self, nChunks: int): - """Set the maximal number of chunks to process""" - return self._set(nChunks=nChunks) - - def setNSequences(self, nSequences: int): - """Set the number of sequences to decode""" - return self._set(nSequences=nSequences) - - def setPSplit(self, pSplit: float): - """Set the speculative decoding split probability""" - return self._set(pSplit=pSplit) - - def setNGpuLayers(self, nGpuLayers: int): - """Set the number of layers to store in VRAM (-1 - use default)""" - return self._set(nGpuLayers=nGpuLayers) - - def setNGpuLayersDraft(self, nGpuLayersDraft: int): - """Set the number of layers to store in VRAM for the draft model (-1 - use default)""" - return self._set(nGpuLayersDraft=nGpuLayersDraft) - - def setGpuSplitMode(self, gpuSplitMode: str): - """Set how to split the model across GPUs""" - return self._set(gpuSplitMode=gpuSplitMode) - - def setMainGpu(self, mainGpu: int): - """Set the main GPU that is used for scratch and small tensors.""" - return self._set(mainGpu=mainGpu) - - def setTensorSplit(self, tensorSplit: List[float]): - """Set how split tensors should be distributed across GPUs""" - return self._set(tensorSplit=tensorSplit) - - def setGrpAttnN(self, grpAttnN: int): - """Set the group-attention factor""" - return self._set(grpAttnN=grpAttnN) - - def setGrpAttnW(self, grpAttnW: int): - """Set the group-attention width""" - return self._set(grpAttnW=grpAttnW) - - def setRopeFreqBase(self, ropeFreqBase: float): - """Set the RoPE base frequency, used by NTK-aware scaling""" - return self._set(ropeFreqBase=ropeFreqBase) - - def setRopeFreqScale(self, ropeFreqScale: float): - """Set the RoPE frequency scaling factor, expands context by a factor of 1/N""" - return self._set(ropeFreqScale=ropeFreqScale) - - def setYarnExtFactor(self, yarnExtFactor: float): - """Set the YaRN extrapolation mix factor""" - return self._set(yarnExtFactor=yarnExtFactor) - - def setYarnAttnFactor(self, yarnAttnFactor: float): - """Set the YaRN scale sqrt(t) or attention magnitude""" - return self._set(yarnAttnFactor=yarnAttnFactor) - - def setYarnBetaFast(self, yarnBetaFast: float): - """Set the YaRN low correction dim or beta""" - return self._set(yarnBetaFast=yarnBetaFast) - - def setYarnBetaSlow(self, yarnBetaSlow: float): - """Set the YaRN high correction dim or alpha""" - return self._set(yarnBetaSlow=yarnBetaSlow) - - def setYarnOrigCtx(self, yarnOrigCtx: int): - """Set the YaRN original context size of model""" - return self._set(yarnOrigCtx=yarnOrigCtx) - - def setDefragmentationThreshold(self, defragmentationThreshold: float): - """Set the KV cache defragmentation threshold""" - return self._set(defragmentationThreshold=defragmentationThreshold) - - def setNumaStrategy(self, numaStrategy: str): - """Set optimization strategies that help on some NUMA systems (if available)""" - numaUpper = numaStrategy.upper() - numaStrategies = ["DISABLED", "DISTRIBUTE", "ISOLATE", "NUMA_CTL", "MIRROR"] - if numaUpper not in numaStrategies: - raise ValueError( - f"Invalid NUMA strategy: {numaUpper}. " - + f"Valid values are: {numaStrategies}" - ) - return self._set(numaStrategy=numaStrategy) - - def setRopeScalingType(self, ropeScalingType: str): - """Set the RoPE frequency scaling method, defaults to linear unless specified by the model""" - return self._set(ropeScalingType=ropeScalingType) - - def setPoolingType(self, poolingType: bool): - """Set the pooling type for embeddings, use model default if unspecified""" - poolingTypeUpper = poolingType.upper() - poolingTypes = ["NONE", "MEAN", "CLS", "LAST"] - if poolingTypeUpper not in poolingTypes: - raise ValueError( - f"Invalid pooling type: {poolingType}. " - + f"Valid values are: {poolingTypes}" - ) - return self._set(poolingType=poolingType) - - def setModelDraft(self, modelDraft: str): - """Set the draft model for speculative decoding""" - return self._set(modelDraft=modelDraft) - - def setModelAlias(self, modelAlias: str): - """Set a model alias""" - return self._set(modelAlias=modelAlias) - - def setLookupCacheStaticFilePath(self, lookupCacheStaticFilePath: str): - """Set path to static lookup cache to use for lookup decoding (not updated by generation)""" - return self._set(lookupCacheStaticFilePath=lookupCacheStaticFilePath) - - def setLookupCacheDynamicFilePath(self, lookupCacheDynamicFilePath: str): - """Set path to dynamic lookup cache to use for lookup decoding (updated by generation)""" - return self._set(lookupCacheDynamicFilePath=lookupCacheDynamicFilePath) - - def setEmbedding(self, embedding: bool): - """Whether to load model with embedding support""" - return self._set(embedding=embedding) - - def setFlashAttention(self, flashAttention: bool): - """Whether to enable Flash Attention""" - return self._set(flashAttention=flashAttention) - - def setInputPrefixBos(self, inputPrefixBos: bool): - """Whether to add prefix BOS to user inputs, preceding the `--in-prefix` bool""" - return self._set(inputPrefixBos=inputPrefixBos) - - def setUseMmap(self, useMmap: bool): - """Whether to use memory-map model (faster load but may increase pageouts if not using mlock)""" - return self._set(useMmap=useMmap) - - def setUseMlock(self, useMlock: bool): - """Whether to force the system to keep model in RAM rather than swapping or compressing""" - return self._set(useMlock=useMlock) - - def setNoKvOffload(self, noKvOffload: bool): - """Whether to disable KV offload""" - return self._set(noKvOffload=noKvOffload) - - def setSystemPrompt(self, systemPrompt: bool): - """Set a system prompt to use""" - return self._set(systemPrompt=systemPrompt) - - def setChatTemplate(self, chatTemplate: str): - """The chat template to use""" - return self._set(chatTemplate=chatTemplate) - - # -------- INFERENCE SETTERS -------- - def setInputPrefix(self, inputPrefix: str): - """Set the prompt to start generation with""" - return self._set(inputPrefix=inputPrefix) - - def setInputSuffix(self, inputSuffix: str): - """Set a suffix for infilling""" - return self._set(inputSuffix=inputSuffix) - - def setCachePrompt(self, cachePrompt: bool): - """Whether to remember the prompt to avoid reprocessing it""" - return self._set(cachePrompt=cachePrompt) - - def setNPredict(self, nPredict: int): - """Set the number of tokens to predict""" - return self._set(nPredict=nPredict) - - def setTopK(self, topK: int): - """Set top-k sampling""" - return self._set(topK=topK) - - def setTopP(self, topP: float): - """Set top-p sampling""" - return self._set(topP=topP) - - def setMinP(self, minP: float): - """Set min-p sampling""" - return self._set(minP=minP) - - def setTfsZ(self, tfsZ: float): - """Set tail free sampling, parameter z""" - return self._set(tfsZ=tfsZ) - - def setTypicalP(self, typicalP: float): - """Set locally typical sampling, parameter p""" - return self._set(typicalP=typicalP) - - def setTemperature(self, temperature: float): - """Set the temperature""" - return self._set(temperature=temperature) - - def setDynamicTemperatureRange(self, dynamicTemperatureRange: float): - """Set the dynamic temperature range""" - return self._set(dynamicTemperatureRange=dynamicTemperatureRange) - - def setDynamicTemperatureExponent(self, dynamicTemperatureExponent: float): - """Set the dynamic temperature exponent""" - return self._set(dynamicTemperatureExponent=dynamicTemperatureExponent) - - def setRepeatLastN(self, repeatLastN: int): - """Set the last n tokens to consider for penalties""" - return self._set(repeatLastN=repeatLastN) - - def setRepeatPenalty(self, repeatPenalty: float): - """Set the penalty of repeated sequences of tokens""" - return self._set(repeatPenalty=repeatPenalty) - - def setFrequencyPenalty(self, frequencyPenalty: float): - """Set the repetition alpha frequency penalty""" - return self._set(frequencyPenalty=frequencyPenalty) - - def setPresencePenalty(self, presencePenalty: float): - """Set the repetition alpha presence penalty""" - return self._set(presencePenalty=presencePenalty) - - def setMiroStat(self, miroStat: str): - """Set MiroStat sampling strategies.""" - return self._set(miroStat=miroStat) - - def setMiroStatTau(self, miroStatTau: float): - """Set the MiroStat target entropy, parameter tau""" - return self._set(miroStatTau=miroStatTau) - - def setMiroStatEta(self, miroStatEta: float): - """Set the MiroStat learning rate, parameter eta""" - return self._set(miroStatEta=miroStatEta) - - def setPenalizeNl(self, penalizeNl: bool): - """Whether to penalize newline tokens""" - return self._set(penalizeNl=penalizeNl) - - def setNKeep(self, nKeep: int): - """Set the number of tokens to keep from the initial prompt""" - return self._set(nKeep=nKeep) - - def setSeed(self, seed: int): - """Set the RNG seed""" - return self._set(seed=seed) - - def setNProbs(self, nProbs: int): - """Set the amount top tokens probabilities to output if greater than 0.""" - return self._set(nProbs=nProbs) - - def setMinKeep(self, minKeep: int): - """Set the amount of tokens the samplers should return at least (0 = disabled)""" - return self._set(minKeep=minKeep) - - def setGrammar(self, grammar: bool): - """Set BNF-like grammar to constrain generations""" - return self._set(grammar=grammar) - - def setPenaltyPrompt(self, penaltyPrompt: str): - """Override which part of the prompt is penalized for repetition.""" - return self._set(penaltyPrompt=penaltyPrompt) - - def setIgnoreEos(self, ignoreEos: bool): - """Set whether to ignore end of stream token and continue generating (implies --logit-bias 2-inf)""" - return self._set(ignoreEos=ignoreEos) - - def setDisableTokenIds(self, disableTokenIds: List[int]): - """Set the token ids to disable in the completion""" - return self._set(disableTokenIds=disableTokenIds) - - def setStopStrings(self, stopStrings: List[str]): - """Set strings upon seeing which token generation is stopped""" - return self._set(stopStrings=stopStrings) - - def setSamplers(self, samplers: List[str]): - """Set which samplers to use for token generation in the given order""" - return self._set(samplers=samplers) - - def setUseChatTemplate(self, useChatTemplate: bool): - """Set whether generate should apply a chat template""" - return self._set(useChatTemplate=useChatTemplate) - - # -------- JAVA SETTERS -------- - def setTokenIdBias(self, tokenIdBias: Dict[int, float]): - """Set token id bias""" - return self._call_java("setTokenIdBias", tokenIdBias) - - def setTokenBias(self, tokenBias: Dict[str, float]): - """Set token id bias""" - return self._call_java("setTokenBias", tokenBias) - - def setLoraAdapters(self, loraAdapters: Dict[str, float]): - """Set token id bias""" - return self._call_java("setLoraAdapters", loraAdapters) - - def getMetadata(self): - """Gets the metadata of the model""" - return self._call_java("getMetadata") @keyword_only def __init__(self, classname="com.johnsnowlabs.nlp.annotators.seq2seq.AutoGGUFModel", java_model=None): @@ -749,7 +248,13 @@ def __init__(self, classname="com.johnsnowlabs.nlp.annotators.seq2seq.AutoGGUFMo classname=classname, java_model=java_model ) - # self._setDefault() + self._setDefault( + useChatTemplate=True, + nCtx=4096, + nBatch=512, + embedding=False, + nPredict=100 + ) @staticmethod def loadSavedModel(folder, spark_session): diff --git a/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py b/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py new file mode 100755 index 00000000000000..83d3a6abf29c74 --- /dev/null +++ b/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py @@ -0,0 +1,332 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains classes for the AutoGGUFVisionModel.""" +from sparknlp.common import * + + +class AutoGGUFVisionModel(AnnotatorModel, HasBatchedAnnotate, HasLlamaCppProperties): + """Multimodal annotator that uses the llama.cpp library to generate text completions with large + language models. It supports ingesting images for captioning. + + For settable parameters, and their explanations, see HasLlamaCppInferenceProperties, + HasLlamaCppModelProperties and refer to the llama.cpp documentation of + `server.cpp `__ + for more information. + + If the parameters are not set, the annotator will default to use the parameters provided by + the model. + + This annotator expects a column of annotator type AnnotationImage for the image and + Annotation for the caption. Note that the image bytes in the image annotation need to be + raw image bytes without preprocessing. We provide the helper function + ImageAssembler.loadImagesAsBytes to load the image bytes from a directory. + + Pretrained models can be loaded with ``pretrained`` of the companion object: + + .. code-block:: python + + autoGGUFModel = AutoGGUFModel.pretrained() \\ + .setInputCols(["image', "document"]) \\ + .setOutputCol("completions") + + + The default model is ``"llava_v1.5_7b_Q4_0_gguf"``, if no name is provided. + + For available pretrained models please see the `Models Hub `__. + + For extended examples of usage, see the + `AutoGGUFVisionModelTest `__ + and the + `example notebook `__. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``IMAGE, DOCUMENT`` ``DOCUMENT`` + ====================== ====================== + + Parameters + ---------- + nThreads + Set the number of threads to use during generation + nThreadsDraft + Set the number of threads to use during draft generation + nThreadsBatch + Set the number of threads to use during batch and prompt processing + nThreadsBatchDraft + Set the number of threads to use during batch and prompt processing + nCtx + Set the size of the prompt context + nBatch + Set the logical batch size for prompt processing (must be >=32 to use BLAS) + nUbatch + Set the physical batch size for prompt processing (must be >=32 to use BLAS) + nDraft + Set the number of tokens to draft for speculative decoding + nChunks + Set the maximal number of chunks to process + nSequences + Set the number of sequences to decode + pSplit + Set the speculative decoding split probability + nGpuLayers + Set the number of layers to store in VRAM (-1 - use default) + nGpuLayersDraft + Set the number of layers to store in VRAM for the draft model (-1 - use default) + gpuSplitMode + Set how to split the model across GPUs + mainGpu + Set the main GPU that is used for scratch and small tensors. + tensorSplit + Set how split tensors should be distributed across GPUs + grpAttnN + Set the group-attention factor + grpAttnW + Set the group-attention width + ropeFreqBase + Set the RoPE base frequency, used by NTK-aware scaling + ropeFreqScale + Set the RoPE frequency scaling factor, expands context by a factor of 1/N + yarnExtFactor + Set the YaRN extrapolation mix factor + yarnAttnFactor + Set the YaRN scale sqrt(t) or attention magnitude + yarnBetaFast + Set the YaRN low correction dim or beta + yarnBetaSlow + Set the YaRN high correction dim or alpha + yarnOrigCtx + Set the YaRN original context size of model + defragmentationThreshold + Set the KV cache defragmentation threshold + numaStrategy + Set optimization strategies that help on some NUMA systems (if available) + ropeScalingType + Set the RoPE frequency scaling method, defaults to linear unless specified by the model + poolingType + Set the pooling type for embeddings, use model default if unspecified + modelDraft + Set the draft model for speculative decoding + modelAlias + Set a model alias + lookupCacheStaticFilePath + Set path to static lookup cache to use for lookup decoding (not updated by generation) + lookupCacheDynamicFilePath + Set path to dynamic lookup cache to use for lookup decoding (updated by generation) + embedding + Whether to load model with embedding support + flashAttention + Whether to enable Flash Attention + inputPrefixBos + Whether to add prefix BOS to user inputs, preceding the `--in-prefix` string + useMmap + Whether to use memory-map model (faster load but may increase pageouts if not using mlock) + useMlock + Whether to force the system to keep model in RAM rather than swapping or compressing + noKvOffload + Whether to disable KV offload + systemPrompt + Set a system prompt to use + chatTemplate + The chat template to use + inputPrefix + Set the prompt to start generation with + inputSuffix + Set a suffix for infilling + cachePrompt + Whether to remember the prompt to avoid reprocessing it + nPredict + Set the number of tokens to predict + topK + Set top-k sampling + topP + Set top-p sampling + minP + Set min-p sampling + tfsZ + Set tail free sampling, parameter z + typicalP + Set locally typical sampling, parameter p + temperature + Set the temperature + dynatempRange + Set the dynamic temperature range + dynatempExponent + Set the dynamic temperature exponent + repeatLastN + Set the last n tokens to consider for penalties + repeatPenalty + Set the penalty of repeated sequences of tokens + frequencyPenalty + Set the repetition alpha frequency penalty + presencePenalty + Set the repetition alpha presence penalty + miroStat + Set MiroStat sampling strategies. + mirostatTau + Set the MiroStat target entropy, parameter tau + mirostatEta + Set the MiroStat learning rate, parameter eta + penalizeNl + Whether to penalize newline tokens + nKeep + Set the number of tokens to keep from the initial prompt + seed + Set the RNG seed + nProbs + Set the amount top tokens probabilities to output if greater than 0. + minKeep + Set the amount of tokens the samplers should return at least (0 = disabled) + grammar + Set BNF-like grammar to constrain generations + penaltyPrompt + Override which part of the prompt is penalized for repetition. + ignoreEos + Set whether to ignore end of stream token and continue generating (implies --logit-bias 2-inf) + disableTokenIds + Set the token ids to disable in the completion + stopStrings + Set strings upon seeing which token generation is stopped + samplers + Set which samplers to use for token generation in the given order + useChatTemplate + Set whether or not generate should apply a chat template + + Notes + ----- + To use GPU inference with this annotator, make sure to use the Spark NLP GPU package and set + the number of GPU layers with the `setNGpuLayers` method. + + When using larger models, we recommend adjusting GPU usage with `setNCtx` and `setNGpuLayers` + according to your hardware to avoid out-of-memory errors. + + Examples + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> from pyspark.sql.functions import lit + >>> documentAssembler = DocumentAssembler() \\ + ... .setInputCol("caption") \\ + ... .setOutputCol("caption_document") + >>> imageAssembler = ImageAssembler() \\ + ... .setInputCol("image") \\ + ... .setOutputCol("image_assembler") + >>> imagesPath = "src/test/resources/image/" + >>> data = ImageAssembler \\ + ... .loadImagesAsBytes(spark, imagesPath) \\ + ... .withColumn("caption", lit("Caption this image.")) # Add a caption to each image. + >>> nPredict = 40 + >>> model = AutoGGUFVisionModel.pretrained() \\ + ... .setInputCols(["caption_document", "image_assembler"]) \\ + ... .setOutputCol("completions") \\ + ... .setChatTemplate("vicuna") \\ + ... .setBatchSize(4) \\ + ... .setNGpuLayers(99) \\ + ... .setNCtx(4096) \\ + ... .setMinKeep(0) \\ + ... .setMinP(0.05) \\ + ... .setNPredict(nPredict) \\ + ... .setNProbs(0) \\ + ... .setPenalizeNl(False) \\ + ... .setRepeatLastN(256) \\ + ... .setRepeatPenalty(1.18) \\ + ... .setStopStrings(["", "Llama:", "User:"]) \\ + ... .setTemperature(0.05) \\ + ... .setTfsZ(1) \\ + ... .setTypicalP(1) \\ + ... .setTopK(40) \\ + ... .setTopP(0.95) + >>> pipeline = Pipeline().setStages([documentAssembler, imageAssembler, model]) + >>> pipeline.fit(data).transform(data) \\ + ... .selectExpr("reverse(split(image.origin, '/'))[0] as image_name", "completions.result") \\ + ... .show(truncate = False) + +-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |image_name |result | + +-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |palace.JPEG |[ The image depicts a large, ornate room with high ceilings and beautifully decorated walls. There are several chairs placed throughout the space, some of which have cushions] | + |egyptian_cat.jpeg|[ The image features two cats lying on a pink surface, possibly a bed or sofa. One cat is positioned towards the left side of the scene and appears to be sleeping while holding] | + |hippopotamus.JPEG|[ A large brown hippo is swimming in a body of water, possibly an aquarium. The hippo appears to be enjoying its time in the water and seems relaxed as it floats] | + |hen.JPEG |[ The image features a large chicken standing next to several baby chickens. In total, there are five birds in the scene: one adult and four young ones. They appear to be gathered together] | + |ostrich.JPEG |[ The image features a large, long-necked bird standing in the grass. It appears to be an ostrich or similar species with its head held high and looking around. In addition to] | + |junco.JPEG |[ A small bird with a black head and white chest is standing on the snow. It appears to be looking at something, possibly food or another animal in its vicinity. The scene takes place out] | + |bluetick.jpg |[ A dog with a red collar is sitting on the floor, looking at something. The dog appears to be staring into the distance or focusing its attention on an object in front of it.] | + |chihuahua.jpg |[ A small brown dog wearing a sweater is sitting on the floor. The dog appears to be looking at something, possibly its owner or another animal in the room. It seems comfortable and relaxed]| + |tractor.JPEG |[ A man is sitting in the driver's seat of a green tractor, which has yellow wheels and tires. The tractor appears to be parked on top of an empty field with] | + |ox.JPEG |[ A large bull with horns is standing in a grassy field.] | + +-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------- + """ + + name = "AutoGGUFVisionModel" + inputAnnotatorTypes = [AnnotatorType.IMAGE, AnnotatorType.DOCUMENT] + outputAnnotatorType = AnnotatorType.DOCUMENT + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.seq2seq.AutoGGUFVisionModel", java_model=None): + super(AutoGGUFVisionModel, self).__init__( + classname=classname, + java_model=java_model + ) + + self._setDefault( + useChatTemplate=True, + nCtx=4096, + nBatch=512, + embedding=False, + nPredict=100 + ) + + @staticmethod + def loadSavedModel(modelPath, mmprojPath, spark_session): + """Loads a locally saved modelPath. + + Parameters + ---------- + modelPath : str + Path to the modelPath file + mmprojPath : str + Path to the mmprojPath file + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + AutoGGUFVisionModel + The restored modelPath + """ + from sparknlp.internal import _AutoGGUFVisionLoader + jModel = _AutoGGUFVisionLoader(modelPath, mmprojPath, spark_session._jsparkSession)._java_obj + return AutoGGUFVisionModel(java_model=jModel) + + @staticmethod + def pretrained(name="llava_v1.5_7b_Q4_0_gguf", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default "llava_v1.5_7b_Q4_0_gguf" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + AutoGGUFVisionModel + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(AutoGGUFVisionModel, name, lang, remote_loc) diff --git a/python/sparknlp/base/image_assembler.py b/python/sparknlp/base/image_assembler.py index cc8a9eb8c91253..5d4194e2209739 100644 --- a/python/sparknlp/base/image_assembler.py +++ b/python/sparknlp/base/image_assembler.py @@ -15,6 +15,8 @@ from pyspark import keyword_only from pyspark.ml.param import TypeConverters, Params, Param +from pyspark.sql import SparkSession, DataFrame +from pyspark.sql.functions import regexp_replace, col from sparknlp.common import AnnotatorType from sparknlp.internal import AnnotatorTransformer @@ -112,3 +114,51 @@ def setTextCol(self, value): Name of an optional input text column """ return self._set(inputCol=value) + + @classmethod + def loadImagesAsBytes(cls, spark: SparkSession, path: str): + """ + Loads images from a given path and returns them as raw bytes, instead of the default + OpenCV-compatible format. Supported image types include JPEG, PNG, GIF, and BMP. + + Multimodal inference with llama.cpp requires raw bytes as input. + + Parameters + ---------- + spark : SparkSession + The active SparkSession. + path : str + The path to the images. Supported image types are JPEG, PNG, GIF, and BMP. + + Returns + ------- + DataFrame + A DataFrame containing the images as raw bytes along with their metadata. + """ + # Load the images as metadata with the default Spark image format + data = ( + spark.read + .format("image") + .option("dropInvalid", True) + .load(path) + ) + + # Load the images as raw binary files + image_bytes = ( + spark.read + .format("binaryFile") + .option("pathGlobFilter", "*.{jpeg,jpg,png,gif,bmp,JPEG,JPG,PNG,GIF,BMP}") + .option("dropInvalid", True) + .load(path) + .withColumn("path", regexp_replace(col("path"), ":/", ":///")) + ) + + # Join the two datasets on the file path + df_joined = data.join(image_bytes, data["image.origin"] == image_bytes["path"], "inner") + + # Replace the `data` field of the `image` column with raw bytes + df_image_replaced = df_joined.withColumn( + "image", df_joined["image"].withField("data", df_joined["content"]) + ) + + return df_image_replaced diff --git a/python/sparknlp/common/properties.py b/python/sparknlp/common/properties.py index f5d7e55cfbdccb..7930fe1228d2f5 100644 --- a/python/sparknlp/common/properties.py +++ b/python/sparknlp/common/properties.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Contains classes for Annotator properties.""" +from typing import List, Dict from pyspark.ml.param import Param, Params, TypeConverters @@ -601,133 +602,637 @@ class HasGeneratorProperties: typeConverter=TypeConverters.toInt) -def setTask(self, value): - """Sets the transformer's task, e.g. ``summarize:``. + def setTask(self, value): + """Sets the transformer's task, e.g. ``summarize:``. - Parameters - ---------- - value : str - The transformer's task - """ - return self._set(task=value) + Parameters + ---------- + value : str + The transformer's task + """ + return self._set(task=value) + + + def setMinOutputLength(self, value): + """Sets minimum length of the sequence to be generated. + + Parameters + ---------- + value : int + Minimum length of the sequence to be generated + """ + return self._set(minOutputLength=value) + + + def setMaxOutputLength(self, value): + """Sets maximum length of output text. + + Parameters + ---------- + value : int + Maximum length of output text + """ + return self._set(maxOutputLength=value) + + + def setDoSample(self, value): + """Sets whether or not to use sampling, use greedy decoding otherwise. + + Parameters + ---------- + value : bool + Whether or not to use sampling; use greedy decoding otherwise + """ + return self._set(doSample=value) + + + def setTemperature(self, value): + """Sets the value used to module the next token probabilities. + + Parameters + ---------- + value : float + The value used to module the next token probabilities + """ + return self._set(temperature=value) + + + def setTopK(self, value): + """Sets the number of highest probability vocabulary tokens to keep for + top-k-filtering. + + Parameters + ---------- + value : int + Number of highest probability vocabulary tokens to keep + """ + return self._set(topK=value) -def setMinOutputLength(self, value): - """Sets minimum length of the sequence to be generated. + def setTopP(self, value): + """Sets the top cumulative probability for vocabulary tokens. - Parameters - ---------- - value : int - Minimum length of the sequence to be generated - """ - return self._set(minOutputLength=value) + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + Parameters + ---------- + value : float + Cumulative probability for vocabulary tokens + """ + return self._set(topP=value) -def setMaxOutputLength(self, value): - """Sets maximum length of output text. - Parameters - ---------- - value : int - Maximum length of output text - """ - return self._set(maxOutputLength=value) + def setRepetitionPenalty(self, value): + """Sets the parameter for repetition penalty. 1.0 means no penalty. + Parameters + ---------- + value : float + The repetition penalty -def setDoSample(self, value): - """Sets whether or not to use sampling, use greedy decoding otherwise. + References + ---------- + See `Ctrl: A Conditional Transformer Language Model For Controllable + Generation `__ for more details. + """ + return self._set(repetitionPenalty=value) - Parameters - ---------- - value : bool - Whether or not to use sampling; use greedy decoding otherwise - """ - return self._set(doSample=value) + def setNoRepeatNgramSize(self, value): + """Sets size of n-grams that can only occur once. -def setTemperature(self, value): - """Sets the value used to module the next token probabilities. + If set to int > 0, all ngrams of that size can only occur once. - Parameters - ---------- - value : float - The value used to module the next token probabilities - """ - return self._set(temperature=value) + Parameters + ---------- + value : int + N-gram size can only occur once + """ + return self._set(noRepeatNgramSize=value) -def setTopK(self, value): - """Sets the number of highest probability vocabulary tokens to keep for - top-k-filtering. + def setBeamSize(self, value): + """Sets the number of beam size for beam search. - Parameters - ---------- - value : int - Number of highest probability vocabulary tokens to keep - """ - return self._set(topK=value) + Parameters + ---------- + value : int + Number of beam size for beam search + """ + return self._set(beamSize=value) -def setTopP(self, value): - """Sets the top cumulative probability for vocabulary tokens. + def setNReturnSequences(self, value): + """Sets the number of sequences to return from the beam search. - If set to float < 1, only the most probable tokens with probabilities - that add up to ``topP`` or higher are kept for generation. + Parameters + ---------- + value : int + Number of sequences to return + """ + return self._set(nReturnSequences=value) - Parameters - ---------- - value : float - Cumulative probability for vocabulary tokens - """ - return self._set(topP=value) +class HasLlamaCppProperties: + # -------- MODEl PARAMETERS -------- + nThreads = Param(Params._dummy(), "nThreads", "Set the number of threads to use during generation", + typeConverter=TypeConverters.toInt) + nThreadsDraft = Param(Params._dummy(), "nThreadsDraft", "Set the number of threads to use during draft generation", + typeConverter=TypeConverters.toInt) + nThreadsBatch = Param(Params._dummy(), "nThreadsBatch", + "Set the number of threads to use during batch and prompt processing", + typeConverter=TypeConverters.toInt) + nThreadsBatchDraft = Param(Params._dummy(), "nThreadsBatchDraft", + "Set the number of threads to use during batch and prompt processing", + typeConverter=TypeConverters.toInt) + nCtx = Param(Params._dummy(), "nCtx", "Set the size of the prompt context", typeConverter=TypeConverters.toInt) + nBatch = Param(Params._dummy(), "nBatch", + "Set the logical batch size for prompt processing (must be >=32 to use BLAS)", + typeConverter=TypeConverters.toInt) + nUbatch = Param(Params._dummy(), "nUbatch", + "Set the physical batch size for prompt processing (must be >=32 to use BLAS)", + typeConverter=TypeConverters.toInt) + nDraft = Param(Params._dummy(), "nDraft", "Set the number of tokens to draft for speculative decoding", + typeConverter=TypeConverters.toInt) + nChunks = Param(Params._dummy(), "nChunks", "Set the maximal number of chunks to process", + typeConverter=TypeConverters.toInt) + nSequences = Param(Params._dummy(), "nSequences", "Set the number of sequences to decode", + typeConverter=TypeConverters.toInt) + pSplit = Param(Params._dummy(), "pSplit", "Set the speculative decoding split probability", + typeConverter=TypeConverters.toFloat) + nGpuLayers = Param(Params._dummy(), "nGpuLayers", "Set the number of layers to store in VRAM (-1 - use default)", + typeConverter=TypeConverters.toInt) + nGpuLayersDraft = Param(Params._dummy(), "nGpuLayersDraft", + "Set the number of layers to store in VRAM for the draft model (-1 - use default)", + typeConverter=TypeConverters.toInt) + # Set how to split the model across GPUs + # + # - NONE: No GPU split + # - LAYER: Split the model across GPUs by layer + # - ROW: Split the model across GPUs by rows + gpuSplitMode = Param(Params._dummy(), "gpuSplitMode", "Set how to split the model across GPUs", + typeConverter=TypeConverters.toString) + mainGpu = Param(Params._dummy(), "mainGpu", "Set the main GPU that is used for scratch and small tensors.", + typeConverter=TypeConverters.toInt) + tensorSplit = Param(Params._dummy(), "tensorSplit", "Set how split tensors should be distributed across GPUs", + typeConverter=TypeConverters.toListFloat) + grpAttnN = Param(Params._dummy(), "grpAttnN", "Set the group-attention factor", typeConverter=TypeConverters.toInt) + grpAttnW = Param(Params._dummy(), "grpAttnW", "Set the group-attention width", typeConverter=TypeConverters.toInt) + ropeFreqBase = Param(Params._dummy(), "ropeFreqBase", "Set the RoPE base frequency, used by NTK-aware scaling", + typeConverter=TypeConverters.toFloat) + ropeFreqScale = Param(Params._dummy(), "ropeFreqScale", + "Set the RoPE frequency scaling factor, expands context by a factor of 1/N", + typeConverter=TypeConverters.toFloat) + yarnExtFactor = Param(Params._dummy(), "yarnExtFactor", "Set the YaRN extrapolation mix factor", + typeConverter=TypeConverters.toFloat) + yarnAttnFactor = Param(Params._dummy(), "yarnAttnFactor", "Set the YaRN scale sqrt(t) or attention magnitude", + typeConverter=TypeConverters.toFloat) + yarnBetaFast = Param(Params._dummy(), "yarnBetaFast", "Set the YaRN low correction dim or beta", + typeConverter=TypeConverters.toFloat) + yarnBetaSlow = Param(Params._dummy(), "yarnBetaSlow", "Set the YaRN high correction dim or alpha", + typeConverter=TypeConverters.toFloat) + yarnOrigCtx = Param(Params._dummy(), "yarnOrigCtx", "Set the YaRN original context size of model", + typeConverter=TypeConverters.toInt) + defragmentationThreshold = Param(Params._dummy(), "defragmentationThreshold", + "Set the KV cache defragmentation threshold", typeConverter=TypeConverters.toFloat) + # Set optimization strategies that help on some NUMA systems (if available) + # + # Available Strategies: + # + # - DISABLED: No NUMA optimizations + # - DISTRIBUTE: Spread execution evenly over all + # - ISOLATE: Only spawn threads on CPUs on the node that execution started on + # - NUMA_CTL: Use the CPU map provided by numactl + # - MIRROR: Mirrors the model across NUMA nodes + numaStrategy = Param(Params._dummy(), "numaStrategy", + "Set optimization strategies that help on some NUMA systems (if available)", + typeConverter=TypeConverters.toString) + # Set the RoPE frequency scaling method, defaults to linear unless specified by the model. + # + # - UNSPECIFIED: Don't use any scaling + # - LINEAR: Linear scaling + # - YARN: YaRN RoPE scaling + ropeScalingType = Param(Params._dummy(), "ropeScalingType", + "Set the RoPE frequency scaling method, defaults to linear unless specified by the model", + typeConverter=TypeConverters.toString) + # Set the pooling type for embeddings, use model default if unspecified + # + # - 0 NONE: Don't use any pooling + # - 1 MEAN: Mean Pooling + # - 2 CLS: CLS Pooling + poolingType = Param(Params._dummy(), "poolingType", + "Set the pooling type for embeddings, use model default if unspecified", + typeConverter=TypeConverters.toString) + modelDraft = Param(Params._dummy(), "modelDraft", "Set the draft model for speculative decoding", + typeConverter=TypeConverters.toString) + modelAlias = Param(Params._dummy(), "modelAlias", "Set a model alias", typeConverter=TypeConverters.toString) + lookupCacheStaticFilePath = Param(Params._dummy(), "lookupCacheStaticFilePath", + "Set path to static lookup cache to use for lookup decoding (not updated by generation)", + typeConverter=TypeConverters.toString) + lookupCacheDynamicFilePath = Param(Params._dummy(), "lookupCacheDynamicFilePath", + "Set path to dynamic lookup cache to use for lookup decoding (updated by generation)", + typeConverter=TypeConverters.toString) + # loraAdapters = new StructFeature[Map[String, Float]](this, "loraAdapters") + embedding = Param(Params._dummy(), "embedding", "Whether to load model with embedding support", + typeConverter=TypeConverters.toBoolean) + flashAttention = Param(Params._dummy(), "flashAttention", "Whether to enable Flash Attention", + typeConverter=TypeConverters.toBoolean) + inputPrefixBos = Param(Params._dummy(), "inputPrefixBos", + "Whether to add prefix BOS to user inputs, preceding the `--in-prefix` string", + typeConverter=TypeConverters.toBoolean) + useMmap = Param(Params._dummy(), "useMmap", + "Whether to use memory-map model (faster load but may increase pageouts if not using mlock)", + typeConverter=TypeConverters.toBoolean) + useMlock = Param(Params._dummy(), "useMlock", + "Whether to force the system to keep model in RAM rather than swapping or compressing", + typeConverter=TypeConverters.toBoolean) + noKvOffload = Param(Params._dummy(), "noKvOffload", "Whether to disable KV offload", + typeConverter=TypeConverters.toBoolean) + systemPrompt = Param(Params._dummy(), "systemPrompt", "Set a system prompt to use", + typeConverter=TypeConverters.toString) + chatTemplate = Param(Params._dummy(), "chatTemplate", "The chat template to use", + typeConverter=TypeConverters.toString) + + # -------- INFERENCE PARAMETERS -------- + inputPrefix = Param(Params._dummy(), "inputPrefix", "Set the prompt to start generation with", + typeConverter=TypeConverters.toString) + inputSuffix = Param(Params._dummy(), "inputSuffix", "Set a suffix for infilling", + typeConverter=TypeConverters.toString) + cachePrompt = Param(Params._dummy(), "cachePrompt", "Whether to remember the prompt to avoid reprocessing it", + typeConverter=TypeConverters.toBoolean) + nPredict = Param(Params._dummy(), "nPredict", "Set the number of tokens to predict", + typeConverter=TypeConverters.toInt) + topK = Param(Params._dummy(), "topK", "Set top-k sampling", typeConverter=TypeConverters.toInt) + topP = Param(Params._dummy(), "topP", "Set top-p sampling", typeConverter=TypeConverters.toFloat) + minP = Param(Params._dummy(), "minP", "Set min-p sampling", typeConverter=TypeConverters.toFloat) + tfsZ = Param(Params._dummy(), "tfsZ", "Set tail free sampling, parameter z", typeConverter=TypeConverters.toFloat) + typicalP = Param(Params._dummy(), "typicalP", "Set locally typical sampling, parameter p", + typeConverter=TypeConverters.toFloat) + temperature = Param(Params._dummy(), "temperature", "Set the temperature", typeConverter=TypeConverters.toFloat) + dynamicTemperatureRange = Param(Params._dummy(), "dynatempRange", "Set the dynamic temperature range", + typeConverter=TypeConverters.toFloat) + dynamicTemperatureExponent = Param(Params._dummy(), "dynatempExponent", "Set the dynamic temperature exponent", + typeConverter=TypeConverters.toFloat) + repeatLastN = Param(Params._dummy(), "repeatLastN", "Set the last n tokens to consider for penalties", + typeConverter=TypeConverters.toInt) + repeatPenalty = Param(Params._dummy(), "repeatPenalty", "Set the penalty of repeated sequences of tokens", + typeConverter=TypeConverters.toFloat) + frequencyPenalty = Param(Params._dummy(), "frequencyPenalty", "Set the repetition alpha frequency penalty", + typeConverter=TypeConverters.toFloat) + presencePenalty = Param(Params._dummy(), "presencePenalty", "Set the repetition alpha presence penalty", + typeConverter=TypeConverters.toFloat) + miroStat = Param(Params._dummy(), "miroStat", "Set MiroStat sampling strategies.", + typeConverter=TypeConverters.toString) + miroStatTau = Param(Params._dummy(), "mirostatTau", "Set the MiroStat target entropy, parameter tau", + typeConverter=TypeConverters.toFloat) + miroStatEta = Param(Params._dummy(), "mirostatEta", "Set the MiroStat learning rate, parameter eta", + typeConverter=TypeConverters.toFloat) + penalizeNl = Param(Params._dummy(), "penalizeNl", "Whether to penalize newline tokens", + typeConverter=TypeConverters.toBoolean) + nKeep = Param(Params._dummy(), "nKeep", "Set the number of tokens to keep from the initial prompt", + typeConverter=TypeConverters.toInt) + seed = Param(Params._dummy(), "seed", "Set the RNG seed", typeConverter=TypeConverters.toInt) + nProbs = Param(Params._dummy(), "nProbs", "Set the amount top tokens probabilities to output if greater than 0.", + typeConverter=TypeConverters.toInt) + minKeep = Param(Params._dummy(), "minKeep", + "Set the amount of tokens the samplers should return at least (0 = disabled)", + typeConverter=TypeConverters.toInt) + grammar = Param(Params._dummy(), "grammar", "Set BNF-like grammar to constrain generations", + typeConverter=TypeConverters.toString) + penaltyPrompt = Param(Params._dummy(), "penaltyPrompt", + "Override which part of the prompt is penalized for repetition.", + typeConverter=TypeConverters.toString) + ignoreEos = Param(Params._dummy(), "ignoreEos", + "Set whether to ignore end of stream token and continue generating (implies --logit-bias 2-inf)", + typeConverter=TypeConverters.toBoolean) + disableTokenIds = Param(Params._dummy(), "disableTokenIds", "Set the token ids to disable in the completion", + typeConverter=TypeConverters.toListInt) + stopStrings = Param(Params._dummy(), "stopStrings", "Set strings upon seeing which token generation is stopped", + typeConverter=TypeConverters.toListString) + samplers = Param(Params._dummy(), "samplers", "Set which samplers to use for token generation in the given order", + typeConverter=TypeConverters.toListString) + useChatTemplate = Param(Params._dummy(), "useChatTemplate", + "Set whether or not generate should apply a chat template", + typeConverter=TypeConverters.toBoolean) + + # -------- MODEL SETTERS -------- + def setNThreads(self, nThreads: int): + """Set the number of threads to use during generation""" + return self._set(nThreads=nThreads) + + def setNThreadsDraft(self, nThreadsDraft: int): + """Set the number of threads to use during draft generation""" + return self._set(nThreadsDraft=nThreadsDraft) + + def setNThreadsBatch(self, nThreadsBatch: int): + """Set the number of threads to use during batch and prompt processing""" + return self._set(nThreadsBatch=nThreadsBatch) + + def setNThreadsBatchDraft(self, nThreadsBatchDraft: int): + """Set the number of threads to use during batch and prompt processing""" + return self._set(nThreadsBatchDraft=nThreadsBatchDraft) + + def setNCtx(self, nCtx: int): + """Set the size of the prompt context""" + return self._set(nCtx=nCtx) + + def setNBatch(self, nBatch: int): + """Set the logical batch size for prompt processing (must be >=32 to use BLAS)""" + return self._set(nBatch=nBatch) + + def setNUbatch(self, nUbatch: int): + """Set the physical batch size for prompt processing (must be >=32 to use BLAS)""" + return self._set(nUbatch=nUbatch) + + def setNDraft(self, nDraft: int): + """Set the number of tokens to draft for speculative decoding""" + return self._set(nDraft=nDraft) + + def setNChunks(self, nChunks: int): + """Set the maximal number of chunks to process""" + return self._set(nChunks=nChunks) + + def setNSequences(self, nSequences: int): + """Set the number of sequences to decode""" + return self._set(nSequences=nSequences) + + def setPSplit(self, pSplit: float): + """Set the speculative decoding split probability""" + return self._set(pSplit=pSplit) + + def setNGpuLayers(self, nGpuLayers: int): + """Set the number of layers to store in VRAM (-1 - use default)""" + return self._set(nGpuLayers=nGpuLayers) + + def setNGpuLayersDraft(self, nGpuLayersDraft: int): + """Set the number of layers to store in VRAM for the draft model (-1 - use default)""" + return self._set(nGpuLayersDraft=nGpuLayersDraft) + + def setGpuSplitMode(self, gpuSplitMode: str): + """Set how to split the model across GPUs""" + return self._set(gpuSplitMode=gpuSplitMode) + + def setMainGpu(self, mainGpu: int): + """Set the main GPU that is used for scratch and small tensors.""" + return self._set(mainGpu=mainGpu) + + def setTensorSplit(self, tensorSplit: List[float]): + """Set how split tensors should be distributed across GPUs""" + return self._set(tensorSplit=tensorSplit) + + def setGrpAttnN(self, grpAttnN: int): + """Set the group-attention factor""" + return self._set(grpAttnN=grpAttnN) + + def setGrpAttnW(self, grpAttnW: int): + """Set the group-attention width""" + return self._set(grpAttnW=grpAttnW) + + def setRopeFreqBase(self, ropeFreqBase: float): + """Set the RoPE base frequency, used by NTK-aware scaling""" + return self._set(ropeFreqBase=ropeFreqBase) + + def setRopeFreqScale(self, ropeFreqScale: float): + """Set the RoPE frequency scaling factor, expands context by a factor of 1/N""" + return self._set(ropeFreqScale=ropeFreqScale) + + def setYarnExtFactor(self, yarnExtFactor: float): + """Set the YaRN extrapolation mix factor""" + return self._set(yarnExtFactor=yarnExtFactor) + + def setYarnAttnFactor(self, yarnAttnFactor: float): + """Set the YaRN scale sqrt(t) or attention magnitude""" + return self._set(yarnAttnFactor=yarnAttnFactor) + + def setYarnBetaFast(self, yarnBetaFast: float): + """Set the YaRN low correction dim or beta""" + return self._set(yarnBetaFast=yarnBetaFast) + + def setYarnBetaSlow(self, yarnBetaSlow: float): + """Set the YaRN high correction dim or alpha""" + return self._set(yarnBetaSlow=yarnBetaSlow) + + def setYarnOrigCtx(self, yarnOrigCtx: int): + """Set the YaRN original context size of model""" + return self._set(yarnOrigCtx=yarnOrigCtx) + + def setDefragmentationThreshold(self, defragmentationThreshold: float): + """Set the KV cache defragmentation threshold""" + return self._set(defragmentationThreshold=defragmentationThreshold) -def setRepetitionPenalty(self, value): - """Sets the parameter for repetition penalty. 1.0 means no penalty. + def setNumaStrategy(self, numaStrategy: str): + """Set optimization strategies that help on some NUMA systems (if available)""" + numaUpper = numaStrategy.upper() + numaStrategies = ["DISABLED", "DISTRIBUTE", "ISOLATE", "NUMA_CTL", "MIRROR"] + if numaUpper not in numaStrategies: + raise ValueError( + f"Invalid NUMA strategy: {numaUpper}. " + + f"Valid values are: {numaStrategies}" + ) + return self._set(numaStrategy=numaStrategy) + + def setRopeScalingType(self, ropeScalingType: str): + """Set the RoPE frequency scaling method, defaults to linear unless specified by the model""" + return self._set(ropeScalingType=ropeScalingType) + + def setPoolingType(self, poolingType: str): + """Set the pooling type for embeddings, use model default if unspecified""" + poolingTypeUpper = poolingType.upper() + poolingTypes = ["NONE", "MEAN", "CLS", "LAST"] + if poolingTypeUpper not in poolingTypes: + raise ValueError( + f"Invalid pooling type: {poolingType}. " + + f"Valid values are: {poolingTypes}" + ) + return self._set(poolingType=poolingType) + + def setModelDraft(self, modelDraft: str): + """Set the draft model for speculative decoding""" + return self._set(modelDraft=modelDraft) + + def setModelAlias(self, modelAlias: str): + """Set a model alias""" + return self._set(modelAlias=modelAlias) + + def setLookupCacheStaticFilePath(self, lookupCacheStaticFilePath: str): + """Set path to static lookup cache to use for lookup decoding (not updated by generation)""" + return self._set(lookupCacheStaticFilePath=lookupCacheStaticFilePath) + + def setLookupCacheDynamicFilePath(self, lookupCacheDynamicFilePath: str): + """Set path to dynamic lookup cache to use for lookup decoding (updated by generation)""" + return self._set(lookupCacheDynamicFilePath=lookupCacheDynamicFilePath) + + def setEmbedding(self, embedding: bool): + """Whether to load model with embedding support""" + return self._set(embedding=embedding) + + def setFlashAttention(self, flashAttention: bool): + """Whether to enable Flash Attention""" + return self._set(flashAttention=flashAttention) + + def setInputPrefixBos(self, inputPrefixBos: bool): + """Whether to add prefix BOS to user inputs, preceding the `--in-prefix` bool""" + return self._set(inputPrefixBos=inputPrefixBos) + + def setUseMmap(self, useMmap: bool): + """Whether to use memory-map model (faster load but may increase pageouts if not using mlock)""" + return self._set(useMmap=useMmap) + + def setUseMlock(self, useMlock: bool): + """Whether to force the system to keep model in RAM rather than swapping or compressing""" + return self._set(useMlock=useMlock) + + def setNoKvOffload(self, noKvOffload: bool): + """Whether to disable KV offload""" + return self._set(noKvOffload=noKvOffload) + + def setSystemPrompt(self, systemPrompt: bool): + """Set a system prompt to use""" + return self._set(systemPrompt=systemPrompt) + + def setChatTemplate(self, chatTemplate: str): + """The chat template to use""" + return self._set(chatTemplate=chatTemplate) + + # -------- INFERENCE SETTERS -------- + def setInputPrefix(self, inputPrefix: str): + """Set the prompt to start generation with""" + return self._set(inputPrefix=inputPrefix) - Parameters - ---------- - value : float - The repetition penalty + def setInputSuffix(self, inputSuffix: str): + """Set a suffix for infilling""" + return self._set(inputSuffix=inputSuffix) - References - ---------- - See `Ctrl: A Conditional Transformer Language Model For Controllable - Generation `__ for more details. - """ - return self._set(repetitionPenalty=value) + def setCachePrompt(self, cachePrompt: bool): + """Whether to remember the prompt to avoid reprocessing it""" + return self._set(cachePrompt=cachePrompt) + def setNPredict(self, nPredict: int): + """Set the number of tokens to predict""" + return self._set(nPredict=nPredict) -def setNoRepeatNgramSize(self, value): - """Sets size of n-grams that can only occur once. + def setTopK(self, topK: int): + """Set top-k sampling""" + return self._set(topK=topK) - If set to int > 0, all ngrams of that size can only occur once. + def setTopP(self, topP: float): + """Set top-p sampling""" + return self._set(topP=topP) - Parameters - ---------- - value : int - N-gram size can only occur once - """ - return self._set(noRepeatNgramSize=value) + def setMinP(self, minP: float): + """Set min-p sampling""" + return self._set(minP=minP) + + def setTfsZ(self, tfsZ: float): + """Set tail free sampling, parameter z""" + return self._set(tfsZ=tfsZ) + + def setTypicalP(self, typicalP: float): + """Set locally typical sampling, parameter p""" + return self._set(typicalP=typicalP) + + def setTemperature(self, temperature: float): + """Set the temperature""" + return self._set(temperature=temperature) + + def setDynamicTemperatureRange(self, dynamicTemperatureRange: float): + """Set the dynamic temperature range""" + return self._set(dynamicTemperatureRange=dynamicTemperatureRange) + + def setDynamicTemperatureExponent(self, dynamicTemperatureExponent: float): + """Set the dynamic temperature exponent""" + return self._set(dynamicTemperatureExponent=dynamicTemperatureExponent) + + def setRepeatLastN(self, repeatLastN: int): + """Set the last n tokens to consider for penalties""" + return self._set(repeatLastN=repeatLastN) + + def setRepeatPenalty(self, repeatPenalty: float): + """Set the penalty of repeated sequences of tokens""" + return self._set(repeatPenalty=repeatPenalty) + + def setFrequencyPenalty(self, frequencyPenalty: float): + """Set the repetition alpha frequency penalty""" + return self._set(frequencyPenalty=frequencyPenalty) + + def setPresencePenalty(self, presencePenalty: float): + """Set the repetition alpha presence penalty""" + return self._set(presencePenalty=presencePenalty) + + def setMiroStat(self, miroStat: str): + """Set MiroStat sampling strategies.""" + return self._set(miroStat=miroStat) + + def setMiroStatTau(self, miroStatTau: float): + """Set the MiroStat target entropy, parameter tau""" + return self._set(miroStatTau=miroStatTau) + + def setMiroStatEta(self, miroStatEta: float): + """Set the MiroStat learning rate, parameter eta""" + return self._set(miroStatEta=miroStatEta) + + def setPenalizeNl(self, penalizeNl: bool): + """Whether to penalize newline tokens""" + return self._set(penalizeNl=penalizeNl) + + def setNKeep(self, nKeep: int): + """Set the number of tokens to keep from the initial prompt""" + return self._set(nKeep=nKeep) + + def setSeed(self, seed: int): + """Set the RNG seed""" + return self._set(seed=seed) + + def setNProbs(self, nProbs: int): + """Set the amount top tokens probabilities to output if greater than 0.""" + return self._set(nProbs=nProbs) + + def setMinKeep(self, minKeep: int): + """Set the amount of tokens the samplers should return at least (0 = disabled)""" + return self._set(minKeep=minKeep) + + def setGrammar(self, grammar: bool): + """Set BNF-like grammar to constrain generations""" + return self._set(grammar=grammar) + + def setPenaltyPrompt(self, penaltyPrompt: str): + """Override which part of the prompt is penalized for repetition.""" + return self._set(penaltyPrompt=penaltyPrompt) + def setIgnoreEos(self, ignoreEos: bool): + """Set whether to ignore end of stream token and continue generating (implies --logit-bias 2-inf)""" + return self._set(ignoreEos=ignoreEos) -def setBeamSize(self, value): - """Sets the number of beam size for beam search. + def setDisableTokenIds(self, disableTokenIds: List[int]): + """Set the token ids to disable in the completion""" + return self._set(disableTokenIds=disableTokenIds) - Parameters - ---------- - value : int - Number of beam size for beam search - """ - return self._set(beamSize=value) + def setStopStrings(self, stopStrings: List[str]): + """Set strings upon seeing which token generation is stopped""" + return self._set(stopStrings=stopStrings) + def setSamplers(self, samplers: List[str]): + """Set which samplers to use for token generation in the given order""" + return self._set(samplers=samplers) -def setNReturnSequences(self, value): - """Sets the number of sequences to return from the beam search. + def setUseChatTemplate(self, useChatTemplate: bool): + """Set whether generate should apply a chat template""" + return self._set(useChatTemplate=useChatTemplate) - Parameters - ---------- - value : int - Number of sequences to return - """ - return self._set(nReturnSequences=value) + # -------- JAVA SETTERS -------- + def setTokenIdBias(self, tokenIdBias: Dict[int, float]): + """Set token id bias""" + return self._call_java("setTokenIdBias", tokenIdBias) + + def setTokenBias(self, tokenBias: Dict[str, float]): + """Set token id bias""" + return self._call_java("setTokenBias", tokenBias) + + def setLoraAdapters(self, loraAdapters: Dict[str, float]): + """Set token id bias""" + return self._call_java("setLoraAdapters", loraAdapters) + + def getMetadata(self): + """Gets the metadata of the model""" + return self._call_java("getMetadata") diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..44504cb749151b 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -992,7 +992,7 @@ class _AutoGGUFLoader(ExtendedJavaWrapper): def __init__(self, path, jspark): super(_AutoGGUFLoader, self).__init__( "com.johnsnowlabs.nlp.annotators.seq2seq.AutoGGUFModel.loadSavedModel", path, jspark) - + class _MxbaiEmbeddingsLoader(ExtendedJavaWrapper): def __init__(self, path, jspark): @@ -1021,3 +1021,9 @@ def __init__(self, path, jspark): path, jspark, ) + + +class _AutoGGUFVisionLoader(ExtendedJavaWrapper): + def __init__(self, modelPath, mmprojPath, jspark): + super(_AutoGGUFVisionLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.seq2seq.AutoGGUFVisionModel.loadSavedModel", modelPath, mmprojPath, jspark) diff --git a/python/test/annotator/seq2seq/auto_gguf_model_test.py b/python/test/annotator/seq2seq/auto_gguf_model_test.py index e6553bc509e5ff..76ad82c21d0814 100644 --- a/python/test/annotator/seq2seq/auto_gguf_model_test.py +++ b/python/test/annotator/seq2seq/auto_gguf_model_test.py @@ -102,7 +102,6 @@ def runTest(self): model.setGpuSplitMode("NONE") model.setMainGpu(0) model.setTensorSplit([]) - model.setNBeams(0) model.setGrpAttnN(1) model.setGrpAttnW(512) model.setRopeFreqBase(1.0) @@ -115,11 +114,10 @@ def runTest(self): model.setDefragmentationThreshold(-1.0) model.setNumaStrategy("DISTRIBUTE") model.setRopeScalingType("UNSPECIFIED") - model.setPoolingType("UNSPECIFIED") + model.setPoolingType("NONE") model.setModelDraft("") model.setLookupCacheStaticFilePath("/tmp/sparknlp-llama-cpp-cache") model.setLookupCacheDynamicFilePath("/tmp/sparknlp-llama-cpp-cache") - model.setLoraBase("") model.setEmbedding(False) model.setFlashAttention(False) model.setInputPrefixBos(False) @@ -171,6 +169,7 @@ def runTest(self): pipeline = Pipeline().setStages([document_assembler, model]) results = pipeline.fit(data).transform(data) + # Can fail due to bogus parameters, but at least we are testing the setters results.select("completions").show(truncate=False) diff --git a/python/test/annotator/seq2seq/auto_gguf_vision_model_test.py b/python/test/annotator/seq2seq/auto_gguf_vision_model_test.py new file mode 100644 index 00000000000000..1bdefcd369330d --- /dev/null +++ b/python/test/annotator/seq2seq/auto_gguf_vision_model_test.py @@ -0,0 +1,90 @@ +# Copyright 2017-2023 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +import pytest +from pyspark.sql.functions import lit + +from sparknlp.annotator import * +from sparknlp.base import * +from test.util import SparkContextForTest + + +@pytest.mark.slow +class AutoGGUFVisionModelTestSpec(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + + def runTest(self): + documentAssembler = ( + DocumentAssembler().setInputCol("caption").setOutputCol("caption_document") + ) + imageAssembler = ( + ImageAssembler().setInputCol("image").setOutputCol("image_assembler") + ) + imagesPath = "../src/test/resources/image/" + data = ImageAssembler.loadImagesAsBytes(self.spark, imagesPath).withColumn( + "caption", lit("Caption this image.") + ) # Add a caption to each image. + nPredict = 40 + model = ( + AutoGGUFVisionModel.loadSavedModel( + "models/llava-v1.5-7b-Q4_0.gguf", + "models/llava-v1.5-7b-mmproj-model-f16.gguf", + self.spark, + ) + .setInputCols(["caption_document", "image_assembler"]) + .setOutputCol("completions") + .setChatTemplate("vicuna") + .setBatchSize(4) + .setNGpuLayers(99) + .setNCtx(4096) + .setMinKeep(0) + .setMinP(0.05) + .setNPredict(nPredict) + .setNProbs(0) + .setPenalizeNl(False) + .setRepeatLastN(256) + .setRepeatPenalty(1.18) + .setStopStrings(["", "Llama:", "User:"]) + .setTemperature(0.05) + .setTfsZ(1) + .setTypicalP(1) + .setTopK(40) + .setTopP(0.95) + ) + pipeline = Pipeline().setStages([documentAssembler, imageAssembler, model]) + # pipeline.fit(data).transform(data).selectExpr( + # "reverse(split(image.origin, '/'))[0] as image_name", "completions.result" + # ).show(truncate=False) + + results = pipeline.fit(data).transform(data).collect() + + expectedWords = { + "bluetick.jpg": "dog", + "chihuahua.jpg": "dog", + "egyptian_cat.jpeg": "cat", + "hen.JPEG": "chick", + "hippopotamus.JPEG": "hippo", + "junco.JPEG": "bird", + "ostrich.JPEG": "ostrich", + "ox.JPEG": "bull", + "palace.JPEG": "room", + "tractor.JPEG": "tractor", + } + + for result in results: + image_name = result["image_assembler"][0]["origin"].split("/")[-1] + completion = result["completions"][0]["result"] + assert expectedWords[image_name] in completion, f"Expected '{expectedWords[image_name]}' in '{completion}'" From ac80d3f0c8e11cc71b6eac6cdb749b8ae2bf4e20 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Sat, 18 Jan 2025 13:50:20 +0100 Subject: [PATCH 015/108] [SPARKNLP-1079] AutoGGUFVisionModel documentation and end-to-end example --- .../annotator_entries/AutoGGUFVisionModel.md | 200 +++++ docs/en/annotators.md | 1 + .../PromptAssember_with_AutoGGUFModel.ipynb | 3 +- ...llama.cpp_in_Spark_NLP_AutoGGUFModel.ipynb | 14 +- ...cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb | 804 ++++++++++++++++++ .../seq2seq/auto_gguf_vision_model.py | 7 +- .../seq2seq/AutoGGUFVisionModel.scala | 5 +- 7 files changed, 1017 insertions(+), 17 deletions(-) create mode 100644 docs/en/annotator_entries/AutoGGUFVisionModel.md create mode 100644 examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb diff --git a/docs/en/annotator_entries/AutoGGUFVisionModel.md b/docs/en/annotator_entries/AutoGGUFVisionModel.md new file mode 100644 index 00000000000000..1e10040ae40856 --- /dev/null +++ b/docs/en/annotator_entries/AutoGGUFVisionModel.md @@ -0,0 +1,200 @@ +{%- capture title -%} +AutoGGUFVisionModel +{%- endcapture -%} + +{%- capture description -%} +Multimodal annotator that uses the llama.cpp library to generate text completions with large +language models. It supports ingesting images for captioning. + +For settable parameters, and their explanations, see HasLlamaCppInferenceProperties, +HasLlamaCppModelProperties and refer to the llama.cpp documentation of +[server.cpp](https://github.com/ggerganov/llama.cpp/tree/7d5e8777ae1d21af99d4f95be10db4870720da91/examples/server) +for more information. + +If the parameters are not set, the annotator will default to use the parameters provided by +the model. + +This annotator expects a column of annotator type AnnotationImage for the image and +Annotation for the caption. Note that the image bytes in the image annotation need to be +raw image bytes without preprocessing. We provide the helper function +ImageAssembler.loadImagesAsBytes to load the image bytes from a directory. + +Pretrained models can be loaded with `pretrained` of the companion object: + +```scala +val autoGGUFVisionModel = AutoGGUFVisionModel.pretrained() + .setInputCols("image", "document") + .setOutputCol("completions") +``` + +The default model is `"llava_v1.5_7b_Q4_0_gguf"`, if no name is provided. + +For available pretrained models please see the [Models Hub](https://sparknlp.org/models). + +For extended examples of usage, see the +[AutoGGUFVisionModelTest](https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTest.scala) +and the +[example notebook](https://github.com/JohnSnowLabs/spark-nlp/tree/master/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb). + +**Note**: To use GPU inference with this annotator, make sure to use the Spark NLP GPU package and set +the number of GPU layers with the `setNGpuLayers` method. + +When using larger models, we recommend adjusting GPU usage with `setNCtx` and `setNGpuLayers` +according to your hardware to avoid out-of-memory errors. +{%- endcapture -%} + +{%- capture input_anno -%} +IMAGE, DOCUMENT +{%- endcapture -%} + +{%- capture output_anno -%} +DOCUMENT +{%- endcapture -%} + +{%- capture python_example -%} +import sparknlp +from sparknlp.base import * +from sparknlp.annotator import * +from pyspark.ml import Pipeline +from pyspark.sql.functions import lit + +documentAssembler = DocumentAssembler() \ + .setInputCol("caption") \ + .setOutputCol("caption_document") +imageAssembler = ImageAssembler() \ + .setInputCol("image") \ + .setOutputCol("image_assembler") + +imagesPath = "src/test/resources/image/" +data = ImageAssembler \ + .loadImagesAsBytes(spark, imagesPath) \ + .withColumn("caption", lit("Caption this image.")) # Add a caption to each image. + +nPredict = 40 +model = AutoGGUFVisionModel.pretrained() \ + .setInputCols(["caption_document", "image_assembler"]) \ + .setOutputCol("completions") \ + .setBatchSize(4) \ + .setNGpuLayers(99) \ + .setNCtx(4096) \ + .setMinKeep(0) \ + .setMinP(0.05) \ + .setNPredict(nPredict) \ + .setNProbs(0) \ + .setPenalizeNl(False) \ + .setRepeatLastN(256) \ + .setRepeatPenalty(1.18) \ + .setStopStrings(["", "Llama:", "User:"]) \ + .setTemperature(0.05) \ + .setTfsZ(1) \ + .setTypicalP(1) \ + .setTopK(40) \ + .setTopP(0.95) + +pipeline = Pipeline().setStages([documentAssembler, imageAssembler, model]) +pipeline.fit(data).transform(data) \ + .selectExpr("reverse(split(image.origin, '/'))[0] as image_name", "completions.result") \ + .show(truncate = False) ++-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|image_name |result | ++-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|palace.JPEG |[ The image depicts a large, ornate room with high ceilings and beautifully decorated walls. There are several chairs placed throughout the space, some of which have cushions] | +|egyptian_cat.jpeg|[ The image features two cats lying on a pink surface, possibly a bed or sofa. One cat is positioned towards the left side of the scene and appears to be sleeping while holding] | +|hippopotamus.JPEG|[ A large brown hippo is swimming in a body of water, possibly an aquarium. The hippo appears to be enjoying its time in the water and seems relaxed as it floats] | +|hen.JPEG |[ The image features a large chicken standing next to several baby chickens. In total, there are five birds in the scene: one adult and four young ones. They appear to be gathered together] | +|ostrich.JPEG |[ The image features a large, long-necked bird standing in the grass. It appears to be an ostrich or similar species with its head held high and looking around. In addition to] | +|junco.JPEG |[ A small bird with a black head and white chest is standing on the snow. It appears to be looking at something, possibly food or another animal in its vicinity. The scene takes place out] | +|bluetick.jpg |[ A dog with a red collar is sitting on the floor, looking at something. The dog appears to be staring into the distance or focusing its attention on an object in front of it.] | +|chihuahua.jpg |[ A small brown dog wearing a sweater is sitting on the floor. The dog appears to be looking at something, possibly its owner or another animal in the room. It seems comfortable and relaxed]| +|tractor.JPEG |[ A man is sitting in the driver's seat of a green tractor, which has yellow wheels and tires. The tractor appears to be parked on top of an empty field with] | +|ox.JPEG |[ A large bull with horns is standing in a grassy field.] | ++-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +{%- endcapture -%} + +{%- capture scala_example -%} +import com.johnsnowlabs.nlp.ImageAssembler +import com.johnsnowlabs.nlp.annotator._ +import com.johnsnowlabs.nlp.base._ +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit + +val documentAssembler = new DocumentAssembler() + .setInputCol("caption") + .setOutputCol("caption_document") + +val imageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + +val imagesPath = "src/test/resources/image/" +val data: DataFrame = ImageAssembler + .loadImagesAsBytes(ResourceHelper.spark, imagesPath) + .withColumn("caption", lit("Caption this image.")) // Add a caption to each image. + +val nPredict = 40 +val model = AutoGGUFVisionModel.pretrained() + .setInputCols("caption_document", "image_assembler") + .setOutputCol("completions") + .setBatchSize(4) + .setNGpuLayers(99) + .setNCtx(4096) + .setMinKeep(0) + .setMinP(0.05f) + .setNPredict(nPredict) + .setNProbs(0) + .setPenalizeNl(false) + .setRepeatLastN(256) + .setRepeatPenalty(1.18f) + .setStopStrings(Array("", "Llama:", "User:")) + .setTemperature(0.05f) + .setTfsZ(1) + .setTypicalP(1) + .setTopK(40) + .setTopP(0.95f) + +val pipeline = new Pipeline().setStages(Array(documentAssembler, imageAssembler, model)) +pipeline + .fit(data) + .transform(data) + .selectExpr("reverse(split(image.origin, '/'))[0] as image_name", "completions.result") + .show(truncate = false) ++-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|image_name |result | ++-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +|palace.JPEG |[ The image depicts a large, ornate room with high ceilings and beautifully decorated walls. There are several chairs placed throughout the space, some of which have cushions] | +|egyptian_cat.jpeg|[ The image features two cats lying on a pink surface, possibly a bed or sofa. One cat is positioned towards the left side of the scene and appears to be sleeping while holding] | +|hippopotamus.JPEG|[ A large brown hippo is swimming in a body of water, possibly an aquarium. The hippo appears to be enjoying its time in the water and seems relaxed as it floats] | +|hen.JPEG |[ The image features a large chicken standing next to several baby chickens. In total, there are five birds in the scene: one adult and four young ones. They appear to be gathered together] | +|ostrich.JPEG |[ The image features a large, long-necked bird standing in the grass. It appears to be an ostrich or similar species with its head held high and looking around. In addition to] | +|junco.JPEG |[ A small bird with a black head and white chest is standing on the snow. It appears to be looking at something, possibly food or another animal in its vicinity. The scene takes place out] | +|bluetick.jpg |[ A dog with a red collar is sitting on the floor, looking at something. The dog appears to be staring into the distance or focusing its attention on an object in front of it.] | +|chihuahua.jpg |[ A small brown dog wearing a sweater is sitting on the floor. The dog appears to be looking at something, possibly its owner or another animal in the room. It seems comfortable and relaxed]| +|tractor.JPEG |[ A man is sitting in the driver's seat of a green tractor, which has yellow wheels and tires. The tractor appears to be parked on top of an empty field with] | +|ox.JPEG |[ A large bull with horns is standing in a grassy field.] | ++-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +{%- endcapture -%} + +{%- capture api_link -%} +[AutoGGUFVisionModel](/api/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel) +{%- endcapture -%} + +{%- capture python_api_link -%} +[AutoGGUFVisionModel](/api/python/reference/autosummary/sparknlp/annotator/seq2seq/auto_gguf_vision_model/index.html) +{%- endcapture -%} + +{%- capture source_link -%} +[AutoGGUFVisionModel](https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala) +{%- endcapture -%} + +{% include templates/anno_template.md +title=title +description=description +input_anno=input_anno +output_anno=output_anno +python_example=python_example +scala_example=scala_example +api_link=api_link +python_api_link=python_api_link +source_link=source_link +%} \ No newline at end of file diff --git a/docs/en/annotators.md b/docs/en/annotators.md index c5c21707b80f8e..541d151533c3ce 100644 --- a/docs/en/annotators.md +++ b/docs/en/annotators.md @@ -47,6 +47,7 @@ There are two types of Annotators: |---|---|---| {% include templates/anno_table_entry.md path="" name="AutoGGUFEmbeddings" summary="Annotator that uses the llama.cpp library to generate text embeddings with large language models."%} {% include templates/anno_table_entry.md path="" name="AutoGGUFModel" summary="Annotator that uses the llama.cpp library to generate text completions with large language models."%} +{% include templates/anno_table_entry.md path="" name="AutoGGUFVisionModel" summary="Multimodal annotator that uses the llama.cpp library to generate text completions with large language models."%} {% include templates/anno_table_entry.md path="" name="BGEEmbeddings" summary="Sentence embeddings using BGE."%} {% include templates/anno_table_entry.md path="" name="BigTextMatcher" summary="Annotator to match exact phrases (by token) provided in a file against a Document."%} {% include templates/anno_table_entry.md path="" name="Chunk2Doc" summary="Converts a `CHUNK` type column back into `DOCUMENT`. Useful when trying to re-tokenize or do further analysis on a `CHUNK` result."%} diff --git a/examples/python/llama.cpp/PromptAssember_with_AutoGGUFModel.ipynb b/examples/python/llama.cpp/PromptAssember_with_AutoGGUFModel.ipynb index d4152e51194c25..8d00e9d3b1a291 100644 --- a/examples/python/llama.cpp/PromptAssember_with_AutoGGUFModel.ipynb +++ b/examples/python/llama.cpp/PromptAssember_with_AutoGGUFModel.ipynb @@ -264,8 +264,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFModel.ipynb b/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFModel.ipynb index 3a76bdf5f01ece..09be6b85ee1083 100644 --- a/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFModel.ipynb +++ b/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFModel.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -320,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -335,7 +335,6 @@ "source": [ "from sparknlp.annotator import *\n", "\n", - "# All these params should be identical to the original ONNX model\n", "autoGGUFModel = (\n", " AutoGGUFModel.loadSavedModel(EXPORT_PATH, spark)\n", " .setInputCols(\"document\")\n", @@ -355,7 +354,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -389,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -415,7 +414,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -619,8 +618,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb b/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb new file mode 100644 index 00000000000000..c3175b9ea0babf --- /dev/null +++ b/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb @@ -0,0 +1,804 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb)\n", + "\n", + "# Import llama.cpp ๐Ÿฆ™ vision models into Spark NLP ๐Ÿš€\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- Multimodal inference with llama.cpp was introduced in `Spark NLP 5.6.0`, enabling quantized LLM inference on a wide range of devices. Please make sure you have upgraded to the latest Spark NLP release.\n", + "- You need to use your own `.gguf` model files, which also include the models from the [Hugging Face Models](https://huggingface.co/models?library=gguf)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download a GGUF Vision Model\n", + "\n", + "Let's download a GGUF vision model to test it out. For this, we will use [Mozilla/llava-v1.5-7b](https://huggingface.co/Mozilla/llava-v1.5-7b-llamafile/tree/main). It is a 7B parameter model which also is available in 4-bit quantization.\n", + "\n", + "We can download the model and its multimodal projection (mmproj) file by selecting the q4 GGUF file from the \"Files and versions\" tab.\n", + "\n", + "Once downloaded, we can directly import this model into Spark NLP!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "EXPORT_PATH_MODEL = \"llava-v1.5-7b-Q4_K.gguf\"\n", + "EXPORT_PATH_MMPROJ = \"llava-v1.5-7b-mmproj-Q4_0.gguf\"\n", + "! wget \"https://huggingface.co/Mozilla/llava-v1.5-7b-llamafile/resolve/main/{EXPORT_PATH_MODEL}?download=true\" -O {EXPORT_PATH_MODEL}\n", + "! wget \"https://huggingface.co/Mozilla/llava-v1.5-7b-llamafile/resolve/main/{EXPORT_PATH_MMPROJ}?download=true\" -O {EXPORT_PATH_MMPROJ}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import and Save AutGGUFVision models in Spark NLP\n", + "\n", + "- Let's install and setup Spark NLP (if running it Google Colab)\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Only execute this if you are on Google Colab\n", + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start Spark with Spark NLP included via our simple `start()` function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sparknlp\n", + "\n", + "# let's start Spark with Spark NLP with GPU enabled. If you don't have GPUs available remove this parameter.\n", + "spark = sparknlp.start(gpu=True)\n", + "print(sparknlp.version())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Let's use the `loadSavedModel` function in `AutoGGUFVisionModel`\n", + "- Most parameters will be set automatically. They can also be set later after loading the model in `AutoGGUFVisionModel` during runtime, so don't worry about setting them now.\n", + "- `loadSavedModel` accepts three parameters: \n", + " 1. the path to the exported gguf model\n", + " 1. the path to the exported mmproj gguf model\n", + " 2. the SparkSession that is `spark` variable we previously started via `sparknlp.start()`\n", + "- NOTE: `loadSavedModel` accepts local paths in addition to distributed file systems such as `HDFS`, `S3`, `DBFS`, etc. This feature was introduced in Spark NLP 4.2.2 release. Keep in mind the best and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sparknlp.annotator import *\n", + "\n", + "autoGGUFModel = (\n", + " AutoGGUFVisionModel.loadSavedModel(EXPORT_PATH_MODEL, EXPORT_PATH_MMPROJ, spark)\n", + " .setInputCols([\"caption_document\", \"image_assembler\"])\n", + " .setOutputCol(\"completions\")\n", + " .setChatTemplate(\"vicuna\")\n", + " .setBatchSize(4)\n", + " .setNGpuLayers(99)\n", + " .setNCtx(4096)\n", + " .setMinKeep(0)\n", + " .setMinP(0.05)\n", + " .setNPredict(40)\n", + " .setNProbs(0)\n", + " .setPenalizeNl(False)\n", + " .setRepeatLastN(256)\n", + " .setRepeatPenalty(1.18)\n", + " .setStopStrings([\"\", \"Llama:\", \"User:\"])\n", + " .setTemperature(0.05)\n", + " .setTfsZ(1)\n", + " .setTypicalP(1)\n", + " .setTopK(40)\n", + " .setTopP(0.95)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Let's save it on disk so it is easier to be moved around and also be used later via `.load` function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "autoGGUFModel.write().overwrite().save(f\"llava_v1.5_7b_Q4_0_gguf_spark_nlp\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Awesome ๐Ÿ˜Ž !\n", + "\n", + "This is your GGUF model from loaded and saved by Spark NLP ๐Ÿš€. You can now use it on other machines, clusters, or any place you wish to use your new and shiny GGUF model ๐Ÿ˜Š" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "llava-v1.5-7b-mmproj-Q4_0.gguf\tllava-v1.5-7b-Q4_K.gguf metadata\n" + ] + } + ], + "source": [ + "! ls llava_v1.5_7b_Q4_0_gguf_spark_nlp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example: Captioning Images\n", + "\n", + "Now let's see how we can use the model to caption some images. Let's first download some images we can caption." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA94AAAHbCAYAAAA9P0KxAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOy9ebwcVZn//z5LVXX33ZLc7GEJoCCC4AIuIBJlExAQVwQcQRYV/QHCuI5+Ccgo4qAwAwoiyyig4IqCoqw6CqMoCILLEGVPQpKb3LW7aznn/P44VdXdScAQQFTq83pdQldXVZ9auut8nufzfB7hnHNUqFChQoUKFSpUqFChQoUKFZ4RyGd7ABUqVKhQoUKFChUqVKhQocI/MyriXaFChQoVKlSoUKFChQoVKjyDqIh3hQoVKlSoUKFChQoVKlSo8AyiIt4VKlSoUKFChQoVKlSoUKHCM4iKeFeoUKFChQoVKlSoUKFChQrPICriXaFChQoVKlSoUKFChQoVKjyDqIh3hQoVKlSoUKFChQoVKlSo8AyiIt4VKlSoUKFChQoVKlSoUKHCM4iKeFeoUKFChQoVKlSoUKFChQrPIJ5TxPvSSy9FCMEDDzzwbA9lo1Ecw69//ety2eLFixFClH9hGLLFFltwwgknMDo6us62j/d3yy239HxWHMecd9557L777gwPDxMEAcPDwyxatIgLLriAiYmJv9FRV6hQoUKFChUqVKhQocI/LvSzPYC/Jfbff39uu+025s2b92wP5RnBddddx9DQEBMTE/zwhz/knHPO4Ve/+hW33norQohyvUsuuYQXvOAF62z/whe+sPz/lStX8vrXv5577rmHd73rXRx//PHMnj2bkZERbrrpJj784Q/z85//nK997Wt/k2OrUKFChQoVKlSoUKFChX9UPKeI96xZs5g1a9azPYxnDC972cuYOXMmAHvttRcjIyN87Wtf49Zbb2XXXXct19t+++3ZaaednnBfhx9+OL/73e+44YYbeM1rXtPz3hvf+EZOOeUUfvSjHz39B1GhQoUKFSpUqFChQoUK/2R4TkvNFy5cyBFHHLHOeosWLWLRokXl61tuuQUhBF//+tf5t3/7N+bPn8/g4CB77rknf/rTn9bZ/rrrrmOPPfZgaGiIRqPBtttuy2c+85medb7//e/zqle9ikajwcDAAHvttRe33Xbb03m4vPKVrwTgwQcffFLb3X777fzkJz/h2GOPXYd0FxgeHubwww9/ymOsUKFChQoVKlSoUKFChX92PKeI91PFxz/+cR588EG+8pWv8OUvf5n77ruPAw44AGNMuc5FF13Efvvth7WW888/nx/84Accf/zxPPLII+U6V1xxBQcddBCDg4N8/etf56KLLmLNmjUsWrSIn//850/beJcsWQKwTpbfGEOWZT1/3cdw/fXXA3DggQc+bWOpUKFChQoVKlSoUKFChecqnlNS86eKF77whVx22WXla6UUb3vb27j99tt55StfyeTkJCeddBK77rorN910U1lXvccee5TbWGv50Ic+xIte9CJ+9KMfIaWPfey3335stdVWfOQjH+EXv/jFRo2vINSTk5Nce+21nH/++Wy66abstttuPesVmfBuKKXIsgyAhx9+GIDNN9+8Zx3nXA9BF0KglNqosVaoUKFChQoVKlSoUKHCcwUV8X4SWDsDvMMOOwBeyv3KV76SW2+9lfHxcY477rgeM7Nu/OlPf2Lp0qWceOKJJekG6O/v581vfjMXXHABzWaTRqPxpMc3d+7cnte77rorX/7yl6nVaj3Lv/rVr7Ltttv2LHu88Xbj6quv5uCDDy5f9/X1MTk5+aTHWaFChQoVKlSoUKFChQrPJVTE+0lgeHi453UURQC0Wi3AO4EDbLLJJo+7j5GREYD1OqvPnz8fay1r1qzZKOJ9ww03MDQ0RBAEbLLJJuuMt8C22277hOZqm222GeADCttss025fNGiRdx+++0AnHrqqdx8881PeowVKlSoUKFChQoVKlSo8FzDc7rGu1arEcfxOstXrVq1Ufsraqm767nXRkGGly1bts57S5cuRUrJ9OnTN+rzd9xxR3baaSd23HHHxyXdG4K99toL8AZw3Zg2bRo77bQTO+2001Paf4UKFSpUqFChQoUKFSo8l/CcJt4LFy7k7rvv7ln2f//3f+t1Kt8Q7LLLLgwNDXH++efjnFvvOttssw0LFizgiiuu6FlnamqKb3/726XT+bOJnXbaib333psLL7yQ//mf/3lWx1KhQoUKFSpUqFChQoUK/+h4TkvN3/nOd3L44Ydz3HHH8eY3v5kHH3yQM888c6N7fff393PWWWdx9NFHs+eee3LMMccwZ84clixZwl133cW5556LlJIzzzyTww47jDe84Q285z3vIY5jPve5zzE6OsoZZ5xR7u+0007jtNNO48Ybb2T33Xfv+awNqcl+PNxzzz2lkVo3ttpqq/LYL7vsMvbZZx/23HNPjjjiCPbZZx9mz57N+Pg4d999NzfccAODg4MbPYYKFSpUqFChQoUKFSpUeK7gOUm8C9J66KGHsnTpUs4//3wuueQStt9+e770pS9x6qmnbvS+jzrqKObPn89nP/tZjj76aJxzLFy4kHe9613lOoceeih9fX185jOf4e1vfztKKV75yldy8803s8suu5TrWWsxxvRkxpvNJtCpL98YHHnkketdfuGFF3L00UcDXjZ/2223ceGFF3LllVdy1VVXMTk5yeDgINtttx3HH398uW6FChUqVKhQoUKFChUqVHh8CPd4muh/QpxzzjmceOKJTExM0N/f/2wPZ6NwwgkncO655zI6OsrAwMCzPZwKFSpUqFChQoUKFSpUqPBX8Jyo8R4bG+O6667j0ksvZfvtt/+HJN2/+c1vOP/887n44os58MADK9Jd4W+GpUuXsnjxYn7729+u897ixYufUtnD3yN+//vfs3jxYh544IENWv/SSy9FCLHB61eoUKFChQoV/vlQzIk21qT56cbChQt5wxve0LNMCNHzNzQ0xKJFi7j22mvX2XbtdYu/RYsWrfNZd999N0cddRRbbbUV9Xqder3O85//fN7znvfw61//+pk8zH8oPCek5nfeeScHH3wwO+ywAxdddNGzPZyNwlve8hbGxsY48MAD+c///M9nezgVnkNYunQpp556KgsXLuTFL35xz3tHH300r3/965+dgT1D+P3vf8+pp57KokWLWLhw4V9df//99+e2225bb4vAChUqVKhQoUKFvye85S1v4eSTT8Zay1/+8hdOP/10DjjgAH7wgx+w//77l+vtuuuu/Md//Mc626/t8XTBBRfwgQ98gG222YYTTjiB7bbbDiEEf/jDH/j617/OzjvvzJIlS9hqq62e8WP7e8dzgngvWrSo7LX9j4r777//2R5ChQrrYJNNNnnCvvXPBcyaNWujDRkrVKhQoUKFChX+lpgzZw6vfOUrAd+R6VWvehXPe97zOPvss3uI97Rp08r1Hg+/+MUvOO6449h///351re+RRiG5Xuve93reP/73883v/lN6vX6M3Mw/2B4TkjNK1T4e8d9993HoYceyuzZs4miiG233ZbzzjuvZ517772Xvffem0ajwaxZs3j/+9/PtddeixCCW265BYBPfepTaK15+OGH1/mMd7/73QwPD9Nut4GOBOm73/0uO+ywA7VajS233LJHUXHLLbew8847A96Ur5AZLV68GFi/1PzKK69k7733Zt68edTrdbbddls++tGPMjU11bPeEUccQX9/P0uWLGG//fajv7+fTTfdlJNPPpk4jp/0ObzuuuvYY489GBoaotFosO222/KZz3ymfP/Xv/41hxxyCAsXLqRer7Nw4ULe8Y538OCDD5brXHrppbz1rW8F4LWvfW15vJdeeunjfu76pOaLFi1i++2353/+53945StfSb1eZ8GCBXzyk5/EGNOzfZIknH766bzgBS8giiJmzZrFkUceycqVK3vWi+OYk08+mblz59JoNHjNa17Db37zGxYuXMgRRxzxpM9XhQoVKlSoUOHpx2OPPcY73vEOhoaGmDNnDu9+97sZGxsr33fO8cUvfpEXv/jF1Ot1pk+fzlve8hb+8pe/9OynmEvcfvvt7LbbbjQaDbbcckvOOOMMrLVP23iLrkbd86ENxac//WmUUlxwwQU9pLsbb33rW5k/f/5THeY/BSriXaHCs4zf//737Lzzztxzzz2cddZZXHPNNey///4cf/zxpcP+smXL2H333fnTn/7El770Jb761a8yMTHBBz7wgZ59vec970FrzQUXXNCzfPXq1XzjG9/gqKOOolarlct/+9vfcuKJJ/LBD36Q7373u+yyyy6ccMIJpbTopS99KZdccgkAn/jEJ7jtttu47bbbntDR/r777mO//fbjoosu4rrrruPEE0/kqquu4oADDlhn3TRNOfDAA9ljjz24+uqrefe7380XvvAFPvvZzz6pc3jRRRex3377Ya3l/PPP5wc/+AHHH388jzzySLnOAw88wDbbbMPZZ5/Nj3/8Yz772c+ybNkydt5557Iea//99+fTn/40AOedd155vN0R4A3F8uXLOeSQQzjssMO4+uqrectb3sLpp5/OCSecUK5jreWggw7ijDPO4NBDD+Xaa6/ljDPO4Prrr19HqXPkkUdy9tlnc+SRR3L11Vfz5je/mYMPPpjR0dEnPbYKFSpUqFChwjODN7/5zWy99dZ8+9vf5qMf/ShXXHEFH/zgB8v33/Oe93DiiSey55578r3vfY8vfvGL3Hvvveyyyy489thjPftavnw5hx12GIcffjjf//732XffffnYxz7GZZdd9rSNd82aNYyMjKyj3nPOkWXZOn+FL7cxhptvvpmddtqpKrfbULgKFSo8q9hnn33cJpts4sbGxnqWf+ADH3C1Ws2tXr3afehDH3JCCHfvvfeusy3gbr755nLZu971Ljd79mwXx3G57LOf/ayTUrr777+/XLb55ps7IYT77W9/27PPvfbayw0ODrqpqSnnnHO33367A9wll1yyzthPOeUU90Q/I9Zal6ap++lPf+oAd9ddd/WME3BXXXVVzzb77bef22abbR53n2tjYmLCDQ4Oule/+tXOWrvB22VZ5iYnJ11fX58755xzyuXf/OY31zmnT4RLLrnEAT3ndvfdd3eAu/rqq3vWPeaYY5yU0j344IPOOee+/vWvO8B9+9vf7lmvOOdf/OIXnXPO3XvvvQ5wH/nIR3rWK7Z/17vetYFHXaFChQoVKlR4JlDMic4888ye5ccdd5yr1WrOWutuu+02B7izzjqrZ52HH37Y1et19+EPf7hcVswlfvnLX/as+8IXvtDts88+f3U8m2++udt///17lgHuuOOOc2mauiRJ3B/+8Ae37777OsCdd955PdsC6/371Kc+5Zxzbvny5Q5whxxyyDqfnWWZS9O0/Hsy87N/Zmxwjfet//sbpBBYBEpJpBAoKQvy3k3ksc5RLBH5Mudc+f/FO16h6sr3/V/+bqleFUjp5Z5KKZRSpbTVWlf2ubbWlrKLQh4KdNYXohyTH4tASVF+jnMu30Z0xiv8+1JKpOyIA5zrHJO1FucsCEFfo4Zzgsz4bR2WuN3GWQcOHK5nrMWfMQZjDJm1OCwiH19xHFLI/Bgoj09K1eMw6M+H7RpjfrRSoLVCFhsjkFKW2zkhes4/eBlEsU+RnzeXL3eA7bre1lqkEMh8feccNj9HUhbXTgESf/c4jHM4axHF/vP9OSj3Y63FWFt+lrMWVx6XQAiJEKBlfjxS4hD5tTFYa8rrVHyO37fLx+av56te+TKeTbTbbW688Ube97730Wg0yLKsfG+//fbj3HPP5X//93/56U9/yvbbb88LX/jCnu3f8Y538OMf/7hn2QknnMB///d/881vfpPDDjsMay1f+tKX2H///dcxC9tuu+3Ycccde5YdeuihXH/99dxxxx28+tWvftLH9Je//IVPfOIT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5ps56KCDOPjgg9luu+14xzvewVlnnVVGmO+9917OOusshoaG1rn+Ff46iszSHXfcwac+9SluuOGG0nPgC1/4QqmkuPLKK7nooov43e9+x+joKAsXLuSggw7ik5/8JH19fU/4GU9m21/+8pecfvrp3HrrrUxNTbFgwQIOOOAAzj777HKd++67j1NOOYUbbriBsbExttxySz7wgQ88rlpmY/C3aCtT4anhJS95ybM9hArPMF72speVqoe99tqLkZERvva1r3Hrrbey6667luttv/32fzVzefjhh/O73/2OG264oQziFnjjG9/IKaecwo9+9KOn/yAqrBe77rorxx57LEceeSS//vWvec1rXkNfXx/Lli3j5z//OS960Yt43/ve96T3q7Vm991358Ybb+xZ/lTUnaOjo/zv//7vOsujKCp/h3bddVfOO+88/r//7//jpS99KcceeyzbbbcdUkqWLVvGt7/9bWBdddRzFRs8WytMwDJryXL5NHSZV3Wt203Auq93xwxMlpljymWFxFrkme7eG6VDtmVXPbRESYWWnsArIdCyME3rkHhvtFWQwbVHm4+ZvH5ddjLaPe/lyzLTdfzOIZwB50jSjHacMjnRZGxsnGaz6TNR+X6U8HXbuqjfzok3zoDNcDYGUoT0UvqizjvUmppWRFoTBpp6PaKvUaNeC9BK4rKE5vgoy5c+zJ/u+xO//d0d3PuH3/HoskeZak2RmgRrTZ4571zL8npmGUmWkRqTBxHygACUCgIJKBwyl5B7o7LOzePwgZmsqLXvIt50XQfnivcsXYn8QpNAERjxpHjtK2XLc1mqEfyLde6T4r4q9uByyXsZjMmDBk9fB8Snhhe+8IXccccdbL/99nziE59g77335qijjuJb3/oWe+yxB+AJ8k9/+lO23npr3vve93LYYYcRhiGnnXYawDrkEeDtb387AO985zsZGhpa72e/+MUv5vOf/zxnnXUWBx10EL/4xS/4/Oc/z4c//OFynUajwcUXX8zIyAh77703O++8M1/+8pfXu7/h4WGuvfZaGo0Ghx9+OO9+97vp7+/nyiuvfCqn6K/iqKOO4oc//CHGGI4++mje8IY3cPbZZ5cEF3y2+bWvfS0f/vCHedOb3sSvf/1rrr/++nXOzRZbbMHZZ5/NXXfdxaJFi9h55535wQ9+AHR+B9cXrFv74TZ37lyuuOIK/vu//5sDDzyQq666io9//OM9fdKVUnz/+9/n4x//ON/5znc4+OCDeeMb38gZZ5xBrVbjRS96UbnuJZdcwgknnMBFF13EAQccwDe+8Q2uuuoqYP3Xv8KG4eCDD+Z5z3se3/rWt1i8eDHf+9732GeffUjTFHhy7fHWxoZu++Mf/5jddtuNhx56iM9//vP86Ec/4hOf+ERPucKGtB18OlC1lXn28dd6AC9cuLAnmHrLLbcghOCyyy7jpJNOYu7cudTrdXbffXfuvPPOnn0fccQR9Pf3c++997LHHnvQ19fHrFmz+MAHPkCz2exZt91u87GPfYwtttiCMAxZsGAB73//+9cpHVq4cCFveMMb+O53v8sOO+xArVZjyy237PmtK/DQQw9x+OGHM3v2bKIoYtttt+Wss84qn+kPPPBAee+deuqp5TO9ON4lS5Zw5JFH8vznP59Go1EGqH73u9/1fE5xTq644go+8pGPMG/ePPr7+znggAN47LHHmJiY4Nhjj2XmzJnMnDmTI488ksnJyXL7Bx54ACHEeuX8QggWL15cvl65ciXHHnssm266KVEUMWvWLHbddVduuOGGdbbdWBRqkycbELv99tv5yU9+wrHHHrsO6S4wPDzM4Ycf/pTHWGHDccEFF3Duuefys5/9jEMOOYT999+f//f//h9TU1PrGKltKAqj5m40m82nJF3/xS9+wate9ap1/tZ+hr33ve/l17/+NTvvvDNf+MIX2G+//dh33335f//v/9HX18eNN97Iscceu9Hj+GfCkzJX86TMYnLGso7UGP9wNsZLH4XoUCcppa/RplMXXcqDO3rhHtK2vkymdTY3DBMIKRDO5jW9hRlbMZ6ODHltiXVB8Lv3LQCpZFk77p3DZfGheaCgMDbDy7xzuXOaZSXh7CaCAk9eC+fv7gyyd/Y2CDpu6kUGXyqNr8nOncvLHLA/LmsVQoDJ/MSw2ZpiZHQFq0aWkhqJEZLG6hkMzphBo6/RyXbnQzDrZKZ9Jr8YuqVwdJcFU/U54+Lwyv15klxmqAGpFDgw5HLznmuYu5znZQGulKAXWxfXa21K7HLZPbmWoWPAVri/07WX4pytff17lBdu/eqHZwsLFy7koosuesJ1tttuO66//vqeZcceeyz9/f3rlZp9//vfB1in1/faePOb38yb3/zmJ1znkEMO4ZBDDlln+eLFi3smIACvetWruPXWW9dZd+3zfemll653UrO+fW4I9t13X/bdd9/HfX/BggV861vfWmf5+uqPTjjhhJ5+2080tmJCvL6a+N13353bb7/9Ccettebkk0/m5JNPfsL1oijirLPO4qyzziqX3XrrrYyNjVVmVU8Bb3rTmzjzzDMB2HvvvZkzZw6HHXYYV111FYcddhif+MQnynWdc+y6665su+227L777tx99909Lv1rY0O3ff/7389mm23GL3/5yx6zwe4azpNOOomBgQF+/vOfl5mDvfbaiziOOeOMMzj++OOZPn36kz7+glA75/jzn//MSSedhLWWww47rGe9H/7wh+uVEH7qU5/iE5/4BKtWraLVarH55puvs87awaru7iQV1sWb3/xm3v72t3PUUUfxu9/9jo997GMAXHzxxU+43cc//nFe+tKX8pWvfIWxsTEWL17MokWLuPPOO3sMm9I0Zb/99uM973kPH/3oR7n11ls5/fTTefDBB8sgo3OON77xjdx444187GMfY7fdduPuu+/mlFNO4bbbbuO2227rmdD/9re/5cQTT2Tx4sXMnTuXyy+/nBNOOIEkSUon45UrV7LLLruQJAmf+tSnWLhwIddccw3/+q//yp///Ge++MUvMm/ePK677jpe//rXc9RRR3H00UcDHS+PpUuXMjw8zBlnnMGsWbNYvXo1//3f/80rXvEK7rzzTrbZZpt1zslrX/taLr30Uh544AH+9V//lXe84x1ordlxxx35+te/zp133snHP/5xBgYG1hss+Gt45zvfyR133MG///u/s/XWWzM6Osodd9zxtPpvLFmyBGCdgFiRQOlGxyuHcs5QmVv9bfB485cjjjhiHeXhkUce+YR1+sA6xpUF1jd3WnuONTIywsjIyDpmbRs6932ytdk77rjjX/2NqvAkiLcntnn2Mq/ntush3t1y7u4Mt5SeKGO7LnhOftbeT0Gquglzd8spv3P/52zvDdRZ3782niV3tQ7zRLObjBUEtDBiK/ZRkjvRWVdaS+Zc3ubKljL0sp6ZbupPSbRtNwHNM/Rpvq0FtJAIpVEqQCr/PoUyIKeq1ll/vLIYtyWzGYlNsFrQGBwiigaZOWs+06ZPQyiFEAqtNUrrznEZ26tYKI+veN1Vp+86png4i0MiLFgpUNIHCgQ+o1+GT2TH7Kw4j15ant8LQng/gDxwIeh8dufaFRl6l6+XS/Lz2EVxvlWXXL1ob1eQeZ9Z71zftUsj1s6W/73jtNNOY/78+Wy55ZZMTk5yzTXX8JWvfIVPfOITPbLOO++8k/vvv5/TTjuNgw46iO222+5ZHPU/L8bGxrjtttu49NJL2X777env739GP+/666/ntttu42Uvexn1ep277rqLM844g+c///m86U1vekY/+58ZaxPMt73tbbzrXe/i5ptv5rDDDntS7fHWxoZs+3//93/8+c9/5tOf/vQ6Dv8FNrTt4BMFnh4PVVuZvz8cddRRfOhDHwJgzz33ZMmSJVx88cVcdNFFTxiwmDVrFt/97nfLdV796lfz/Oc/n8985jNceOGF5XpJknDyySdz/PHHAz6AEwQB//Zv/8YvfvELdt11V37yk5/w4x//mDPPPLMcy1577cWmm27K29/+dr761a9yzDHHlPtcunQpd955Z+kXsu+++7JixQo+9alPcdxxx9FoNPj85z/Po48+yi9/+csyq7fPPvtgjOH888/nxBNPZOutt+ZlL/NtPjfZZJN1fAVe85rX9GRujTHsv//+bLfddlxwwQV8/vOf71l/hx124JJLLilf//GPf+Tss8/m+OOP53Of+1x5XLfddhuXX375RhHvX/ziFxx99NE95+Oggw560vvpRkGoJycnufbaazn//PPZdNNN2W233XrWW5/vglKq/I14+OGHAdYJiPkkWSc72k3WK/xjY+XKlaWxLrDepEmFZw8bbq5WZidFxyBtLWJcvF8QnSKpLWV3xrFD+Dp9t8v/9MiJyzyos539CoG0DifAYnsIlciNxDzRz3eV99guiSVdcmPX66hdkO/u7HhBGv0xFdXCtpRum9x1XLpuOTxA78OxOF/kBDGQEq1UpwZaFwQ58C26pKQ4A2XrNuODFOSSbXCgJLX+AWbMWcDQDEl/Y4iZwzMZHBiir95HLYoItCfzxZyvIMlF+zGlBDqX92cOtJIorRCAMQ5jM1zmAw44k19DhUAihetxS+9ukdad7e6u4adHTWC9W3qZnbZd5QnFtZEoVbQTy5UEpZTdn13Xde90lvv9mbx2vTCq6w4Q/CMhCAI+97nP8cgjj5BlGc9//vP5/Oc/v05m9uCDD2b58uXstttunH/++c/SaJ8ePJ6su8CzOVm48847Ofjgg9lhhx3+qlrh6cDg4CA/+clPOPvs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3l85QxuOOXPmUKvVuPvuu3uWX3311U+43WabbcYHPvABbrzxRn7xi188bePZWOy0007svffeXHjhhbz97W9fp0a8QoUKf1s8qYx34UTe6Uvd+bfjTm0xJjdgy8mrFeCERXURaCEECplnKHsnBAJwhW67C91S9cL5upN9FaVM2+R101L61mPdraccYE3xOUXG3kvFu1G0vrLWltnY0rm8kK93nYtiRyIftS3MxPL9qrwPNTkJRvYSW+gyZyvPEWVmujgXPedcSqT0tXieSHe3cStao+XnwHXau3Vq8S1ZZvKAQNGmzUvMnfLS8GJ03W3WnC1qrIta/iKr3pHMF5/nBAhnc3G7KMsOimy+sYYkjmm2m4xOTtJsNgm0YqBvgMF+n9nv3HM+IFLUahefVxJtOnXbhau5K5UZ+Y1WbFtI9ytUqPCcxne+8x0WL17Ml770JYQQZe/sMAzL9ngnn3wyhx9+OH19fRx00EFceeWVf7Vu7slsu88++/Czn/2M0047jeOPP552u80mm2zSU+tbtB0sXMRXrFjBtGnTeP7zn89+++3Xs79nsq3M2liwYEGPTP69730vr3rVqzjnnHP4whe+wNKlSxFCsMkmm7DLLrtw44038rrXvW6jx1Hh8bFixQoOPvhgjjnmGMbGxjjllFOo1WrrlCiFYchZZ53F5OQkO++8c+lqvu+++/LqV78a8JnSffbZh4985COMj4+z6667lq7mL3nJS3jnO9/Zs8/58+dz4IEHsnjxYubNm8dll13G9ddfz2c/+1kajQYAH/zgB/nqV7/K/vvvz2mnncbmm2/Otddeyxe/+EXe9773lfXdAwMDbL755lx99dXssccezJgxg5kzZ5Ztyy699FJe8IIXsMMOO/Cb3/yGz33uc0/okbAxEEJw+OGHc/HFF7PVVlux44478qtf/aoMUBQYGxvjta99LYceeigveMELGBgY4Pbbb+e6667rMb087bTTOO2007jxxht7Sk+Kz9pY3HPPPeu4mgNstdVWpVLhsssuY5999mHPPffkiCOOYJ999mH27NmMj49z9913c8MNN1TBsAoV/kZ4Eq7msiSb9HDNdWXmBSHyHDWvvbUir9WVXTW2RWbc/383CV3bzdzmMnOKLfL9O/K+2F2ybS07WXlRZKW79pdXi3dkya6o8+7N3hpjyNLUE++CLHYRbiW7e353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K664gt/+7d+mUqmwb98+3vGOd3Ddddc9oHNxMtI05YorruBXfuVXeN3rXsc111zD2972Ng4ePMinPvUpIP+xX3nllXzrW9/izW9+8zBs/bnPfe5D2qbD4XA8nHz1q1/l8ssv57zzzuPv//7vCcOQ97znPVx55ZV86EMf4qd+6qf4nd/5Hd7//vfztre9jcc97nF0Oh1uuukmlpaWgPzZ0Ol0+OhHP8q11147XPemTZuGf3/84x/na1/7Gm984xuZmZlhamrqpPtz9OhRnvCEJ5CmKa9//es577zzWFpa4v/+3//LysoK09PTw3lf//rX85SnPIW/+7u/o9ls8vu///tceeWV3HrrrSilHpbzc+eddwIwOTl5wnStNVmWnTBNCDHc7r//+78D8MIXvvBh2Q+Hw+F4NPATP/ETvOxlL+PVr341N998M//jf/wPbrnlFr75zW/i+/4PtO4vf/nLPPe5z+Wiiy7ir//6r2k0Gnz4wx/mp37qp+h2u1x99dU8//nP53/+z//J61//et797ndzwQUXAPfv6b766qv5wAc+wKte9Sre+ta3EgQB3/nOd06aIw55VNbv/d7v8Rd/8Rf83d/9HVdfffVDOp6//Mu/ZMeOHfzZn/0Zxhje+c538rznPY+vfvWrXHzxxUDuZHzJS17C05/+dD7ykY+QZRl//Md/fC9D848d9keMZz7zmXZkZMTOz8+f9PP3vve9FrC/9mu/dsL0d77znRawx48fH0675JJL7CWXXDJ8v3//fgvYc88912ZZNpx+3XXXWcB+6EMfus9lB/zcz/2c3bFjx33uv9bapmlq3/rWt9rx8XFrjBl+tmPHDhtFkT148OBwWq/Xs2NjY/ZXfuVXhtPe9KY32ZN9dYNj379//0m3bYyxaZrar371qxawN9xww33up7XWfvnLX7aA/fKXv3zC8QH2z//8z0+Y9w//8A8tYL/+9a9ba6399Kc/bQH7V3/1VyfM9/a3v90C9k1vetP9btvhcDhOJU960pPs1NSUbbVaw2lZltlzzjnHbt261Rpj7DnnnGNf/OIX3+96fv3Xf/2k92NrrQVso9Gwy8vLJ/1s433wF37hF6zv+/aWW265z20N7slXXHHFCdP/5V/+xQL22muvvd99HTwjrr/++uG0wfNkdnbWpmlqV1ZW7Ac+8AFbKpXstm3bbK/XO2HZk72U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", + "text/plain": [ + "

" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "!wget -q https://s3.amazonaws.com/auxdata.johnsnowlabs.com/public/resources/en/images/images.zip\n", + "import shutil\n", + "shutil.unpack_archive(\"images.zip\", \"images\", \"zip\")\n", + "\n", + "from PIL import Image\n", + "import matplotlib.pyplot as plt\n", + "import os\n", + "\n", + "_, axes = plt.subplots(2, 5, figsize=(10,5))\n", + "axes = axes.flatten()\n", + "\n", + "i = 0\n", + "images_path = \"images/images/\"\n", + "for file_name in os.listdir(images_path):\n", + " if file_name.lower().endswith((\".png\", \".jpg\", \".jpeg\", \".gif\")):\n", + " file_path = os.path.join(\"images/images/\", file_name)\n", + " ax = axes[i]\n", + " ax.imshow(Image.open(file_path).convert(\"RGB\"))\n", + " ax.title.set_text(file_name)\n", + " ax.axis(\"off\")\n", + " i += 1\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can load the images to Spark.\n", + "\n", + "**NOTE**: The llama.cpp backend of the annotator expects a different image byte format than the default format used by Spark. This annotator expects *raw* image bytes, instead of the OpenCV image compatible format, which is used by default.\n", + "\n", + "For this, we can use the helper function `loadImagesAsBytes` from the `ImageAssembler`. It will load the images in the right format in a Spark DataFrame. Additionally, we will add a column for the caption:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sparknlp.base import *\n", + "from pyspark.sql.functions import lit\n", + "\n", + "data = ImageAssembler.loadImagesAsBytes(spark, images_path)\n", + "# Add a caption to each image.\n", + "data = data.withColumn(\"caption\", lit(\"Caption this image.\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now We need an `ImageAssembler` and `DocumentAssembler` to turn the images and captions into the right format for Spark NLP. We also load the model we just saved above. Then we can assemble a pipeline and run it!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "25/01/18 13:46:33 WARN DAGScheduler: Broadcasting large task binary with size 1090.9 KiB\n", + "clip_model_load: model name: openai/clip-vit-large-patch14-336 (0 + 1) / 1]\n", + "clip_model_load: description: image encoder for LLaVA\n", + "clip_model_load: GGUF version: 3\n", + "clip_model_load: alignment: 32\n", + "clip_model_load: n_tensors: 377\n", + "clip_model_load: n_kv: 19\n", + "clip_model_load: ftype: q4_0\n", + "\n", + "clip_model_load: loaded meta data with 19 key-value pairs and 377 tensors from /tmp/spark-5acddb2b-4bca-474e-befd-d8613d27a78e/userFiles-4926735e-f265-46bc-8a9f-9edb6a65484e/llava-v1.5-7b-mmproj-Q4_0.gguf\n", + "clip_model_load: Dumping metadata keys/values. Note: KV overrides do not apply in this output.\n", + "clip_model_load: - kv 0: general.architecture str = clip\n", + "clip_model_load: - kv 1: clip.has_text_encoder bool = false\n", + "clip_model_load: - kv 2: clip.has_vision_encoder bool = true\n", + "clip_model_load: - kv 3: clip.has_llava_projector bool = true\n", + "clip_model_load: - kv 4: general.file_type u32 = 2\n", + "clip_model_load: - kv 5: general.name str = openai/clip-vit-large-patch14-336\n", + "clip_model_load: - kv 6: general.description str = image encoder for LLaVA\n", + "clip_model_load: - kv 7: clip.vision.image_size u32 = 336\n", + "clip_model_load: - kv 8: clip.vision.patch_size u32 = 14\n", + "clip_model_load: - kv 9: clip.vision.embedding_length u32 = 1024\n", + "clip_model_load: - kv 10: clip.vision.feed_forward_length u32 = 4096\n", + "clip_model_load: - kv 11: clip.vision.projection_dim u32 = 768\n", + "clip_model_load: - kv 12: clip.vision.attention.head_count u32 = 16\n", + "clip_model_load: - kv 13: clip.vision.attention.layer_norm_epsilon f32 = 0.000010\n", + "clip_model_load: - kv 14: clip.vision.block_count u32 = 23\n", + "clip_model_load: - kv 15: clip.vision.image_mean arr[f32,3] = [0.481455, 0.457828, 0.408211]\n", + "clip_model_load: - kv 16: clip.vision.image_std arr[f32,3] = [0.268630, 0.261303, 0.275777]\n", + "clip_model_load: - kv 17: clip.use_gelu bool = false\n", + "clip_model_load: - kv 18: general.quantization_version u32 = 2\n", + "clip_model_load: - type f32: 235 tensors\n", + "clip_model_load: - type f16: 1 tensors\n", + "clip_model_load: - type q4_0: 141 tensors\n", + "ggml_cuda_init: GGML_CUDA_FORCE_MMQ: no\n", + "ggml_cuda_init: GGML_CUDA_FORCE_CUBLAS: no\n", + "ggml_cuda_init: found 1 CUDA devices:\n", + " Device 0: NVIDIA GeForce RTX 3070, compute capability 8.6, VMM: yes\n", + "clip_model_load: CLIP using CUDA backend\n", + "clip_model_load: text_encoder: 0\n", + "clip_model_load: vision_encoder: 1\n", + "clip_model_load: llava_projector: 1\n", + "clip_model_load: model size: 169.18 MB\n", + "clip_model_load: metadata size: 0.13 MB\n", + "clip_model_load: params backend buffer size = 169.18 MB (377 tensors)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] build info build=3534 commit=\"641f5dd2\"\n", + "[INFO] system info n_threads=6 n_threads_batch=-1 total_threads=6 system_info=\"AVX = 1 | AVX_VNNI = 0 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | AVX512_BF16 = 0 | FMA = 1 | NEON = 0 | SVE = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 | MATMUL_INT8 = 0 | LLAMAFILE = 1 | \"\n", + "[INFO] Multi Modal Mode Enabled\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "key clip.vision.image_grid_pinpoints not found in file\n", + "key clip.vision.mm_patch_merge_type not found in file\n", + "key clip.vision.image_crop_resolution not found in file\n", + "ggml_gallocr_reserve_n: reallocating CUDA0 buffer from size 0.00 MiB to 32.89 MiB\n", + "clip_model_load: compute allocated memory: 32.89 MB\n", + "llama_model_loader: loaded meta data with 19 key-value pairs and 291 tensors from /tmp/spark-5acddb2b-4bca-474e-befd-d8613d27a78e/userFiles-4926735e-f265-46bc-8a9f-9edb6a65484e/llava-v1.5-7b-Q4_K.gguf (version GGUF V3 (latest))\n", + "llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.\n", + "llama_model_loader: - kv 0: general.architecture str = llama\n", + "llama_model_loader: - kv 1: general.name str = LLaMA v2\n", + "llama_model_loader: - kv 2: llama.context_length u32 = 4096\n", + "llama_model_loader: - kv 3: llama.embedding_length u32 = 4096\n", + "llama_model_loader: - kv 4: llama.block_count u32 = 32\n", + "llama_model_loader: - kv 5: llama.feed_forward_length u32 = 11008\n", + "llama_model_loader: - kv 6: llama.rope.dimension_count u32 = 128\n", + "llama_model_loader: - kv 7: llama.attention.head_count u32 = 32\n", + "llama_model_loader: - kv 8: llama.attention.head_count_kv u32 = 32\n", + "llama_model_loader: - kv 9: llama.attention.layer_norm_rms_epsilon f32 = 0.000010\n", + "llama_model_loader: - kv 10: general.file_type u32 = 15\n", + "llama_model_loader: - kv 11: tokenizer.ggml.model str = llama\n", + "llama_model_loader: - kv 12: tokenizer.ggml.tokens arr[str,32000] = [\"\", \"\", \"\", \"<0x00>\", \"<...\n", + "llama_model_loader: - kv 13: tokenizer.ggml.scores arr[f32,32000] = [0.000000, 0.000000, 0.000000, 0.0000...\n", + "llama_model_loader: - kv 14: tokenizer.ggml.token_type arr[i32,32000] = [2, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, ...\n", + "llama_model_loader: - kv 15: tokenizer.ggml.bos_token_id u32 = 1\n", + "llama_model_loader: - kv 16: tokenizer.ggml.eos_token_id u32 = 2\n", + "llama_model_loader: - kv 17: tokenizer.ggml.padding_token_id u32 = 0\n", + "llama_model_loader: - kv 18: general.quantization_version u32 = 2\n", + "llama_model_loader: - type f32: 65 tensors\n", + "llama_model_loader: - type q4_K: 193 tensors\n", + "llama_model_loader: - type q6_K: 33 tensors\n", + "llm_load_vocab: special tokens cache size = 3\n", + "llm_load_vocab: token to piece cache size = 0.1684 MB\n", + "llm_load_print_meta: format = GGUF V3 (latest)\n", + "llm_load_print_meta: arch = llama\n", + "llm_load_print_meta: vocab type = SPM\n", + "llm_load_print_meta: n_vocab = 32000\n", + "llm_load_print_meta: n_merges = 0\n", + "llm_load_print_meta: vocab_only = 0\n", + "llm_load_print_meta: n_ctx_train = 4096\n", + "llm_load_print_meta: n_embd = 4096\n", + "llm_load_print_meta: n_layer = 32\n", + "llm_load_print_meta: n_head = 32\n", + "llm_load_print_meta: n_head_kv = 32\n", + "llm_load_print_meta: n_rot = 128\n", + "llm_load_print_meta: n_swa = 0\n", + "llm_load_print_meta: n_embd_head_k = 128\n", + "llm_load_print_meta: n_embd_head_v = 128\n", + "llm_load_print_meta: n_gqa = 1\n", + "llm_load_print_meta: n_embd_k_gqa = 4096\n", + "llm_load_print_meta: n_embd_v_gqa = 4096\n", + "llm_load_print_meta: f_norm_eps = 0.0e+00\n", + "llm_load_print_meta: f_norm_rms_eps = 1.0e-05\n", + "llm_load_print_meta: f_clamp_kqv = 0.0e+00\n", + "llm_load_print_meta: f_max_alibi_bias = 0.0e+00\n", + "llm_load_print_meta: f_logit_scale = 0.0e+00\n", + "llm_load_print_meta: n_ff = 11008\n", + "llm_load_print_meta: n_expert = 0\n", + "llm_load_print_meta: n_expert_used = 0\n", + "llm_load_print_meta: causal attn = 1\n", + "llm_load_print_meta: pooling type = 0\n", + "llm_load_print_meta: rope type = 0\n", + "llm_load_print_meta: rope scaling = linear\n", + "llm_load_print_meta: freq_base_train = 10000.0\n", + "llm_load_print_meta: freq_scale_train = 1\n", + "llm_load_print_meta: n_ctx_orig_yarn = 4096\n", + "llm_load_print_meta: rope_finetuned = unknown\n", + "llm_load_print_meta: ssm_d_conv = 0\n", + "llm_load_print_meta: ssm_d_inner = 0\n", + "llm_load_print_meta: ssm_d_state = 0\n", + "llm_load_print_meta: ssm_dt_rank = 0\n", + "llm_load_print_meta: model type = 7B\n", + "llm_load_print_meta: model ftype = Q4_K - Medium\n", + "llm_load_print_meta: model params = 6.74 B\n", + "llm_load_print_meta: model size = 3.80 GiB (4.84 BPW) \n", + "llm_load_print_meta: general.name = LLaMA v2\n", + "llm_load_print_meta: BOS token = 1 ''\n", + "llm_load_print_meta: EOS token = 2 ''\n", + "llm_load_print_meta: UNK token = 0 ''\n", + "llm_load_print_meta: PAD token = 0 ''\n", + "llm_load_print_meta: LF token = 13 '<0x0A>'\n", + "llm_load_print_meta: max token length = 48\n", + "llm_load_tensors: ggml ctx size = 0.27 MiB\n", + "llm_load_tensors: offloading 32 repeating layers to GPU\n", + "llm_load_tensors: offloading non-repeating layers to GPU\n", + "llm_load_tensors: offloaded 33/33 layers to GPU\n", + "llm_load_tensors: CPU buffer size = 70.31 MiB\n", + "llm_load_tensors: CUDA0 buffer size = 3820.94 MiB\n", + "..................................................................................................\n", + "llama_new_context_with_model: n_ctx = 4096\n", + "llama_new_context_with_model: n_batch = 512\n", + "llama_new_context_with_model: n_ubatch = 512\n", + "llama_new_context_with_model: flash_attn = 0\n", + "llama_new_context_with_model: freq_base = 10000.0\n", + "llama_new_context_with_model: freq_scale = 1\n", + "llama_kv_cache_init: CUDA0 KV buffer size = 2048.00 MiB\n", + "llama_new_context_with_model: KV self size = 2048.00 MiB, K (f16): 1024.00 MiB, V (f16): 1024.00 MiB\n", + "llama_new_context_with_model: CUDA_Host output buffer size = 0.12 MiB\n", + "ggml_gallocr_reserve_n: reallocating CUDA0 buffer from size 0.00 MiB to 296.00 MiB\n", + "ggml_gallocr_reserve_n: reallocating CUDA_Host buffer from size 0.00 MiB to 16.01 MiB\n", + "llama_new_context_with_model: CUDA0 compute buffer size = 296.00 MiB\n", + "llama_new_context_with_model: CUDA_Host compute buffer size = 16.01 MiB\n", + "llama_new_context_with_model: graph nodes = 1030\n", + "llama_new_context_with_model: graph splits = 2\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] initializing slots n_slots=1\n", + "[INFO] new slot slot_id=0 n_ctx_slot=4096\n", + "[INFO] model loaded\n", + "[INFO] chat template chat_example=\"You are a helpful assistant\\n\\nUSER: Hello\\nASSISTANT: Hi there\\nUSER: How are you?\\nASSISTANT:\" built_in=false\n", + "[INFO] all slots are idle and system prompt is empty, clear the KV cache\n", + "[INFO] slot is processing task slot_id=0 task_id=0\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=0 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 76.17 ms by CLIP ( 0.13 ms per image patch)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "llama_output_reserve: reallocating output buffer from size 0.12 MiB to 1.22 MiB\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 481.17 ms / 1 tokens ( 481.17 ms per token, 2.08 tokens per second) slot_id=0 task_id=0 t_prompt_processing=481.165 n_prompt_tokens_processed=1 t_token=481.165 n_tokens_second=2.078289152369769\n", + "[INFO] generation eval time = 757.27 ms / 40 runs ( 18.93 ms per token, 52.82 tokens per second) slot_id=0 task_id=0 t_token_generation=757.271 n_decoded=40 t_token=18.931775 n_tokens_second=52.821248932020374\n", + "[INFO] total time = 1238.44 ms slot_id=0 task_id=0 t_prompt_processing=481.165 t_token_generation=757.271 t_total=1238.436\n", + "[INFO] slot released slot_id=0 task_id=0 n_ctx=4096 n_past=632 n_system_tokens=0 n_cache_tokens=41 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=1\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=1 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 48.94 ms by CLIP ( 0.08 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 418.86 ms / 1 tokens ( 418.86 ms per token, 2.39 tokens per second) slot_id=0 task_id=1 t_prompt_processing=418.858 n_prompt_tokens_processed=1 t_token=418.858 n_tokens_second=2.387443954753162\n", + "[INFO] generation eval time = 760.78 ms / 40 runs ( 19.02 ms per token, 52.58 tokens per second) slot_id=0 task_id=1 t_token_generation=760.785 n_decoded=40 t_token=19.019624999999998 n_tokens_second=52.57727215967718\n", + "[INFO] total time = 1179.64 ms slot_id=0 task_id=1 t_prompt_processing=418.858 t_token_generation=760.785 t_total=1179.643\n", + "[INFO] slot released slot_id=0 task_id=1 n_ctx=4096 n_past=632 n_system_tokens=0 n_cache_tokens=41 truncated=false\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "25/01/18 13:46:37 WARN DAGScheduler: Broadcasting large task binary with size 1090.9 KiB\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] slot is processing task slot_id=0 task_id=84\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=84 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 51.93 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 434.93 ms / 1 tokens ( 434.93 ms per token, 2.30 tokens per second) slot_id=0 task_id=84 t_prompt_processing=434.926 n_prompt_tokens_processed=1 t_token=434.926 n_tokens_second=2.2992417100840146\n", + "[INFO] generation eval time = 759.00 ms / 40 runs ( 18.98 ms per token, 52.70 tokens per second) slot_id=0 task_id=84 t_token_generation=759.003 n_decoded=40 t_token=18.975075 n_tokens_second=52.70071396292241\n", + "[INFO] total time = 1193.93 ms slot_id=0 task_id=84 t_prompt_processing=434.926 t_token_generation=759.003 t_total=1193.929\n", + "[INFO] slot released slot_id=0 task_id=84 n_ctx=4096 n_past=632 n_system_tokens=0 n_cache_tokens=41 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=85\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=85 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 49.35 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "encode_image_with_clip: image embedding created: 576 tokens (1 + 3) / 4]\n", + "\n", + "encode_image_with_clip: image encoded in 50.33 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 418.23 ms / 1 tokens ( 418.23 ms per token, 2.39 tokens per second) slot_id=0 task_id=85 t_prompt_processing=418.234 n_prompt_tokens_processed=1 t_token=418.234 n_tokens_second=2.391005991861016\n", + "[INFO] generation eval time = 310.67 ms / 17 runs ( 18.27 ms per token, 54.72 tokens per second) slot_id=0 task_id=85 t_token_generation=310.665 n_decoded=17 t_token=18.274411764705885 n_tokens_second=54.72132361225113\n", + "[INFO] total time = 728.90 ms slot_id=0 task_id=85 t_prompt_processing=418.234 t_token_generation=310.665 t_total=728.899\n", + "[INFO] slot released slot_id=0 task_id=85 n_ctx=4096 n_past=609 n_system_tokens=0 n_cache_tokens=18 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=87\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=87 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 423.11 ms / 1 tokens ( 423.11 ms per token, 2.36 tokens per second) slot_id=0 task_id=87 t_prompt_processing=423.106 n_prompt_tokens_processed=1 t_token=423.106 n_tokens_second=2.3634739285190944\n", + "[INFO] generation eval time = 771.11 ms / 40 runs ( 19.28 ms per token, 51.87 tokens per second) slot_id=0 task_id=87 t_token_generation=771.106 n_decoded=40 t_token=19.27765 n_tokens_second=51.873542677660396\n", + "[INFO] total time = 1194.21 ms slot_id=0 task_id=87 t_prompt_processing=423.106 t_token_generation=771.106 t_total=1194.212\n", + "[INFO] slot released slot_id=0 task_id=87 n_ctx=4096 n_past=632 n_system_tokens=0 n_cache_tokens=41 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=88\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=88 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 50.07 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 423.79 ms / 1 tokens ( 423.79 ms per token, 2.36 tokens per second) slot_id=0 task_id=88 t_prompt_processing=423.79 n_prompt_tokens_processed=1 t_token=423.79 n_tokens_second=2.359659265202105\n", + "[INFO] generation eval time = 251.86 ms / 14 runs ( 17.99 ms per token, 55.59 tokens per second) slot_id=0 task_id=88 t_token_generation=251.863 n_decoded=14 t_token=17.990214285714284 n_tokens_second=55.58577480614461\n", + "[INFO] total time = 675.65 ms slot_id=0 task_id=88 t_prompt_processing=423.79 t_token_generation=251.863 t_total=675.653\n", + "[INFO] slot released slot_id=0 task_id=88 n_ctx=4096 n_past=606 n_system_tokens=0 n_cache_tokens=15 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=89\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=89 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 49.78 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 50.26 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 422.05 ms / 1 tokens ( 422.05 ms per token, 2.37 tokens per second) slot_id=0 task_id=89 t_prompt_processing=422.047 n_prompt_tokens_processed=1 t_token=422.047 n_tokens_second=2.369404355439086\n", + "[INFO] generation eval time = 351.31 ms / 19 runs ( 18.49 ms per token, 54.08 tokens per second) slot_id=0 task_id=89 t_token_generation=351.31 n_decoded=19 t_token=18.49 n_tokens_second=54.08328826392644\n", + "[INFO] total time = 773.36 ms slot_id=0 task_id=89 t_prompt_processing=422.047 t_token_generation=351.31 t_total=773.357\n", + "[INFO] slot released slot_id=0 task_id=89 n_ctx=4096 n_past=611 n_system_tokens=0 n_cache_tokens=20 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=90\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=90 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 419.07 ms / 1 tokens ( 419.07 ms per token, 2.39 tokens per second) slot_id=0 task_id=90 t_prompt_processing=419.071 n_prompt_tokens_processed=1 t_token=419.071 n_tokens_second=2.386230495548487\n", + "[INFO] generation eval time = 768.85 ms / 40 runs ( 19.22 ms per token, 52.03 tokens per second) slot_id=0 task_id=90 t_token_generation=768.849 n_decoded=40 t_token=19.221225 n_tokens_second=52.0258204146718\n", + "[INFO] total time = 1187.92 ms slot_id=0 task_id=90 t_prompt_processing=419.071 t_token_generation=768.849 t_total=1187.92\n", + "[INFO] slot released slot_id=0 task_id=90 n_ctx=4096 n_past=632 n_system_tokens=0 n_cache_tokens=41 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=91\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=91 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 49.82 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 424.45 ms / 1 tokens ( 424.45 ms per token, 2.36 tokens per second) slot_id=0 task_id=91 t_prompt_processing=424.45 n_prompt_tokens_processed=1 t_token=424.45 n_tokens_second=2.3559901048415597\n", + "[INFO] generation eval time = 761.95 ms / 40 runs ( 19.05 ms per token, 52.50 tokens per second) slot_id=0 task_id=91 t_token_generation=761.953 n_decoded=40 t_token=19.048825 n_tokens_second=52.49667630418149\n", + "[INFO] total time = 1186.40 ms slot_id=0 task_id=91 t_prompt_processing=424.45 t_token_generation=761.953 t_total=1186.403\n", + "[INFO] slot released slot_id=0 task_id=91 n_ctx=4096 n_past=632 n_system_tokens=0 n_cache_tokens=41 truncated=false\n", + "[INFO] slot is processing task slot_id=0 task_id=92\n", + "[INFO] kv cache rm [p0, end) slot_id=0 task_id=92 p0=0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "encode_image_with_clip: image embedding created: 576 tokens\n", + "\n", + "encode_image_with_clip: image encoded in 49.04 ms by CLIP ( 0.09 ms per image patch)\n", + "ggml_gallocr_needs_realloc: node inp_embd is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 1)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] prompt eval time = 417.26 ms / 1 tokens ( 417.26 ms per token, 2.40 tokens per second) slot_id=0 task_id=92 t_prompt_processing=417.263 n_prompt_tokens_processed=1 t_token=417.263 n_tokens_second=2.3965700289745318\n", + "[INFO] generation eval time = 329.49 ms / 18 runs ( 18.31 ms per token, 54.63 tokens per second) slot_id=0 task_id=92 t_token_generation=329.493 n_decoded=18 t_token=18.305166666666665 n_tokens_second=54.629385146270174\n", + "[INFO] total time = 746.76 ms slot_id=0 task_id=92 t_prompt_processing=417.263 t_token_generation=329.493 t_total=746.756\n", + "[INFO] slot released slot_id=0 task_id=92 n_ctx=4096 n_past=610 n_system_tokens=0 n_cache_tokens=19 truncated=false\n", + "+-----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|image_name |result |\n", + "+-----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|palace.JPEG |[ The image depicts a large, ornate room with high ceilings and yellow walls. It features an elegant sitting area with several chairs arranged around the space. There are also multiple c] |\n", + "|egyptian_cat.jpeg|[ The image features two cats lying on a pink surface, possibly a bed or sofa. One cat is positioned towards the left side of the frame and appears to be sleeping while holding] |\n", + "|hippopotamus.JPEG|[ A large brown hippo is swimming in a pond, with its head above the water. The hippo appears to be enjoying itself as it floats on top of the water.] |\n", + "|hen.JPEG |[ The image features a large white chicken standing next to several baby chicks. There are at least five visible chickens in the scene, with one adult and four young ones surrounding it. They]|\n", + "|ostrich.JPEG |[ A large ostrich stands in a grassy field, surrounded by trees and bushes. The bird is the main focus of the image with its long neck stretched out as it looks around at] |\n", + "|junco.JPEG |[ A small bird with a black head and white chest is standing on the snow.] |\n", + "|bluetick.jpg |[ A dog with a red collar is sitting on the floor.] |\n", + "|chihuahua.jpg |[ A small brown dog wearing a sweater and collar is sitting on the floor.] |\n", + "|tractor.JPEG |[ A man is sitting in the driver's seat of a green tractor, which has yellow wheels. The tractor appears to be parked on top of an agricultural field with rows of] |\n", + "|ox.JPEG |[ A large bull with long horns is standing in a grassy field.] |\n", + "+-----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "ggml_gallocr_needs_realloc: src 0 (KQ_mask) of node KQ_mask (view) is not valid\n", + "ggml_gallocr_alloc_graph: cannot reallocate multi buffer graph automatically, call reserve\n", + "ggml_backend_sched_alloc_splits: failed to allocate graph, reserving (backend_ids_changed = 0)\n", + " \r" + ] + } + ], + "source": [ + "import sparknlp\n", + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.ml import Pipeline\n", + "\n", + "documentAssembler = (\n", + " DocumentAssembler().setInputCol(\"caption\").setOutputCol(\"caption_document\")\n", + ")\n", + "imageAssembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n", + "model = AutoGGUFVisionModel.load(\"llava_v1.5_7b_Q4_0_gguf_spark_nlp\")\n", + "pipeline = Pipeline().setStages([documentAssembler, imageAssembler, model])\n", + "\n", + "pipeline.fit(data).transform(data).selectExpr(\n", + " \"reverse(split(image.origin, '/'))[0] as image_name\", \"completions.result\"\n", + ").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That's it! You can now go wild and use hundreds of GGUF models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "sparknlp_dev", + "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" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py b/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py index 83d3a6abf29c74..9d7fd977bd1633 100755 --- a/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py +++ b/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py @@ -36,8 +36,8 @@ class AutoGGUFVisionModel(AnnotatorModel, HasBatchedAnnotate, HasLlamaCppPropert .. code-block:: python - autoGGUFModel = AutoGGUFModel.pretrained() \\ - .setInputCols(["image', "document"]) \\ + autoGGUFVisionModel = AutoGGUFVisionModel.pretrained() \\ + .setInputCols(["image", "document"]) \\ .setOutputCol("completions") @@ -48,7 +48,7 @@ class AutoGGUFVisionModel(AnnotatorModel, HasBatchedAnnotate, HasLlamaCppPropert For extended examples of usage, see the `AutoGGUFVisionModelTest `__ and the - `example notebook `__. + `example notebook `__. ====================== ====================== Input Annotation types Output Annotation type @@ -231,7 +231,6 @@ class AutoGGUFVisionModel(AnnotatorModel, HasBatchedAnnotate, HasLlamaCppPropert >>> model = AutoGGUFVisionModel.pretrained() \\ ... .setInputCols(["caption_document", "image_assembler"]) \\ ... .setOutputCol("completions") \\ - ... .setChatTemplate("vicuna") \\ ... .setBatchSize(4) \\ ... .setNGpuLayers(99) \\ ... .setNCtx(4096) \\ diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala index dbce009f356397..6182728d92cd47 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala @@ -43,7 +43,7 @@ import org.apache.spark.sql.SparkSession * * Pretrained models can be loaded with `pretrained` of the companion object: * {{{ - * val autoGGUFModel = AutoGGUFModel.pretrained() + * val autoGGUFVisionModel = AutoGGUFVisionModel.pretrained() * .setInputCols("image', "document") * .setOutputCol("completions") * }}} @@ -54,7 +54,7 @@ import org.apache.spark.sql.SparkSession * For extended examples of usage, see the * [[https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTest.scala AutoGGUFVisionModelTest]] * and the - * [[https://github.com/JohnSnowLabs/spark-nlp/tree/master/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFModel.ipynb example notebook]]. + * [[https://github.com/JohnSnowLabs/spark-nlp/tree/master/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb example notebook]]. * * ==Note== * To use GPU inference with this annotator, make sure to use the Spark NLP GPU package and set @@ -90,7 +90,6 @@ import org.apache.spark.sql.SparkSession * val model = AutoGGUFVisionModel.pretrained() * .setInputCols("caption_document", "image_assembler") * .setOutputCol("completions") - * .setChatTemplate("vicuna") // llava uses vicuna as default * .setBatchSize(4) * .setNGpuLayers(99) * .setNCtx(4096) From 0f5d0736a1aaea73134395153a8566ec139ef147 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Sat, 18 Jan 2025 18:02:53 +0100 Subject: [PATCH 016/108] [SPARKNLP-1079] Bump jsl-llamacpp version --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fae6267df57f21..620ed6b95015eb 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -128,7 +128,7 @@ object Dependencies { val azureIdentity = "com.azure" % "azure-identity" % azureIdentityVersion % Provided val azureStorage = "com.azure" % "azure-storage-blob" % azureStorageVersion % Provided - val llamaCppVersion = "0.1.4" + val llamaCppVersion = "0.1.5" val llamaCppCPU = "com.johnsnowlabs.nlp" %% "jsl-llamacpp-cpu" % llamaCppVersion val llamaCppGPU = "com.johnsnowlabs.nlp" %% "jsl-llamacpp-gpu" % llamaCppVersion val llamaCppSilicon = "com.johnsnowlabs.nlp" %% "jsl-llamacpp-silicon" % llamaCppVersion From 998d1f5f876c2d9b56a7894d32ccf2d476a6e85d Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 24 Jan 2025 12:06:21 +0100 Subject: [PATCH 017/108] [SPARKNLP-1079] AutoGGUFVisionModel pretrained model --- .../nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala index 41a600afc15255..495822e3b212e1 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala @@ -42,10 +42,7 @@ class AutoGGUFVisionModelTestSpec extends AnyFlatSpec { lazy val nPredict = 40 lazy val model = AutoGGUFVisionModel - .loadSavedModel( - "models/llava-v1.5-7b-Q4_0.gguf", - "models/llava-v1.5-7b-mmproj-model-f16.gguf", - ResourceHelper.spark) + .pretrained() .setInputCols("caption_document", "image_assembler") .setOutputCol("completions") .setChatTemplate("vicuna") // llava uses vicuna as default From 73cd3ad86bd13043bea5d99788b70c1f96a2dd96 Mon Sep 17 00:00:00 2001 From: ahmedlone127 Date: Wed, 29 Jan 2025 18:20:59 +0500 Subject: [PATCH 018/108] fixing typo in MXBAI notebook --- .../HuggingFace_ONNX_in_Spark_NLP_mxbai.ipynb | 144 +++++++++--------- 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_mxbai.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_mxbai.ipynb index c09cea1432b6ca..e8ae44495a6288 100644 --- a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_mxbai.ipynb +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_mxbai.ipynb @@ -10,9 +10,9 @@ "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_mxbai.ipynb)\n", "\n", - "# Import ONNX mxbai models from HuggingFace \ud83e\udd17 into Spark NLP \ud83d\ude80\n", + "# Import ONNX mxbai models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", "\n", - "Let's keep in mind a few things before we start \ud83d\ude0a\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", "- ONNX support was introduced in `Spark NLP 5.0.0`, enabling high performance inference for models. Please make sure you have upgraded to the latest Spark NLP release.\n", "- You can import models for mxbai from HuggingFace and they have to be in `Fill Mask` category. Meaning, you cannot use mxbai models trained/fine-tuned on a specific task such as token/sequence classification." @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "id": "faBcByOA-LBV", "outputId": "9d84ba9f-c8ac-4c2c-8bae-8068890262ab", @@ -52,23 +52,23 @@ "output_type": "stream", "name": "stdout", "text": [ - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m1.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m8.8/8.8 MB\u001b[0m \u001b[31m28.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m14.6/14.6 MB\u001b[0m \u001b[31m41.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m421.5/421.5 kB\u001b[0m \u001b[31m23.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m13.2/13.2 MB\u001b[0m \u001b[31m53.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m212.7/212.7 kB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m3.6/3.6 MB\u001b[0m \u001b[31m67.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m2.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m527.3/527.3 kB\u001b[0m \u001b[31m26.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m83.8/83.8 kB\u001b[0m \u001b[31m4.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m455.8/455.8 kB\u001b[0m \u001b[31m24.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m6.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m39.9/39.9 MB\u001b[0m \u001b[31m13.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m4.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m55.5/55.5 kB\u001b[0m \u001b[31m3.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m1.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m8.8/8.8 MB\u001b[0m \u001b[31m28.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m14.6/14.6 MB\u001b[0m \u001b[31m41.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m421.5/421.5 kB\u001b[0m \u001b[31m23.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.2/13.2 MB\u001b[0m \u001b[31m53.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m212.7/212.7 kB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m3.6/3.6 MB\u001b[0m \u001b[31m67.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m2.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m527.3/527.3 kB\u001b[0m \u001b[31m26.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m83.8/83.8 kB\u001b[0m \u001b[31m4.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m455.8/455.8 kB\u001b[0m \u001b[31m24.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m6.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m39.9/39.9 MB\u001b[0m \u001b[31m13.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m4.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.5/55.5 kB\u001b[0m \u001b[31m3.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", "cudf-cu12 24.4.1 requires pyarrow<15.0.0a0,>=14.0.1, but you have pyarrow 17.0.0 which is incompatible.\n", "ibis-framework 8.0.0 requires pyarrow<16,>=2, but you have pyarrow 17.0.0 which is incompatible.\u001b[0m\u001b[31m\n", @@ -93,7 +93,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "id": "NBrJz3Qt-LBX", "outputId": "1a2d3a70-b990-48fc-9208-385a0b5fff05", @@ -347,7 +347,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "id": "o2wua50w-LBY", "outputId": "6a89e07d-a509-4d63-b983-576bf64cbf7d", @@ -376,7 +376,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "id": "97ScuGul-LBY", "outputId": "4a1d4520-2ab8-4dc4-f3a5-cc03ac2340c6", @@ -412,7 +412,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "id": "dxCEAixU-LBZ", "outputId": "fe6c7e6f-e793-45d7-9b44-0aa8a0ff1f4b", @@ -427,17 +427,18 @@ "text": [ "Installing PySpark 3.2.3 and Spark NLP 5.4.2\n", "setup Colab for PySpark 3.2.3 and Spark NLP 5.4.2\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m281.5/281.5 MB\u001b[0m \u001b[31m5.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m281.5/281.5 MB\u001b[0m \u001b[31m5.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", "\u001b[?25h Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m55.6/55.6 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m579.5/579.5 kB\u001b[0m \u001b[31m22.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m199.7/199.7 kB\u001b[0m \u001b[31m13.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.6/55.6 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m579.5/579.5 kB\u001b[0m \u001b[31m22.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m199.7/199.7 kB\u001b[0m \u001b[31m13.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", "\u001b[?25h Building wheel for pyspark (setup.py) ... \u001b[?25l\u001b[?25hdone\n" ] } ], "source": [ - "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash\n", + "!pip install pyspark==3.5.0" ] }, { @@ -451,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "id": "tWzqJOSe-LBb", "outputId": "b5797072-fd37-4d41-ab22-d88007c74f60", @@ -466,9 +467,9 @@ "text": [ "Collecting spark-nlp==5.5.0rc1\n", " Downloading spark_nlp-5.5.0rc1-py2.py3-none-any.whl.metadata (55 kB)\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m55.8/55.8 kB\u001b[0m \u001b[31m891.3 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.8/55.8 kB\u001b[0m \u001b[31m891.3 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", "\u001b[?25hDownloading spark_nlp-5.5.0rc1-py2.py3-none-any.whl (629 kB)\n", - "\u001b[2K \u001b[90m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u001b[0m \u001b[32m629.6/629.6 kB\u001b[0m \u001b[31m4.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m629.6/629.6 kB\u001b[0m \u001b[31m4.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", "\u001b[?25hInstalling collected packages: spark-nlp\n", " Attempting uninstall: spark-nlp\n", " Found existing installation: spark-nlp 5.4.2\n", @@ -486,7 +487,12 @@ ] } ], - "source": "import sparknlp\n# let's start Spark with Spark NLP\nspark = sparknlp.start()\"\n " + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n", + "" + ] }, { "cell_type": "markdown", @@ -497,7 +503,7 @@ "- Let's use `loadSavedModel` functon in `mxbaiEmbeddings` which allows us to load the ONNX model\n", "- Most params will be set automatically. They can also be set later after loading the model in `mxbaiEmbeddings` during runtime, so don't worry about setting them now\n", "- `loadSavedModel` accepts two params, first is the path to the exported model. The second is the SparkSession that is `spark` variable we previously started via `sparknlp.start()`\n", - "- `setStorageRef` is very important. When you are training a task like NER or any Text Classification, we use this reference to bound the trained model to this specific embeddings so you won't load a different embeddings by mistake and see terrible results \ud83d\ude0a\n", + "- `setStorageRef` is very important. When you are training a task like NER or any Text Classification, we use this reference to bound the trained model to this specific embeddings so you won't load a different embeddings by mistake and see terrible results ๐Ÿ˜Š\n", "- It's up to you what you put in `setStorageRef` but it cannot be changed later on. We usually use the name of the model to be clear, but you can get creative if you want!\n", "- The `dimension` param is is purely cosmetic and won't change anything. It's mostly for you to know later via `.getDimension` what is the dimension of your model. So set this accordingly.\n", "- NOTE: `loadSavedModel` accepts local paths in addition to distributed file systems such as `HDFS`, `S3`, `DBFS`, etc. This feature was introduced in Spark NLP 4.2.2 release. Keep in mind the best and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively.st and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively.\n" @@ -505,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "id": "ZfRgnm5V-LBc" }, @@ -532,7 +538,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "id": "thmPSatB-LBc" }, @@ -552,7 +558,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "id": "-GbJfqzE-LBc" }, @@ -567,14 +573,14 @@ "id": "CfhLgj1U-LBd" }, "source": [ - "Awesome \ud83d\ude0e !\n", + "Awesome ๐Ÿ˜Ž !\n", "\n", - "This is your ONNX mxbai model from HuggingFace \ud83e\udd17 loaded and saved by Spark NLP \ud83d\ude80" + "This is your ONNX mxbai model from HuggingFace ๐Ÿค— loaded and saved by Spark NLP ๐Ÿš€" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "id": "9irc4X-h-LBe", "colab": { @@ -604,12 +610,12 @@ "id": "q6kMLGGM-LBe" }, "source": [ - "Now let's see how we can use it on other machines, clusters, or any place you wish to use your new and shiny mxbai model \ud83d\ude0a" + "Now let's see how we can use it on other machines, clusters, or any place you wish to use your new and shiny mxbai model ๐Ÿ˜Š" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "id": "EuxOV23j-LBf" }, @@ -648,12 +654,12 @@ "metadata": { "id": "d3LjIpizF06G" }, - "execution_count": 12, + "execution_count": null, "outputs": [] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "id": "ayJxQu9P-LBf", "colab": { @@ -685,7 +691,7 @@ "id": "5YWVcqLf-LBf" }, "source": [ - "That's it! You can now go wild and use hundreds of mxbai models from HuggingFace \ud83e\udd17 in Spark NLP \ud83d\ude80\n" + "That's it! You can now go wild and use hundreds of mxbai models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" ] } ], @@ -749,9 +755,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_3dad1fc16bec46a6b35a7a8525c0299f", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_f9e07397159b4aaea2bf2786823b714d", - "value": "config.json:\u2007100%" + "value": "config.json:โ€‡100%" } }, "378a2015d90d4545bf1ab75c5e8865e4": { @@ -794,9 +800,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_6f3acd55795f42d3a9eb2a65815653fd", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_b11059c67669456ca3e5c0861cc0a028", - "value": "\u2007677/677\u2007[00:00<00:00,\u200742.3kB/s]" + "value": "โ€‡677/677โ€‡[00:00<00:00,โ€‡42.3kB/s]" } }, "ed2d71aae52d4b0083cfeb46ec9ba48f": { @@ -1091,9 +1097,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_295a6e5a5e654dc18ce6e88b9567f383", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_c91e229854ce48478f218791ad762f5b", - "value": "model.safetensors:\u2007100%" + "value": "model.safetensors:โ€‡100%" } }, "03b23811c34d485c90ea47ae226ac851": { @@ -1136,9 +1142,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_73c126216e6d4dc1b5c2aa3492d364f7", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_0a89bb30a86948aca977f9c58a111011", - "value": "\u2007670M/670M\u2007[00:06<00:00,\u2007130MB/s]" + "value": "โ€‡670M/670Mโ€‡[00:06<00:00,โ€‡130MB/s]" } }, "66ca1e17db024917a7b0a6d08083eec3": { @@ -1433,9 +1439,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_56cea75abf754df7a69b7c7f170077d8", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_81fa13469d0a4979a42578e18cb853ff", - "value": "tokenizer_config.json:\u2007100%" + "value": "tokenizer_config.json:โ€‡100%" } }, "b619910c1842468b8fe68be4e6de5a78": { @@ -1478,9 +1484,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_60b45e155a12462e959c2233a89de214", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_387e0f0772b044c78cceb488ac04c030", - "value": "\u20071.24k/1.24k\u2007[00:00<00:00,\u200735.5kB/s]" + "value": "โ€‡1.24k/1.24kโ€‡[00:00<00:00,โ€‡35.5kB/s]" } }, "039a16df2dc64a20be8ffd6c335d2c6a": { @@ -1775,9 +1781,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_ee50d01675ae4a3f88fe4d1a62afbc44", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_3f9d0d9adee04c7abccafa7ff21ae3e9", - "value": "vocab.txt:\u2007100%" + "value": "vocab.txt:โ€‡100%" } }, "a5741df93ecd40d280aa4404d32182d8": { @@ -1820,9 +1826,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_9464a43340ec4f83897268fa26556962", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_4e0fda0ee64b40d1bbe53db0a36df99e", - "value": "\u2007232k/232k\u2007[00:00<00:00,\u2007789kB/s]" + "value": "โ€‡232k/232kโ€‡[00:00<00:00,โ€‡789kB/s]" } }, "7818b970edd246018514956dd60b5876": { @@ -2117,9 +2123,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_b252854d497746959f8a2446450f4e7b", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_1855b9a52d4440ba9bea4fd1924efff6", - "value": "tokenizer.json:\u2007100%" + "value": "tokenizer.json:โ€‡100%" } }, "aea9930e5a1449f98702cdcc276c20cd": { @@ -2162,9 +2168,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_0e52a746cdfa44708dc0bc6573266fd4", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_7e8c9d3dc32248269742f5fa6193dfef", - "value": "\u2007711k/711k\u2007[00:00<00:00,\u20076.81MB/s]" + "value": "โ€‡711k/711kโ€‡[00:00<00:00,โ€‡6.81MB/s]" } }, "ee949690459545a2a3cac45986c3c75b": { @@ -2459,9 +2465,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_5536334d144c4b69a5daa9b23cebd6e6", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_cf6a5bac1e734decb1ec4777a14c30f8", - "value": "special_tokens_map.json:\u2007100%" + "value": "special_tokens_map.json:โ€‡100%" } }, "68562952ce664883afdce051240467a4": { @@ -2504,9 +2510,9 @@ "description": "", "description_tooltip": null, "layout": "IPY_MODEL_a375b3cbc1394df6bb4fd8b4045ab980", - "placeholder": "\u200b", + "placeholder": "โ€‹", "style": "IPY_MODEL_a5097fe765ff4af6865d0328986d233d", - "value": "\u2007695/695\u2007[00:00<00:00,\u200732.4kB/s]" + "value": "โ€‡695/695โ€‡[00:00<00:00,โ€‡32.4kB/s]" } }, "1a543a3eae694bfeab7b2a5f06f52431": { From cdab6bba3591da565ed92940e5caa3d7ffb9be2d Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 6 Feb 2025 04:11:46 +0000 Subject: [PATCH 019/108] Janus Scala API --- .../scala/com/johnsnowlabs/ml/ai/Janus.scala | 585 ++++++++++++++ .../ml/openvino/OpenvinoWrapper.scala | 9 + .../ml/util/LoadExternalModel.scala | 60 +- .../annotators/cv/JanusforMultiModal.scala | 712 ++++++++++++++++++ .../tokenizer/bpe/BpeTokenizer.scala | 8 + .../tokenizer/bpe/JanusTokenizer.scala | 120 +++ src/test/resources/images/image1.jpg | Bin 0 -> 404080 bytes .../cv/JanusForMultiModalTestSpec.scala | 189 +++++ 8 files changed, 1664 insertions(+), 19 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/JanusTokenizer.scala create mode 100644 src/test/resources/images/image1.jpg create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala b/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala new file mode 100644 index 00000000000000..a123b833a943ee --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala @@ -0,0 +1,585 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.ml.ai + +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.JanusWrappers +import com.johnsnowlabs.nlp.annotators.common.Sentence +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.annotators.common.SentenceSplit +import com.johnsnowlabs.nlp.annotators.cv.util.transform.ImageResizeUtils + +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils +import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{BpeTokenizer, JanusTokenizer, SpecialTokens} +import org.intel.openvino.InferRequest +import java.awt.{Color, Graphics2D} +import java.awt.image.BufferedImage + +import scala.collection.JavaConverters._ + +private[johnsnowlabs] class Janus( + val onnxWrappers: Option[DecoderWrappers], + val openvinoWrapper: Option[JanusWrappers], + merges: Map[(String, String), Int], + vocabulary: Map[String, Int], + addedTokens: Map[String, Int], + preprocessor: Preprocessor, + generationConfig: GenerationConfig, + imageTokenLength: Int, + imageToken: Int) + extends Serializable { + + val detectedEngine: String = + if (onnxWrappers.isDefined) ONNX.name + else if (openvinoWrapper.isDefined) Openvino.name + else Openvino.name + + private val GenerationConfig( + bosTokenId: Int, + paddingTokenId: Int, + eosTokenId: Int, + vocabSize: Int, + beginSuppressTokens, + suppressTokenIds, + forcedDecoderIds) = + generationConfig + val reversedVocabulary: Map[Int, String] = vocabulary.map(_.swap) + + val specialTokens: SpecialTokens = SpecialTokens( + vocabulary, + startTokenString = reversedVocabulary(bosTokenId), + endTokenString = reversedVocabulary(eosTokenId), + unkTokenString = reversedVocabulary(eosTokenId), + maskTokenString = reversedVocabulary(eosTokenId), + padTokenString = reversedVocabulary(paddingTokenId), + additionalStrings = addedTokens.keys.toArray) + + val bpeTokenizer: JanusTokenizer = BpeTokenizer + .forModel( + "Janus", + merges = merges, + vocab = vocabulary, + specialTokens = Some(specialTokens), + addPrefixSpaceToSentence = false, + alwaysAddPrefix = false) + .asInstanceOf[JanusTokenizer] + + /** Decode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of decoded sentences + */ + def decode(sentences: Array[Array[Int]]): Seq[String] = { + sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt))) + } + + /** Encode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of encoded sentences + */ + def encodeText(sentences: Seq[Annotation], imgTokenLen: List[Int]): Seq[Array[Int]] = { + + val pattern = raw"".r + + val startOfImage = "" + val endOfImage = "" + val startOfImageToken = vocabulary.getOrElse(startOfImage, 100016) + val endOfImageToken = vocabulary.getOrElse(endOfImage, 100593) + + // raise an error if the pattern is not found in the text + if (pattern.findFirstIn(sentences.head.result).isEmpty) { + throw new IllegalArgumentException( + "The pattern is not found in the text") + } + + // split the sentences into chunks based on the pattern and tokenize them + // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)] + val promptChunks = sentences + .map(s => { + val sentWithTask = s.result + var offsetLength = 0 + pattern + .split(sentWithTask) + .zipWithIndex + .map(s => { + val sentenceWithTask = Sentence( + content = s._1, + start = offsetLength, + end = offsetLength + s._1.length, + index = s._2) + offsetLength += s._1.length + bpeTokenizer + .tokenize(sentenceWithTask) + .map(bpeTokenizer.encode) + .flatMap(_.map(_.pieceId)) + }) + }) + + // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]] + val tokens = promptChunks + .zip(imgTokenLen) + .map(s => { + val (promptChunk, imgTokenLen) = s + val imgPaddingTokens = + Array(startOfImageToken) ++ Array.fill(imgTokenLen)(imageToken) ++ Array( + endOfImageToken) + val combinedChunks = promptChunk + .map(_.toArray) + .reduce(_ ++ imgPaddingTokens ++ _) + Array(bosTokenId) ++ combinedChunks + }) + + // val tokens = SentenceSplit + // .unpack(sentences) + // .map(s => { + // val sentWithTask = s + // bpeTokenizer + // .tokenize(sentWithTask) + // .map(bpeTokenizer.encode) + // .flatMap(_.map(_.pieceId)) + // }) + tokens + } + + def encode( + imageAnnotations: Seq[AnnotationImage], + sentences: Seq[Annotation], + preprocessor: Preprocessor, + imageTokenLength: Int = imageTokenLength) + : (Seq[Array[Int]], Array[Array[Array[Array[Array[Float]]]]]) = { + val preprocessedImages = encodeImage(imageAnnotations.toArray, preprocessor) + val encodedText = encodeText(sentences, List(imageTokenLength)).toArray + + (encodedText, preprocessedImages) + } + + def tag( + batch: Seq[Array[Int]], + images: Array[Array[Array[Array[Array[Float]]]]], + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long], + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int, + stopTokenIds: Array[Int]): Array[Array[Int]] = { + + val pixelValues = images + val ignoreTokenIdsInt = ignoreTokenIds + val expandedDecoderInputsVals = batch + val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray + val maxSentenceLength = sequencesLength.max // - curLen + // val pixelValues = images._1 + // val imageSizes = images._2 + val numReturn_sequences = 1 + // from config + + var effectiveBatch_size = 1 + var effectiveBatch_mult = 1 + + if (doSample) { + effectiveBatch_size = expandedDecoderInputsVals.length * numReturn_sequences + effectiveBatch_mult = numReturn_sequences + } else { + effectiveBatch_size = expandedDecoderInputsVals.length + effectiveBatch_mult = 1 + } + + val inferRequestLanguageModel = + openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request() + val inferRequestVisionEmbeddingsModel = + openvinoWrapper.get.visionEmbeddingsModel.getCompiledModel().create_infer_request() + val inferRequestTextEmbeddingsModel = + openvinoWrapper.get.textEmbeddingsModel.getCompiledModel().create_infer_request() + val inferRequestLMHeadModel = + openvinoWrapper.get.lmHeadModel.getCompiledModel().create_infer_request() + val inferRequestMergeModel = + openvinoWrapper.get.mergeModel.getCompiledModel().create_infer_request() + + val generatedIds = generateGreedy( + batch.toArray, + batch.toArray, + pixelValues, + maxOutputLength, + inferRequestLanguageModel, + inferRequestVisionEmbeddingsModel, + inferRequestTextEmbeddingsModel, + inferRequestLMHeadModel, + inferRequestMergeModel) + generatedIds + } + + def generateGreedy( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Array[Float]]]]], + maxOutputLength: Int, + inferRequestLanguageModel: InferRequest, + inferRequestVisionEmbeddingsModel: InferRequest, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestLMHeadModel: InferRequest, + inferRequestMergeModel: InferRequest): Array[Array[Int]] = { + + var generatedIds: Array[Array[Int]] = Array() + var decoderInputIdsCopied = decoderInputIds + while (!greedyGenerationFinished(generatedIds, eosTokenId, maxOutputLength)) { + val decoderOutputs = getModelOutputs( + encoderInputIds, + decoderInputIdsCopied, + pixelValues, + inferRequestLanguageModel, + inferRequestVisionEmbeddingsModel, + inferRequestTextEmbeddingsModel, + inferRequestLMHeadModel, + inferRequestMergeModel) + + val nextTokenIds = decoderOutputs.map { scores => + argmax(scores) + } + + if (generatedIds.isEmpty) { + generatedIds = nextTokenIds.map(Array(_)) + } else { + generatedIds = + generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => + currentIds ++ Array(nextId) + } + } + + // extend decoder input ids + decoderInputIdsCopied = + decoderInputIdsCopied.zip(nextTokenIds).map { case (currentIds, nextId) => + currentIds ++ Array(nextId) + } + } + generatedIds + } + + def predict( + sentences: Seq[Annotation], + imageAnnotations: Seq[AnnotationImage], + batchSize: Int, + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long] = None, + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int): Seq[Annotation] = { + + val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences, preprocessor) + val tagged = tag( + encodedText, + preprocessedImages, + minOutputLength, + maxOutputLength, + doSample, + temperature, + topK, + topP, + repetitionPenalty, + noRepeatNgramSize, + randomSeed, + ignoreTokenIds, + beamSize, + maxInputLength, + Array(eosTokenId)) + val decoded = decode(tagged) + + var sentBegin, nextSentEnd = 0 + val annotations = decoded.map { content => + nextSentEnd += content.length - 1 + val annots = new Annotation( + annotatorType = DOCUMENT, + begin = sentBegin, + end = nextSentEnd, + result = content, + metadata = Map()) + sentBegin += nextSentEnd + 1 + annots + } + annotations + } + + def getModelOutputs( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Array[Float]]]]], + inferRequestLanguageModel: InferRequest, + inferRequestVisionEmbeddingsModel: InferRequest, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestLMHeadModel: InferRequest, + inferRequestMergeModel: InferRequest): Array[Array[Float]] = { + + val mergeRequest = openvinoWrapper.get.mergeModel.getCompiledModel().create_infer_request() + val inputEmbeds = getMultimodalEmbeddings( + encoderInputIds, + decoderInputIds, + pixelValues, + inferRequestVisionEmbeddingsModel, + inferRequestTextEmbeddingsModel, + mergeRequest) + val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + val posIdsLong = decoderInputIds.flatMap { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + } + } + (inpIdsLong, posIdsLong) + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + val posIdsLong = decoderInputIds.map { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + }.last + } + (inpIdsLong, posIdsLong) + } + val attentionMask: Array[Long] = + decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) } + + val batchSize: Int = decoderInputIds.length + val beamIdx: Array[Int] = new Array[Int](batchSize) + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + + val decoderAttentionMask: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask) + val decoderPositionIDs: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputPositionIDsLong) + val beamIdxTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize), beamIdx) + + inferRequestLanguageModel.set_tensor("inputs_embeds", inputEmbeds) + inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask) + inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs) + inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor) + + inferRequestLanguageModel.infer() + + val result = inferRequestLanguageModel.get_tensor("last_hidden_state") + + inferRequestLMHeadModel.set_input_tensor(result) + inferRequestLMHeadModel.infer() + + val logits = inferRequestLMHeadModel.get_output_tensor() + + val logitsRaw = logits.data() + + val sequenceLength = inputIdsLong.length / batchSize + val decoderOutputs = (0 until batchSize).map(i => { + logitsRaw + .slice( + i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize, + i * sequenceLength * vocabSize + sequenceLength * vocabSize) + }) + decoderOutputs.toArray + } + + private def argmax(scores: Array[Float]): Int = + scores.zipWithIndex.maxBy { case (score, _) => + score + }._2 + + private def greedyGenerationFinished( + decoderIds: Seq[Array[Int]], + eosTokenId: Int, + maxOutputLength: Int): Boolean = { + if (decoderIds.isEmpty) { + false + } else { + decoderIds.forall { ids => + ids.length >= maxOutputLength || ids.last == eosTokenId + } + } + } + + def getResizeSizes( + width: Int, + height: Int, + minSize: Int = 14, + imageSize: Int = 384): (Int, Int) = { + val maxSize = math.max(width, height) + ( + math.max((height.toFloat / maxSize * imageSize).toInt, minSize), + math.max((width.toFloat / maxSize * imageSize).toInt, minSize)) + } + + def expandToSquare(img: BufferedImage, r: Int, g: Int, b: Int): BufferedImage = { + val backgroundColor = new Color(r, g, b) + val width = img.getWidth + val height = img.getHeight + + if (width == height) { + img + } else { + val size = Math.max(width, height) + val squaredImage = new BufferedImage(size, size, img.getType) + val g2d: Graphics2D = squaredImage.createGraphics() + + // Fill the background + g2d.setColor(backgroundColor) + g2d.fillRect(0, 0, size, size) + + // Calculate the position to center the original image + val x = if (width < height) (size - width) / 2 else 0 + val y = if (height < width) (size - height) / 2 else 0 + + // Draw the original image onto the new square image + g2d.drawImage(img, x, y, null) + g2d.dispose() + + squaredImage + } + } + private def encodeImage( + annotations: Array[AnnotationImage], + preprocessor: Preprocessor): Array[Array[Array[Array[Array[Float]]]]] = { + + val batchProcessedImages = annotations.map { annot => + val bufferedImage = ImageIOUtils.byteToBufferedImage( + bytes = annot.result, + w = annot.width, + h = annot.height, + nChannels = annot.nChannels) + + val (resize_height, resize_width): (Int, Int) = getResizeSizes( + width = bufferedImage.getWidth, + height = bufferedImage.getHeight, + imageSize = preprocessor.size) + + val resizedImage = if (preprocessor.do_resize) { + ImageResizeUtils.resizeBufferedImage( + width = resize_height, + height = resize_width, + preprocessor.resample)(bufferedImage) + } else bufferedImage + + val resizedImageSquare = expandToSquare( + resizedImage, + (preprocessor.image_mean(0) * 255).toInt, + (preprocessor.image_mean(1) * 255).toInt, + (preprocessor.image_mean(2) * 255).toInt) + + val normalizedImage = + ImageResizeUtils.normalizeAndConvertBufferedImage( + img = resizedImageSquare, + mean = preprocessor.image_mean, + std = preprocessor.image_std, + doNormalize = preprocessor.do_normalize, + doRescale = preprocessor.do_rescale, + rescaleFactor = preprocessor.rescale_factor) + + Array(normalizedImage) + } + + batchProcessedImages + + } + + def getMultimodalEmbeddings( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Array[Float]]]]], + inferRequestVisionEmbeddingsModel: InferRequest, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestMergeModel: InferRequest): org.intel.openvino.Tensor = { + val inputIdsLong: Array[Long] = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + + inpIdsLong + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + inpIdsLong + } + val batchSize: Int = decoderInputIds.length + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + val inputIdsLongTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputIdsLong) + + val imageEmbeddings: org.intel.openvino.Tensor = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + val pixelValuesTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor( + Array( + pixelValues.length, + pixelValues.head.length, + pixelValues.head.head.length, + pixelValues.head.head.head.length, + pixelValues.head.head.head.head.length), + pixelValues.flatten.flatten.flatten.flatten.map(_.toFloat)) + + // Get image embeddings + inferRequestVisionEmbeddingsModel.set_input_tensor(pixelValuesTensor) + + inferRequestVisionEmbeddingsModel.infer() + + val imageEmbeddings = inferRequestVisionEmbeddingsModel.get_output_tensor() + + // Get text embeddings + inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor) + + inferRequestTextEmbeddingsModel.infer() + + val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor() + + // Merge image and text embeddings + inferRequestMergeModel.set_tensor("vision_embeds", imageEmbeddings) + inferRequestMergeModel.set_tensor("inputs_embeds", textEmbeddings) + inferRequestMergeModel.set_tensor("input_ids", inputIdsLongTensor) + + inferRequestMergeModel.infer() + + inferRequestMergeModel.get_tensor("final_embeddings") + } else { + // Get text embeddings + inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor) + + inferRequestTextEmbeddingsModel.infer() + + val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor() + + textEmbeddings + } + imageEmbeddings + } + +} diff --git a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala index 0c2f65d4315e4e..f6314fe66fb409 100644 --- a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala +++ b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala @@ -218,4 +218,13 @@ object OpenvinoWrapper { decoderWithPast: OpenvinoWrapper) case class DecoderWrappers(decoder: OpenvinoWrapper) case class EncoderDecoderWithoutPastWrappers(encoder: OpenvinoWrapper, decoder: OpenvinoWrapper) + case class JanusWrappers( + languageModel: OpenvinoWrapper, + lmHeadModel: OpenvinoWrapper, + visionEmbeddingsModel: OpenvinoWrapper, + textEmbeddingsModel: OpenvinoWrapper, + mergeModel: OpenvinoWrapper, + genHeadModel: OpenvinoWrapper, + genEmbeddingsModel: OpenvinoWrapper, + genDecoderModel: OpenvinoWrapper) } diff --git a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala index cd0761f0f9daa3..c795abb8e7baf9 100644 --- a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala +++ b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala @@ -18,6 +18,7 @@ package com.johnsnowlabs.ml.util import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper import com.johnsnowlabs.nlp.util.io.{ExternalResource, ReadAs, ResourceHelper} +import org.glassfish.jersey.internal.inject.Custom import java.io.File import java.nio.file.Paths @@ -103,22 +104,41 @@ object LoadExternalModel { } - def isOpenvinoModel(modelPath: String, isEncoderDecoder: Boolean): Boolean = { - if (isEncoderDecoder) { - val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml") - val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin") - val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml") - val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin") - val ovDecoderModelWithPastXml = new File(modelPath, s"${Openvino.decoderModelWithPast}.xml") - val ovDecoderModelWithPastBin = new File(modelPath, s"${Openvino.decoderModelWithPast}.bin") - - ovEncoderModelXml.exists() && ovEncoderModelBin.exists() && - ovDecoderModelXml.exists() && ovDecoderModelBin.exists() && - ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists() + def isOpenvinoModel( + modelPath: String, + isEncoderDecoder: Boolean, + custom: Option[List[String]] = None): Boolean = { + + if (custom.isDefined) { + for (model <- custom.get) { + val ovModelXml = new File(modelPath, s"${model}.xml") + val ovModelBin = new File(modelPath, s"${model}.bin") + if (!ovModelXml.exists() || !ovModelBin.exists()) { + // If any of the custom models are missing, return false + println(s"Custom model $model is missing") + return false + } + } + true } else { - val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml") - val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin") - modelXml.exists() && modelBin.exists() + if (isEncoderDecoder) { + val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml") + val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin") + val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml") + val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin") + val ovDecoderModelWithPastXml = + new File(modelPath, s"${Openvino.decoderModelWithPast}.xml") + val ovDecoderModelWithPastBin = + new File(modelPath, s"${Openvino.decoderModelWithPast}.bin") + + ovEncoderModelXml.exists() && ovEncoderModelBin.exists() && + ovDecoderModelXml.exists() && ovDecoderModelBin.exists() && + ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists() + } else { + val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml") + val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin") + modelXml.exists() && modelBin.exists() + } } } @@ -126,7 +146,8 @@ object LoadExternalModel { modelPath: String, isEncoderDecoder: Boolean = false, withPast: Boolean = false, - isDecoder: Boolean = false): String = { + isDecoder: Boolean = false, + custom: Option[List[String]] = None): String = { /** Check if the path is correct */ val f = new File(modelPath) @@ -146,7 +167,7 @@ object LoadExternalModel { val onnxModelExist = isOnnxModel(modelPath, isEncoderDecoder, withPast, isDecoder) /*Openvino required model files*/ - val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder) + val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder, custom) if (tfSavedModelExist) { TensorFlow.name @@ -176,10 +197,11 @@ object LoadExternalModel { path: String, isEncoderDecoder: Boolean = false, withPast: Boolean = false, - isDecoder: Boolean = false): (String, String) = { + isDecoder: Boolean = false, + custom: Option[List[String]] = None): (String, String) = { val localPath: String = ResourceHelper.copyToLocal(path) - (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder)) + (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder, custom)) } def loadTextAsset(assetPath: String, assetName: String): Array[String] = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala new file mode 100644 index 00000000000000..1866933b1c6e71 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala @@ -0,0 +1,712 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.ai.Janus +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadJsonStringAsset, + loadTextAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.ml.util.Openvino +import com.johnsnowlabs.nlp.AnnotatorType.{DOCUMENT, IMAGE} +import com.johnsnowlabs.nlp._ +import org.json4s.{DefaultFormats, JValue} +import org.json4s.jackson.JsonMethods.parse +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.JanusWrappers +import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntArrayParam, IntParam} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +/** JanusForMultiModal can load Janus Vision models for visual question answering. The model + * consists of a vision encoder, a text encoder as well as a text decoder. The vision encoder + * will encode the input image, the text encoder will encode the input question together with the + * encoding of the image, and the text decoder will output the answer to the question. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val visualQA = JanusForMultiModal.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * }}} + * The default model is `"Janus"`, if no name is provided. + * + * For available pretrained models please see the + * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTest.scala]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val imageDF: DataFrame = ResourceHelper.spark.read + * .format("image") + * .option("dropInvalid", value = true) + * .load(imageFolder) + * + * val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n <|image|> \nWhat is unusual on this picture? \n ASSISTANT:\n")) + * + * val imageAssembler: ImageAssembler = new ImageAssembler() + * .setInputCol("image") + * .setOutputCol("image_assembler") + * + * val visualQAClassifier = JanusForMultiModal.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * + * val pipeline = new Pipeline().setStages(Array( + * imageAssembler, + * visualQAClassifier + * )) + * + * val result = pipeline.fit(testDF).transform(testDF) + * + * result.select("image_assembler.origin", "answer.result").show(false) + * +--------------------------------------+------+ + * |origin |result| + * +--------------------------------------+------+ + * |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]| + * +--------------------------------------+------+ + * }}} + * + * @see + * [[CLIPForZeroShotClassification]] for Zero Shot Image Classifier + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ + +class JanusForMultiModal(override val uid: String) + extends AnnotatorModel[JanusForMultiModal] + with HasBatchedAnnotateImage[JanusForMultiModal] + with HasImageFeatureProperties + with WriteOpenvinoModel + with HasGeneratorProperties + with HasEngine { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("JanusForMultiModal")) + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(IMAGE) + override val outputAnnotatorType: AnnotatorType = DOCUMENT + + /** @group setParam */ + def setRandomSeed(value: Int): JanusForMultiModal.this.type = { + if (randomSeed.isEmpty) { + this.randomSeed = Some(value) + } + this + } + + /** A list of token ids which are ignored in the decoder's output (Default: `Array()`) + * + * @group param + */ + var ignoreTokenIds = new IntArrayParam( + this, + "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output") + + /** @group setParam */ + def setIgnoreTokenIds(tokenIds: Array[Int]): JanusForMultiModal.this.type = { + set(ignoreTokenIds, tokenIds) + } + + /** @group getParam */ + def getIgnoreTokenIds: Array[Int] = $(ignoreTokenIds) + + /** Vocabulary used to encode the words to ids with bpeTokenizer.encode + * + * @group param + */ + val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() + + /** @group setParam */ + def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value) + + /** Holding merges.txt coming from RoBERTa model + * + * @group param + */ + val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected() + + /** @group setParam */ + def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value) + + /** Additional tokens to be added to the vocabulary + * + * @group param + */ + val addedTokens: MapFeature[String, Int] = new MapFeature(this, "addedTokens").setProtected() + + /** @group setParam */ + def setAddedTokens(value: Map[String, Int]): this.type = set(addedTokens, value) + + /** Stop tokens to terminate the generation + * + * @group param + */ + override val stopTokenIds = + new IntArrayParam(this, "stopTokenIds", "Stop tokens to terminate the generation") + + /** @group setParam */ + override def setStopTokenIds(value: Array[Int]): this.type = { + set(stopTokenIds, value) + } + + /** @group getParam */ + override def getStopTokenIds: Array[Int] = $(stopTokenIds) + + private var _model: Option[Broadcast[Janus]] = None + val generationConfig: StructFeature[GenerationConfig] = + new StructFeature(this, "generationConfig").setProtected() + + def setGenerationConfig(value: GenerationConfig): this.type = + set(generationConfig, value) + + def getGenerationConfig: GenerationConfig = $$(generationConfig) + + val imageToken = + new IntParam(this, "imageToken", "Token id for image embeddings") + + /** @group setParam */ + def setImageToken(value: Int): this.type = set(imageToken, value) + + /** @group getParam */ + def getImageToken: Int = $(imageToken) + + val imageTokenLength = + new IntParam(this, "imageTokenLength", "Token length for image embeddings") + + /** @group setParam */ + def setImageTokenLength(value: Int): this.type = set(imageTokenLength, value) + + /** @group getParam */ + def getImageTokenLength: Int = $(imageTokenLength) + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + preprocessor: Preprocessor, + onnxWrappers: Option[DecoderWrappers], + openvinoWrapper: Option[JanusWrappers]): this.type = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new Janus( + onnxWrappers, + openvinoWrapper, + $$(merges), + $$(vocabulary), + $$(addedTokens), + preprocessor, + generationConfig = getGenerationConfig, + imageToken = getImageToken, + imageTokenLength = getImageTokenLength))) + } + this + } + + /** @group getParam */ + def getModelIfNotSet: Janus = _model.get.value + + setDefault( + minOutputLength -> 0, + maxOutputLength -> 20, + doSample -> false, + temperature -> 0.6, + topK -> -1, + topP -> 0.9, + repetitionPenalty -> 1.0, + noRepeatNgramSize -> 3, + ignoreTokenIds -> Array(), + batchSize -> 1, + beamSize -> 1, + maxInputLength -> 4096, + stopTokenIds -> Array(2), + imageToken -> 100594, + imageTokenLength -> 576) + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + */ + override def batchAnnotate( + batchedAnnotations: Seq[Array[AnnotationImage]]): Seq[Seq[Annotation]] = { + + batchedAnnotations + // .filter { annotationImages => + // annotationImages.exists(_.text.nonEmpty) + // } + .map { cleanAnnotationImages => + val validImages = cleanAnnotationImages.filter(_.result.nonEmpty) + val questionAnnotations = extractInputAnnotation(validImages) + + getModelIfNotSet.predict( + questionAnnotations, + validImages.toSeq, + batchSize = $(batchSize), + minOutputLength = $(minOutputLength), + maxOutputLength = $(maxOutputLength), + doSample = $(doSample), + temperature = $(temperature), + topK = $(topK), + topP = $(topP), + repetitionPenalty = $(repetitionPenalty), + noRepeatNgramSize = $(noRepeatNgramSize), + randomSeed = this.randomSeed, + ignoreTokenIds = $(ignoreTokenIds), + beamSize = $(beamSize), + maxInputLength = $(maxInputLength)) + } + } + + private def extractInputAnnotation( + annotationImages: Array[AnnotationImage]): Seq[Annotation] = { + val questions = annotationImages.map(annotationImage => { + val imageText = + if (annotationImage.text.nonEmpty) annotationImage.text + else + "<|user|> \n <|image|> This is an image\n <|end|>\n <|assistant|>\n" // default question + Annotation(imageText) + }) + + questions + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + getEngine match { + case Openvino.name => + val wrappers = getModelIfNotSet.openvinoWrapper + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.languageModel, "openvino_language_model.xml")), + JanusForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.visionEmbeddingsModel, "openvino_vision_embeddings_model.xml")), + JanusForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.textEmbeddingsModel, "openvino_text_embeddings_model.xml")), + JanusForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.mergeModel, "openvino_multimodal_merge_model.xml")), + JanusForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.lmHeadModel, "openvino_lm_head_model.xml")), + JanusForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.genHeadModel, "openvino_gen_head_model.xml")), + JanusForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.genEmbeddingsModel, "openvino_gen_embeddings_model.xml")), + JanusForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.genDecoderModel, "openvino_gen_decoder_model.xml")), + JanusForMultiModal.suffix) + case _ => + throw new Exception(notSupportedEngineError) + } + } + +} + +trait ReadablePretrainedJanusForMultiModal + extends ParamsAndFeaturesReadable[JanusForMultiModal] + with HasPretrained[JanusForMultiModal] { + + override val defaultModelName: Some[String] = Some("Janus") + + /** Java compliant-overrides */ + override def pretrained(): JanusForMultiModal = super.pretrained() + + override def pretrained(name: String): JanusForMultiModal = + super.pretrained(name) + + override def pretrained(name: String, lang: String): JanusForMultiModal = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): JanusForMultiModal = + super.pretrained(name, lang, remoteLoc) + +} + +trait ReadJanusForMultiModalDLModel extends ReadOpenvinoModel { + this: ParamsAndFeaturesReadable[JanusForMultiModal] => + val suffix: String = "_Janus" + override val openvinoFile: String = "Janus_openvino" + def readModel(instance: JanusForMultiModal, path: String, spark: SparkSession): Unit = { + instance.getEngine match { +// VISION_EMBEDDINGS = "openvino_vision_embeddings_model.xml" +// TEXT_EMBEDDINGS = "openvino_text_embeddings_model.xml" +// LANGUAGE_MODEL = "openvino_language_model.xml" +// LM_HEAD = "openvino_lm_head_model.xml" +// MERGE_MULTIMODAL = "openvino_multimodal_merge_model.xml" +// GEN_HEAD = "openvino_gen_head_model.xml" +// GEN_EMBEDDINGS = "openvino_gen_embeddings_model.xml" +// GEN_DECODER = "openvino_gen_decoder_model.xml" + +// JanusWrappers( +// languageModel: OpenvinoWrapper, +// lmHeadModel: OpenvinoWrapper, +// visionEmbeddingsModel: OpenvinoWrapper, +// textEmbeddingsModel: OpenvinoWrapper, +// mergeModel: OpenvinoWrapper, +// genHeadModel: OpenvinoWrapper, +// genEmbeddingsModel: OpenvinoWrapper, +// genDecoderModel: OpenvinoWrapper) + case Openvino.name => + val languageModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_language_model.xml"), suffix) + + val visionEmbeddingsModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_vision_embeddings_model.xml"), suffix) + + val textEmbeddingsModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_text_embeddings_model.xml"), suffix) + + val mergeModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_multimodal_merge_model.xml"), suffix) + + val lmHeadModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_lm_head_model.xml"), suffix) + + val genHeadModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_gen_head_model.xml"), suffix) + + val genEmbeddingsModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_gen_embeddings_model.xml"), suffix) + + val genDecoderModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_gen_decoder_model.xml"), suffix) + + val ovWrapper = JanusWrappers( + languageModel = languageModelWrappers("openvino_language_model.xml"), + visionEmbeddingsModel = + visionEmbeddingsModelWrappers("openvino_vision_embeddings_model.xml"), + textEmbeddingsModel = textEmbeddingsModelWrappers("openvino_text_embeddings_model.xml"), + mergeModel = mergeModelWrappers("openvino_multimodal_merge_model.xml"), + lmHeadModel = lmHeadModelWrappers("openvino_lm_head_model.xml"), + genHeadModel = genHeadModelWrappers("openvino_gen_head_model.xml"), + genEmbeddingsModel = genEmbeddingsModelWrappers("openvino_gen_embeddings_model.xml"), + genDecoderModel = genDecoderModelWrappers("openvino_gen_decoder_model.xml")) + val preprocessor = Preprocessor( + do_normalize = true, + do_resize = true, + "JanusFeatureExtractor", + instance.getImageMean, + instance.getImageStd, + instance.getResample, + instance.getSize) + instance.setModelIfNotSet(spark, preprocessor, None, Some(ovWrapper)) + case _ => { + throw new Exception(notSupportedEngineError) + } + } + } + + addReader(readModel) + + def loadSavedModel( + modelPath: String, + spark: SparkSession, + useOpenvino: Boolean = false): JanusForMultiModal = { + implicit val formats: DefaultFormats.type = DefaultFormats // for json4 + val (localModelPath, detectedEngine) = + modelSanityCheck( + modelPath, + isDecoder = false, + custom = Some( + List( + "openvino_language_model", + "openvino_vision_embeddings_model", + "openvino_text_embeddings_model", + "openvino_multimodal_merge_model", + "openvino_lm_head_model", + "openvino_gen_head_model", + "openvino_gen_embeddings_model", + "openvino_gen_decoder_model"))) + val modelConfig: JValue = + parse(loadJsonStringAsset(localModelPath, "config.json")) + val preprocessorConfigJsonContent = + loadJsonStringAsset(localModelPath, "preprocessor_config.json") + val preprocessorConfig = Preprocessor.loadPreprocessorConfig(preprocessorConfigJsonContent) + val beginSuppressTokens: Array[Int] = + (modelConfig \ "begin_suppress_tokens").extract[Array[Int]] + + val suppressTokenIds: Array[Int] = + (modelConfig \ "suppress_tokens").extract[Array[Int]] + + val forcedDecoderIds: Array[(Int, Int)] = + (modelConfig \ "forced_decoder_ids").extract[Array[Array[Int]]].map { + case idxWithTokenId: Array[Int] if idxWithTokenId.length == 2 => + (idxWithTokenId(0), idxWithTokenId(1)) + case _ => + throw new Exception( + "Could not extract forced_decoder_ids. Should be a list of tuples with 2 entries.") + } + + def arrayOrNone[T](array: Array[T]): Option[Array[T]] = + if (array.nonEmpty) Some(array) else None + + val vocabSize = (modelConfig \ "language_config" \ "vocab_size").extract[Int] + + val imageTokenLength = 576 + + // Check if tokenizer.json exists + val tokenizerPath = s"$localModelPath/assets/tokenizer.json" + val tokenizerExists = new java.io.File(tokenizerPath).exists() + val (vocabs, addedTokens, bytePairs) = if (tokenizerExists) { + val tokenizerConfig: JValue = parse(loadJsonStringAsset(localModelPath, "tokenizer.json")) + // extract vocab from tokenizer.json ( model -> vocab) + var vocabs: Map[String, Int] = + (tokenizerConfig \ "model" \ "vocab").extract[Map[String, Int]] + + // extract merges from tokenizer.json ( model -> merges) + val bytePairs = (tokenizerConfig \ "model" \ "merges") + .extract[List[Array[String]]] + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + + // extract added_tokens from tokenizer.json (added_tokens) + // "added_tokens": [ + // { + // "id": 128000, + // "content": "<|begin_of_text|>", + // "single_word": false, + // "lstrip": false, + // "rstrip": false, + // "normalized": false, + // "special": true + // }, ... + // ] + val addedTokens = (tokenizerConfig \ "added_tokens") + .extract[List[Map[String, Any]]] + .map { token => + val id = token("id").asInstanceOf[BigInt].intValue() + val content = token("content").asInstanceOf[String] + (content, id) + } + .toMap + + // update vocab with added tokens + addedTokens.foreach { case (content, id) => + vocabs += (content -> id) + } + (vocabs, addedTokens, bytePairs) + } else { + val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap + val addedTokens = loadTextAsset(localModelPath, "added_tokens.txt").zipWithIndex.toMap + val bytePairs = loadTextAsset(localModelPath, "merges.txt") + .map(_.split(" ")) + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + (vocabs, addedTokens, bytePairs) + } + + val tokenizerConfigFile: JValue = + parse(loadJsonStringAsset(localModelPath, "tokenizer_config.json")) + + val bosToken = (tokenizerConfigFile \ "bos_token").extract[String] + val eosToken = (tokenizerConfigFile \ "eos_token").extract[String] + val padToken = (tokenizerConfigFile \ "pad_token").extract[String] + + val bosTokenId = vocabs.getOrElse(bosToken, 100000) + val eosTokenId = vocabs.getOrElse(eosToken, 100001) + val padTokenId = vocabs.getOrElse(padToken, 100015) + val imageToken = vocabs.getOrElse("", 100594) + + val annotatorModel = new JanusForMultiModal() + .setGenerationConfig( + GenerationConfig( + bosTokenId, + padTokenId, + eosTokenId, + vocabSize, + arrayOrNone(beginSuppressTokens), + arrayOrNone(suppressTokenIds), + arrayOrNone(forcedDecoderIds))) + .setVocabulary(vocabs) + .setMerges(bytePairs) + .setAddedTokens(addedTokens) + .setImageToken(imageToken) + .setImageTokenLength(imageTokenLength) + + val modelEngine = + if (useOpenvino) + Openvino.name + else + detectedEngine + annotatorModel.set(annotatorModel.engine, modelEngine) + + detectedEngine match { + case Openvino.name => + val visionWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_vision_embeddings_model") + val textWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_text_embeddings_model") + val mergeWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_multimodal_merge_model") + val languageModelWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_language_model") + val lmHeadWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_lm_head_model") + val genHeadWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_gen_head_model") + val genEmbeddingsWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_gen_embeddings_model") + val genDecoderWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_gen_decoder_model") + val openvinoWrapper = JanusWrappers( + languageModel = languageModelWrapper, + visionEmbeddingsModel = visionWrapper, + textEmbeddingsModel = textWrapper, + mergeModel = mergeWrapper, + lmHeadModel = lmHeadWrapper, + genHeadModel = genHeadWrapper, + genEmbeddingsModel = genEmbeddingsWrapper, + genDecoderModel = genDecoderWrapper) + annotatorModel.setModelIfNotSet(spark, preprocessorConfig, None, Some(openvinoWrapper)) + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } +} + +object JanusForMultiModal + extends ReadablePretrainedJanusForMultiModal + with ReadJanusForMultiModalDLModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala index 8c72a8f99d6685..1e12625f829201 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala @@ -382,6 +382,14 @@ object BpeTokenizer { modelSpecialTokens(), padWithSequenceTokens, addPrefixSpaceToSentence = addPrefixSpaceToSentence) + case "Janus" => + new JanusTokenizer( + merges, + vocab, + modelSpecialTokens(), + padWithSequenceTokens, + addPrefixSpaceToSentence = addPrefixSpaceToSentence) + case _ => throw new IllegalArgumentException("Model type \"" + modelType + "\" not supported yet.") } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/JanusTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/JanusTokenizer.scala new file mode 100644 index 00000000000000..d3960474dd8707 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/JanusTokenizer.scala @@ -0,0 +1,120 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.tokenizer.bpe + +import com.johnsnowlabs.nlp.annotators.common.IndexedToken + +import java.nio.charset.Charset +import scala.collection.mutable.ListBuffer +import scala.util.matching.Regex + +class JanusTokenizer( + merges: Map[(String, String), Int], + vocab: Map[String, Int], + specialTokens: SpecialTokens, + padWithSequenceTokens: Boolean = true, + prependString: String = "", + addPrefixSpaceToSentence: Boolean = false, + alwaysAddPrefix: Boolean = true, + splitPatternRegex: Regex = + raw"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".r) + extends BpeTokenizer( + merges, + vocab, + specialTokens, + padWithSequenceTokens, + addPrefixSpaceToSentence, + alwaysAddPrefix) { + + /** Mapping for bytes to a different set of unicode characters (especially white spaces). This + * improved model performance for gpt-2 + */ + protected val bytesToUnicodeMapping: Map[Int, String] = { + val bytes: ListBuffer[Int] = + ListBuffer.range('!', '~' + 1) ++ ListBuffer.range('ยก', 'ยฌ' + 1) ++ ListBuffer + .range('ยฎ', 'รฟ' + 1) + val characters: ListBuffer[Int] = bytes.clone + var n = 0 + for (b <- 0 to 256) { + if (!bytes.contains(b)) { + bytes += b + characters += (256 + n) + n += 1 + } + } + (bytes zip characters.map(_.toChar.toString)).toMap + } + + // Differs from Transformers, space is always prepended. + // FIX: Space should not be prepended to all tokens, but to the beginning of the text only. Otherwise token + // such as '.' get space prepended and they should not. + override val prefixForPieceId: Option[String] = + if (prependString.nonEmpty) Some(prependString) else None + + protected val decoderVocab: Map[Int, String] = vocab.map(x => (x._2, x._1)) + + protected val unicodeToByteMapping: Map[String, Int] = + bytesToUnicodeMapping.map(x => (x._2, x._1)) + + override def preProcessTokenForBpe(token: String): String = { + token + .getBytes("UTF-8") + .map { b => if (b < 0) 256 + b else b } + .foldLeft("")(_ + bytesToUnicodeMapping(_)) + } + + val splitPattern: Regex = splitPatternRegex + + override def tokenizeSubText(text: String, indexOffset: Int): Array[IndexedToken] = { + // split pattern based on gpt2's bpe tokenizer + splitPattern + .findAllMatchIn(if (prefixForPieceId.isDefined || text.startsWith(" ")) text + else text) // Prepend space to the beginning of text + .map(tok => IndexedToken(tok.matched, tok.start + indexOffset, tok.end + indexOffset - 1)) + .toArray + } + + // def decodeTokens(tokens: Array[Int]): String = { + // val decoded = new mutable.StringBuilder() + // tokens.foreach { token => + // { + // val decodedToken = decoderVocab(token) + // if (!specialTokens.contains(decodedToken)) { + // if (decodedToken.startsWith("<0x") && decodedToken.endsWith(">")) { + // val strippedHex = decodedToken.replaceAll("<0x|>", "") + // val byteValue = Integer.parseInt(strippedHex, 16) + // decoded.append(byteValue.toChar) + // } else { + // decoded.append(decodedToken) + // } + // } + // } + // + // } + // decoded.toString().replaceAll(decoderVocab(29871), " ").trim() + // } + def decodeTokens(tokens: Array[Int]): String = { + val text = tokens + .map(token => decoderVocab(token)) + .filter(x => !specialTokens.contains(x)) + .mkString("") + + val bytes = + text.map(x => unicodeToByteMapping(x.toString)).map(x => x.toByte).toArray + new String(bytes, Charset.forName("UTF-8")) + } +} diff --git a/src/test/resources/images/image1.jpg b/src/test/resources/images/image1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..676d2b343b2103a41f920acbe4854374f5e2240e GIT binary patch literal 404080 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

^=uNjw*GxuS{YH@o6;?~( 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

x9M;npyQp#(X99 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

L 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 X5rZ?v!)yU4=wr!^xL{B+^YQ-x|7O;r literal 0 HcmV?d00001 diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala new file mode 100644 index 00000000000000..e246e11e61cda8 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala @@ -0,0 +1,189 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, ImageAssembler} +import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit +import org.scalatest.flatspec.AnyFlatSpec + +class JanusForMultiModalTestSpec extends AnyFlatSpec { + + lazy val model = getJanusForMultiModalPipelineModel + + "JanusForMultiModal" should "answer a question for a given image" taggedAs FastTest in { + + val testDF = getTestDF + val result = model.transform(testDF) + + val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") + + answerAnnotation.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + + answerAnnotation.foreach { annotation => + annotation.foreach(a => println(a.result)) + } + + } + + it should "work with light pipeline annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/images/image1.jpg" + val resultAnnotate = + lightPipeline.annotate( + imagePath, + "You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\n\nUser: Describe image in details\n\nAssistant:") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.contains("cat")) + } + + it should "work with light pipeline full annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/images/bluetick.jpg" + val resultFullAnnotate = + lightPipeline.fullAnnotateImage( + imagePath, + "You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\n\nUser: Describe image in details\n\nAssistant:") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + println(s"imageName.result: ${answerAnnotation.result}") + assert(answerAnnotation.result.nonEmpty) + } + + it should "fullAnnotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\n\nUser: Describe image in details\n\nAssistant:" + val questions = Array(question, "", question) + + val resultFullAnnotate = lightPipeline.fullAnnotateImages(imagesPath, questions) + + resultFullAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { iAnnotation => + val annotation = iAnnotation.asInstanceOf[Annotation] + assert( + annotation.result.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + } + + it should "annotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\n\nUser: Describe image in details\n\nAssistant:" + val questions = Array(question, "", question) + + val resultAnnotate = lightPipeline.annotate(imagesPath, questions) + + resultAnnotate.foreach { annotate => + println(s"annotate: $annotate") + } + + resultAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { annotation => + assert( + annotation.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + + } + + private def getJanusForMultiModalPipelineModel = { + val testDF = getTestDF + + val imageAssembler: ImageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + + val loadModel = JanusForMultiModal + .loadSavedModel("/mnt/research/Projects/ModelZoo/Janus/Janus-1.3B-ov", ResourceHelper.spark) + .setInputCols("image_assembler") + .setOutputCol("answer") + .setMaxOutputLength(50) + + val newPipeline: Pipeline = + new Pipeline().setStages(Array(imageAssembler, loadModel)) + + newPipeline.fit(testDF) + } + + private def getTestDF: DataFrame = { + val imageFolder = "src/test/resources/images/" + val imageDF: DataFrame = ResourceHelper.spark.read + .format("image") + .option("dropInvalid", value = true) + .load(imageFolder) + + val testDF: DataFrame = imageDF.withColumn( + "text", + lit( + "You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\n\nUser: Describe image in details\n\nAssistant:")) + + testDF + } + +} From 195c097fb60db9b69a0d024ac63a490f9836f483 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 6 Feb 2025 08:26:29 +0000 Subject: [PATCH 020/108] Janus Scala Documentation Signed-off-by: Prabod Rathnayaka --- .../annotators/cv/JanusforMultiModal.scala | 104 +++++++----------- 1 file changed, 37 insertions(+), 67 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala index 1866933b1c6e71..65bc4d42587601 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala @@ -39,72 +39,61 @@ import org.apache.spark.ml.param.{IntArrayParam, IntParam} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.SparkSession -/** JanusForMultiModal can load Janus Vision models for visual question answering. The model - * consists of a vision encoder, a text encoder as well as a text decoder. The vision encoder - * will encode the input image, the text encoder will encode the input question together with the - * encoding of the image, and the text decoder will output the answer to the question. +/** JanusForMultiModal can load Janus models for unified multimodal understanding and generation. + * The model consists of a vision encoder, a text encoder, and a text decoder. Janus decouples + * visual encoding for enhanced flexibility, leveraging a unified transformer architecture for + * both understanding and generation tasks. * - * Pretrained models can be loaded with `pretrained` of the companion object: - * {{{ - * val visualQA = JanusForMultiModal.pretrained() - * .setInputCols("image_assembler") - * .setOutputCol("answer") - * }}} - * The default model is `"Janus"`, if no name is provided. + * Janus uses SigLIP-L as the vision encoder, supporting 384 x 384 image inputs. For image + * generation, it utilizes a tokenizer with a downsample rate of 16. The framework is based on + * DeepSeek-LLM-1.3b-base, trained on approximately 500B text tokens. * - * For available pretrained models please see the + * Pretrained models can be loaded with `pretrained` from the companion object: {{ val visualQA = + * JanusForMultiModal.pretrained() .setInputCols("image_assembler") .setOutputCol("answer") }} + * The default model is "Janus" if no name is provided. + * + * For available pretrained models, please refer to the * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. * - * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To - * see which models are compatible and how to import them see - * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended - * examples, see + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. For + * compatibility details and import instructions, see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]]. For extended examples, refer + * to * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTest.scala]]. * * ==Example== - * {{{ - * import spark.implicits._ - * import com.johnsnowlabs.nlp.base._ - * import com.johnsnowlabs.nlp.annotator._ - * import org.apache.spark.ml.Pipeline + * {{ import spark.implicits._ import com.johnsnowlabs.nlp.base._ import + * com.johnsnowlabs.nlp.annotator._ import org.apache.spark.ml.Pipeline * - * val imageDF: DataFrame = ResourceHelper.spark.read - * .format("image") - * .option("dropInvalid", value = true) - * .load(imageFolder) + * val imageDF: DataFrame = ResourceHelper.spark.read .format("image") .option("dropInvalid", + * value = true) .load(imageFolder) * - * val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n <|image|> \nWhat is unusual on this picture? \n ASSISTANT:\n")) + * val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n \nWhat is + * unusual in this picture? \n ASSISTANT:\n")) * - * val imageAssembler: ImageAssembler = new ImageAssembler() - * .setInputCol("image") - * .setOutputCol("image_assembler") + * val imageAssembler: ImageAssembler = new ImageAssembler() .setInputCol("image") + * .setOutputCol("image_assembler") * - * val visualQAClassifier = JanusForMultiModal.pretrained() - * .setInputCols("image_assembler") - * .setOutputCol("answer") + * val visualQAClassifier = JanusForMultiModal.pretrained() .setInputCols("image_assembler") + * .setOutputCol("answer") * - * val pipeline = new Pipeline().setStages(Array( - * imageAssembler, - * visualQAClassifier - * )) + * val pipeline = new Pipeline().setStages(Array( imageAssembler, visualQAClassifier )) * * val result = pipeline.fit(testDF).transform(testDF) * * result.select("image_assembler.origin", "answer.result").show(false) - * +--------------------------------------+------+ - * |origin |result| - * +--------------------------------------+------+ - * |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]| - * +--------------------------------------+------+ - * }}} + * | origin | result | + * |:---------------------------------------|:----------------------------------------------------------------------------------------| + * | [file:///content/images/cat_image.jpg] | [The unusual aspect of this picture is the presence of two cats lying on a pink couch.] | + * }} * * @see - * [[CLIPForZeroShotClassification]] for Zero Shot Image Classifier + * [[CLIPForZeroShotClassification]] for Zero Shot Image Classification * @see - * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer - * based classifiers + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of + * transformer-based classifiers * @param uid - * required uid for storing annotator to disk + * Required UID for storing the annotator to disk * @groupname anno Annotator types * @groupdesc anno * Required input and expected output annotator types @@ -112,7 +101,6 @@ import org.apache.spark.sql.SparkSession * @groupname param Parameters * @groupname setParam Parameter setters * @groupname getParam Parameter getters - * @groupname Ungrouped Members * @groupprio param 1 * @groupprio anno 2 * @groupprio Ungrouped 3 @@ -325,7 +313,7 @@ class JanusForMultiModal(override val uid: String) val imageText = if (annotationImage.text.nonEmpty) annotationImage.text else - "<|user|> \n <|image|> This is an image\n <|end|>\n <|assistant|>\n" // default question + "<|user|> \n <|image_placeholder|> This is an image\n <|end|>\n <|assistant|>\n" // default question Annotation(imageText) }) @@ -395,7 +383,7 @@ trait ReadablePretrainedJanusForMultiModal extends ParamsAndFeaturesReadable[JanusForMultiModal] with HasPretrained[JanusForMultiModal] { - override val defaultModelName: Some[String] = Some("Janus") + override val defaultModelName: Some[String] = Some("janus_1.3b_int4") /** Java compliant-overrides */ override def pretrained(): JanusForMultiModal = super.pretrained() @@ -417,24 +405,6 @@ trait ReadJanusForMultiModalDLModel extends ReadOpenvinoModel { override val openvinoFile: String = "Janus_openvino" def readModel(instance: JanusForMultiModal, path: String, spark: SparkSession): Unit = { instance.getEngine match { -// VISION_EMBEDDINGS = "openvino_vision_embeddings_model.xml" -// TEXT_EMBEDDINGS = "openvino_text_embeddings_model.xml" -// LANGUAGE_MODEL = "openvino_language_model.xml" -// LM_HEAD = "openvino_lm_head_model.xml" -// MERGE_MULTIMODAL = "openvino_multimodal_merge_model.xml" -// GEN_HEAD = "openvino_gen_head_model.xml" -// GEN_EMBEDDINGS = "openvino_gen_embeddings_model.xml" -// GEN_DECODER = "openvino_gen_decoder_model.xml" - -// JanusWrappers( -// languageModel: OpenvinoWrapper, -// lmHeadModel: OpenvinoWrapper, -// visionEmbeddingsModel: OpenvinoWrapper, -// textEmbeddingsModel: OpenvinoWrapper, -// mergeModel: OpenvinoWrapper, -// genHeadModel: OpenvinoWrapper, -// genEmbeddingsModel: OpenvinoWrapper, -// genDecoderModel: OpenvinoWrapper) case Openvino.name => val languageModelWrappers = readOpenvinoModels(path, spark, Seq("openvino_language_model.xml"), suffix) From 082db05669cae98f299f90d7557bc64cf87dddb6 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 6 Feb 2025 09:24:27 +0000 Subject: [PATCH 021/108] Janus Python API Signed-off-by: Prabod Rathnayaka --- python/sparknlp/annotator/cv/__init__.py | 3 +- .../annotator/cv/janus_for_multimodal.py | 329 ++++++++++++++++++ python/sparknlp/internal/__init__.py | 8 + .../annotator/cv/janus_for_multimodal_test.py | 83 +++++ .../annotators/cv/JanusforMultiModal.scala | 18 +- .../cv/JanusForMultiModalTestSpec.scala | 4 +- 6 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 python/sparknlp/annotator/cv/janus_for_multimodal.py create mode 100644 python/test/annotator/cv/janus_for_multimodal_test.py diff --git a/python/sparknlp/annotator/cv/__init__.py b/python/sparknlp/annotator/cv/__init__.py index 37eeaf696bb2a8..8b50cb4a3ef418 100644 --- a/python/sparknlp/annotator/cv/__init__.py +++ b/python/sparknlp/annotator/cv/__init__.py @@ -16,4 +16,5 @@ from sparknlp.annotator.cv.convnext_for_image_classification import * from sparknlp.annotator.cv.vision_encoder_decoder_for_image_captioning import * from sparknlp.annotator.cv.clip_for_zero_shot_classification import * -from sparknlp.annotator.cv.blip_for_question_answering import * \ No newline at end of file +from sparknlp.annotator.cv.blip_for_question_answering import * +from sparknlp.annotator.cv.janus_for_multimodal import * \ No newline at end of file diff --git a/python/sparknlp/annotator/cv/janus_for_multimodal.py b/python/sparknlp/annotator/cv/janus_for_multimodal.py new file mode 100644 index 00000000000000..b88a75f6948d9d --- /dev/null +++ b/python/sparknlp/annotator/cv/janus_for_multimodal.py @@ -0,0 +1,329 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class JanusForMultiModal(AnnotatorModel, + HasBatchedAnnotateImage, + HasImageFeatureProperties, + HasEngine, + HasCandidateLabelsProperties, + HasRescaleFactor): + """ + JanusForMultiModal can load Janus Vision models for visual question answering. + The model consists of a vision encoder, a text encoder, and a text decoder. + The vision encoder encodes the input image, the text encoder processes the input question + alongside the image encoding, and the text decoder generates the answer to the question. + + Janus is a novel autoregressive framework that unifies multimodal understanding and generation. + It decouples visual encoding into separate pathways while utilizing a single, unified transformer architecture for processing. + This decoupling alleviates conflicts between the visual encoderโ€™s roles in understanding and generation, enhancing the frameworkโ€™s flexibility. + + Janus surpasses previous unified models and matches or exceeds the performance of task-specific models. + It uses the DeepSeek-LLM-1.3b-base trained on approximately 500B text tokens. + For multimodal understanding, it employs the SigLIP-L vision encoder supporting 384 x 384 image input, + and for image generation, it uses a tokenizer with a downsample rate of 16. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion object: + >>> visualQAClassifier = JanusForMultiModal.pretrained() \ + ... .setInputCols(["image_assembler"]) \ + ... .setOutputCol("answer") + + The default model is `"janus_1_3b_int4"`, if no name is provided. + For available pretrained models, refer to the `Models Hub + `__. + + Models from the HuggingFace ๐Ÿงง Transformers library are also compatible with Spark NLP ๐Ÿš€. + To check compatibility and learn how to import them, see `Import Transformers into Spark NLP ๐Ÿš€ + `_. + For extended examples, refer to the `JanusForMultiModal Test Suite + `_. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``IMAGE`` ``DOCUMENT`` + ====================== ====================== + + Parameters + ---------- + batchSize : int, optional + Batch size. Larger values allow faster processing but require more memory, + by default 2. + configProtoBytes : bytes, optional + ConfigProto from TensorFlow, serialized into a byte array. + maxSentenceLength : int, optional + Maximum sentence length to process, by default 50. + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> from pyspark.sql.functions import lit + + >>> image_df = SparkSessionForTest.spark.read.format("image").load(path=images_path) + >>> test_df = image_df.withColumn( + ... "text", + ... lit("User: Describe image in details\n\nAssistant:") + ... ) + + >>> imageAssembler = ImageAssembler() \ + ... .setInputCol("image") \ + ... .setOutputCol("image_assembler") + + >>> visualQAClassifier = JanusForMultiModal.pretrained() \ + ... .setInputCols("image_assembler") \ + ... .setOutputCol("answer") + + >>> pipeline = Pipeline().setStages([ + ... imageAssembler, + ... visualQAClassifier + ... ]) + + >>> result = pipeline.fit(test_df).transform(test_df) + >>> result.select("image_assembler.origin", "answer.result").show(truncate=False) + + +--------------------------------------+----------------------------------------------------------------------+ + |origin |result | + +--------------------------------------+----------------------------------------------------------------------+ + |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]| + +--------------------------------------+----------------------------------------------------------------------+ + """ + + + + name = "JanusForMultiModal" + + inputAnnotatorTypes = [AnnotatorType.IMAGE] + + outputAnnotatorType = AnnotatorType.DOCUMENT + + configProtoBytes = Param(Params._dummy(), + "configProtoBytes", + "ConfigProto from tensorflow, serialized into byte array. Get with " + "config_proto.SerializeToString()", + TypeConverters.toListInt) + + minOutputLength = Param(Params._dummy(), "minOutputLength", "Minimum length of the sequence to be generated", + typeConverter=TypeConverters.toInt) + + maxOutputLength = Param(Params._dummy(), "maxOutputLength", "Maximum length of output text", + typeConverter=TypeConverters.toInt) + + doSample = Param(Params._dummy(), "doSample", "Whether or not to use sampling; use greedy decoding otherwise", + typeConverter=TypeConverters.toBoolean) + + temperature = Param(Params._dummy(), "temperature", "The value used to module the next token probabilities", + typeConverter=TypeConverters.toFloat) + + topK = Param(Params._dummy(), "topK", + "The number of highest probability vocabulary tokens to keep for top-k-filtering", + typeConverter=TypeConverters.toInt) + + topP = Param(Params._dummy(), "topP", + "If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or higher are kept for generation", + typeConverter=TypeConverters.toFloat) + + repetitionPenalty = Param(Params._dummy(), "repetitionPenalty", + "The parameter for repetition penalty. 1.0 means no penalty. See `this paper `__ for more details", + typeConverter=TypeConverters.toFloat) + + noRepeatNgramSize = Param(Params._dummy(), "noRepeatNgramSize", + "If set to int > 0, all ngrams of that size can only occur once", + typeConverter=TypeConverters.toInt) + + ignoreTokenIds = Param(Params._dummy(), "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output", + typeConverter=TypeConverters.toListInt) + beamSize = Param(Params._dummy(), "beamSize", + "The Number of beams for beam search.", + typeConverter=TypeConverters.toInt) + + def setMaxSentenceSize(self, value): + """Sets Maximum sentence length that the annotator will process, by + default 50. + Parameters + ---------- + value : int + Maximum sentence length that the annotator will process + """ + return self._set(maxSentenceLength=value) + + def setIgnoreTokenIds(self, value): + """A list of token ids which are ignored in the decoder's output. + Parameters + ---------- + value : List[int] + The words to be filtered out + """ + return self._set(ignoreTokenIds=value) + + def setConfigProtoBytes(self, b): + """Sets configProto from tensorflow, serialized into byte array. + Parameters + ---------- + b : List[int] + ConfigProto from tensorflow, serialized into byte array + """ + return self._set(configProtoBytes=b) + + def setMinOutputLength(self, value): + """Sets minimum length of the sequence to be generated. + Parameters + ---------- + value : int + Minimum length of the sequence to be generated + """ + return self._set(minOutputLength=value) + + def setMaxOutputLength(self, value): + """Sets maximum length of output text. + Parameters + ---------- + value : int + Maximum length of output text + """ + return self._set(maxOutputLength=value) + + def setDoSample(self, value): + """Sets whether or not to use sampling, use greedy decoding otherwise. + Parameters + ---------- + value : bool + Whether or not to use sampling; use greedy decoding otherwise + """ + return self._set(doSample=value) + + def setTemperature(self, value): + """Sets the value used to module the next token probabilities. + Parameters + ---------- + value : float + The value used to module the next token probabilities + """ + return self._set(temperature=value) + + def setTopK(self, value): + """Sets the number of highest probability vocabulary tokens to keep for + top-k-filtering. + Parameters + ---------- + value : int + Number of highest probability vocabulary tokens to keep + """ + return self._set(topK=value) + + def setTopP(self, value): + """Sets the top cumulative probability for vocabulary tokens. + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + Parameters + ---------- + value : float + Cumulative probability for vocabulary tokens + """ + return self._set(topP=value) + + def setRepetitionPenalty(self, value): + """Sets the parameter for repetition penalty. 1.0 means no penalty. + Parameters + ---------- + value : float + The repetition penalty + References + ---------- + See `Ctrl: A Conditional Transformer Language Model For Controllable + Generation `__ for more details. + """ + return self._set(repetitionPenalty=value) + + def setNoRepeatNgramSize(self, value): + """Sets size of n-grams that can only occur once. + If set to int > 0, all ngrams of that size can only occur once. + Parameters + ---------- + value : int + N-gram size can only occur once + """ + return self._set(noRepeatNgramSize=value) + + def setBeamSize(self, value): + """Sets the number of beam size for beam search, by default `4`. + Parameters + ---------- + value : int + Number of beam size for beam search + """ + return self._set(beamSize=value) + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cv.JanusForMultiModal", + java_model=None): + super(JanusForMultiModal, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=1, + minOutputLength=0, + maxOutputLength=50, + doSample=False, + temperature=1, + topK=50, + topP=1, + repetitionPenalty=1.0, + noRepeatNgramSize=0, + ignoreTokenIds=[], + beamSize=1, + ) + + @staticmethod + def loadSavedModel(folder, spark_session, use_openvino=False): + """Loads a locally saved model. + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.internal import _JanusForMultiModalLoader + jModel = _JanusForMultiModalLoader(folder, spark_session._jsparkSession, use_openvino)._java_obj + return JanusForMultiModal(java_model=jModel) + + @staticmethod + def pretrained(name="janus_1_3b_int4", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "janus_1_3b_int4" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(JanusForMultiModal, name, lang, remote_loc) \ No newline at end of file diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..b9004836e02565 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -245,6 +245,14 @@ def __init__(self, path, jspark): jspark, ) +class _JanusForMultiModalLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark, use_openvino=False): + super(_JanusForMultiModalLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.cv.JanusForMultiModal.loadSavedModel", + path, + jspark, + use_openvino + ) class _LLAMA2Loader(ExtendedJavaWrapper): def __init__(self, path, jspark, use_openvino=False): diff --git a/python/test/annotator/cv/janus_for_multimodal_test.py b/python/test/annotator/cv/janus_for_multimodal_test.py new file mode 100644 index 00000000000000..25ed3ac51283d1 --- /dev/null +++ b/python/test/annotator/cv/janus_for_multimodal_test.py @@ -0,0 +1,83 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +import pytest +import os + +from sparknlp.annotator import * +from sparknlp.base import * +from pyspark.sql.functions import lit +from test.util import SparkSessionForTest,SparkContextForTest + + +class JanusForMultiModalTestSetup(unittest.TestCase): + + def setUp(self): + self.images_path = os.getcwd() + "/../src/test/resources/image/" + self.spark = SparkContextForTest.spark + + image_df = SparkSessionForTest.spark.read.format("image").load( + path=self.images_path + ) + + self.test_df = image_df.withColumn("text", lit("You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\n\nUser: Describe image in details\n\nAssistant:")) + + image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") + + imageClassifier = (JanusForMultiModal \ + .pretrained() \ + .setInputCols("image_assembler") \ + .setOutputCol("answer")) + + self.pipeline = Pipeline( + stages=[ + image_assembler, + imageClassifier, + ] + ) + + self.model = self.pipeline.fit(self.test_df) + +@pytest.mark.slow +class JanusForMultiModalTest(JanusForMultiModalTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + result = self.model.transform(self.test_df).collect() + + for row in result: + self.assertTrue(row["answer"] != "") + + +@pytest.mark.slow +class LightJanusForMultiModalTest(JanusForMultiModalTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.model) + image_path = self.images_path + "bluetick.jpg" + + print("image_path: " + image_path) + annotations_result = light_pipeline.fullAnnotateImage( + image_path, + "You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\n\nUser: Describe image in details\n\nAssistant:" + ) + # print(annotations_result) + for result in annotations_result: + self.assertTrue(len(result["image_assembler"]) > 0) + self.assertTrue(len(result["answer"]) > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala index 65bc4d42587601..3269d93e37eac8 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala @@ -50,7 +50,7 @@ import org.apache.spark.sql.SparkSession * * Pretrained models can be loaded with `pretrained` from the companion object: {{ val visualQA = * JanusForMultiModal.pretrained() .setInputCols("image_assembler") .setOutputCol("answer") }} - * The default model is "Janus" if no name is provided. + * The default model is "janus_1_3b_int4" if no name is provided. * * For available pretrained models, please refer to the * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. @@ -62,14 +62,19 @@ import org.apache.spark.sql.SparkSession * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTest.scala]]. * * ==Example== - * {{ import spark.implicits._ import com.johnsnowlabs.nlp.base._ import - * com.johnsnowlabs.nlp.annotator._ import org.apache.spark.ml.Pipeline + * {{ import spark.implicits._ + * + * import com.johnsnowlabs.nlp.base._ + * + * import com.johnsnowlabs.nlp.annotator._ + * + * import org.apache.spark.ml.Pipeline * * val imageDF: DataFrame = ResourceHelper.spark.read .format("image") .option("dropInvalid", * value = true) .load(imageFolder) * - * val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n \nWhat is - * unusual in this picture? \n ASSISTANT:\n")) + * val testDF: DataFrame = imageDF.withColumn("text", lit("User: Describe + * image in details Assistant:")) * * val imageAssembler: ImageAssembler = new ImageAssembler() .setInputCol("image") * .setOutputCol("image_assembler") @@ -110,7 +115,6 @@ import org.apache.spark.sql.SparkSession * A list of (hyper-)parameter keys this annotator can take. Users can set and get the * parameter values through setters and getters, respectively. */ - class JanusForMultiModal(override val uid: String) extends AnnotatorModel[JanusForMultiModal] with HasBatchedAnnotateImage[JanusForMultiModal] @@ -383,7 +387,7 @@ trait ReadablePretrainedJanusForMultiModal extends ParamsAndFeaturesReadable[JanusForMultiModal] with HasPretrained[JanusForMultiModal] { - override val defaultModelName: Some[String] = Some("janus_1.3b_int4") + override val defaultModelName: Some[String] = Some("janus_1_3b_int4") /** Java compliant-overrides */ override def pretrained(): JanusForMultiModal = super.pretrained() diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala index e246e11e61cda8..3cb5f1e9a5f68b 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala @@ -29,7 +29,7 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { lazy val model = getJanusForMultiModalPipelineModel - "JanusForMultiModal" should "answer a question for a given image" taggedAs FastTest in { + "JanusForMultiModal" should "answer a question for a given image" taggedAs SlowTest in { val testDF = getTestDF val result = model.transform(testDF) @@ -160,7 +160,7 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { .setOutputCol("image_assembler") val loadModel = JanusForMultiModal - .loadSavedModel("/mnt/research/Projects/ModelZoo/Janus/Janus-1.3B-ov", ResourceHelper.spark) + .pretrained() .setInputCols("image_assembler") .setOutputCol("answer") .setMaxOutputLength(50) From 4d8bf475e79c68770de299b51ff439cea259ab12 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 24 Jan 2025 12:06:21 +0100 Subject: [PATCH 022/108] [SPARKNLP-1079] AutoGGUFVisionModel pretrained model --- .../test/annotator/seq2seq/auto_gguf_vision_model_test.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/python/test/annotator/seq2seq/auto_gguf_vision_model_test.py b/python/test/annotator/seq2seq/auto_gguf_vision_model_test.py index 1bdefcd369330d..c0509a59841ba7 100644 --- a/python/test/annotator/seq2seq/auto_gguf_vision_model_test.py +++ b/python/test/annotator/seq2seq/auto_gguf_vision_model_test.py @@ -39,11 +39,7 @@ def runTest(self): ) # Add a caption to each image. nPredict = 40 model = ( - AutoGGUFVisionModel.loadSavedModel( - "models/llava-v1.5-7b-Q4_0.gguf", - "models/llava-v1.5-7b-mmproj-model-f16.gguf", - self.spark, - ) + AutoGGUFVisionModel.pretrained() .setInputCols(["caption_document", "image_assembler"]) .setOutputCol("completions") .setChatTemplate("vicuna") From deb3952d46905e411cbfb9f5c1c710106109725c Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 24 Jan 2025 16:50:46 +0100 Subject: [PATCH 023/108] [SPARKNLP-1079] Fix loadImagesAsBytes path creation --- python/sparknlp/base/image_assembler.py | 20 +++++++++++++------ .../com/johnsnowlabs/nlp/ImageAssembler.scala | 9 +++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/python/sparknlp/base/image_assembler.py b/python/sparknlp/base/image_assembler.py index 5d4194e2209739..61d4a283cdbb60 100644 --- a/python/sparknlp/base/image_assembler.py +++ b/python/sparknlp/base/image_assembler.py @@ -135,26 +135,34 @@ def loadImagesAsBytes(cls, spark: SparkSession, path: str): DataFrame A DataFrame containing the images as raw bytes along with their metadata. """ + + # Replace the path separator in the `origin` field and `path` column, so that they match + def replace_path(column_name: str): + return regexp_replace(col(column_name), ":///", ":/") + # Load the images as metadata with the default Spark image format data = ( - spark.read - .format("image") + spark.read.format("image") .option("dropInvalid", True) .load(path) + .withColumn( + "image", col("image").withField("origin", replace_path("image.origin")) + ) ) # Load the images as raw binary files image_bytes = ( - spark.read - .format("binaryFile") + spark.read.format("binaryFile") .option("pathGlobFilter", "*.{jpeg,jpg,png,gif,bmp,JPEG,JPG,PNG,GIF,BMP}") .option("dropInvalid", True) .load(path) - .withColumn("path", regexp_replace(col("path"), ":/", ":///")) + .withColumn("path", replace_path("path")) ) # Join the two datasets on the file path - df_joined = data.join(image_bytes, data["image.origin"] == image_bytes["path"], "inner") + df_joined = data.join( + image_bytes, data["image.origin"] == image_bytes["path"], "inner" + ) # Replace the `data` field of the `image` column with raw bytes df_image_replaced = df_joined.withColumn( diff --git a/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala b/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala index 3789a0e4299d4d..ae620dc78cbaa5 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/ImageAssembler.scala @@ -230,11 +230,15 @@ object ImageAssembler extends DefaultParamsReadable[ImageAssembler] { * A dataframe with the images as raw bytes, as well as their metadata. */ def loadImagesAsBytes(spark: SparkSession, path: String): DataFrame = { + // Replace the path separator in the `origin` field and `path` column, so that they match + def replacePath(columnName: String) = regexp_replace(col(columnName), ":///", ":/") + val data: DataFrame = spark.read .format("image") .option("dropInvalid", value = true) .load(path) + .withColumn("image", col("image").withField("origin", replacePath("image.origin"))) val imageBytes: DataFrame = spark.read @@ -242,10 +246,7 @@ object ImageAssembler extends DefaultParamsReadable[ImageAssembler] { .option("pathGlobFilter", "*.{jpeg,jpg,png,gif,bmp,JPEG,JPG,PNG,GIF,BMP}") .option("dropInvalid", value = true) .load(path) - .withColumn( - "path", - regexp_replace(col("path"), ":/", ":///") - ) // Paths are different for binary and image + .withColumn("path", replacePath("path")) // Join on path val dfJoined = From f2be0576b33a36e666e10fcc768c8469f4fec87c Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Sun, 9 Feb 2025 10:26:53 +0100 Subject: [PATCH 024/108] [SPARKNLP-1079] Fix batch inference for AutoGGUFVisionModel --- project/Dependencies.scala | 2 +- .../nlp/annotators/seq2seq/AutoGGUFVisionModel.scala | 12 ++++++------ .../seq2seq/AutoGGUFVisionModelTestSpec.scala | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 620ed6b95015eb..ff532c8d56eeb6 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -128,7 +128,7 @@ object Dependencies { val azureIdentity = "com.azure" % "azure-identity" % azureIdentityVersion % Provided val azureStorage = "com.azure" % "azure-storage-blob" % azureStorageVersion % Provided - val llamaCppVersion = "0.1.5" + val llamaCppVersion = "0.1.6" val llamaCppCPU = "com.johnsnowlabs.nlp" %% "jsl-llamacpp-cpu" % llamaCppVersion val llamaCppGPU = "com.johnsnowlabs.nlp" %% "jsl-llamacpp-gpu" % llamaCppVersion val llamaCppSilicon = "com.johnsnowlabs.nlp" %% "jsl-llamacpp-silicon" % llamaCppVersion diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala index 6182728d92cd47..294854458e9bec 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala @@ -226,11 +226,8 @@ class AutoGGUFVisionModel(override val uid: String) batchedAnnotations: Seq[(Annotation, AnnotationImage)]): Seq[Seq[Annotation]] = { if (batchedAnnotations.nonEmpty) { - val modelParams = - // set parallel decoding to batch size - // TODO: might need to set this to 1 for now, as otherwise there are issues - getModelParameters.setNParallel(1) - val inferenceParams = getInferenceParameters + // set parallel decoding to batch size + val modelParams = getModelParameters.setNParallel(getBatchSize) val model: LlamaModel = getModelIfNotSet.getSession(modelParams) val (prompts, base64EncodedImages) = batchedAnnotations.unzip match { @@ -245,7 +242,10 @@ class AutoGGUFVisionModel(override val uid: String) val (completedTexts: Array[String], metadata: Map[String, String]) = try { ( - model.requestBatchImageCompletion(prompts, base64EncodedImages, inferenceParams), + model.requestBatchImageCompletion( + prompts, + base64EncodedImages, + getInferenceParameters), Map.empty) } catch { case e: LlamaException => diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala index 495822e3b212e1..961e2fc49b4488 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala @@ -46,7 +46,7 @@ class AutoGGUFVisionModelTestSpec extends AnyFlatSpec { .setInputCols("caption_document", "image_assembler") .setOutputCol("completions") .setChatTemplate("vicuna") // llava uses vicuna as default - .setBatchSize(4) + .setBatchSize(2) .setNGpuLayers(99) .setNCtx(4096) .setMinKeep(0) From 3d317599226ee0c55c0a35e9311e55ca80e953e5 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Sun, 9 Feb 2025 11:39:33 +0100 Subject: [PATCH 025/108] [SPARKNLP-1079] Add note that only CLIP models are supported --- docs/en/annotator_entries/AutoGGUFVisionModel.md | 2 ++ .../llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb | 3 ++- python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py | 2 ++ .../nlp/annotators/seq2seq/AutoGGUFVisionModel.scala | 2 ++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/en/annotator_entries/AutoGGUFVisionModel.md b/docs/en/annotator_entries/AutoGGUFVisionModel.md index 1e10040ae40856..0d6a6c086eabc3 100644 --- a/docs/en/annotator_entries/AutoGGUFVisionModel.md +++ b/docs/en/annotator_entries/AutoGGUFVisionModel.md @@ -6,6 +6,8 @@ AutoGGUFVisionModel Multimodal annotator that uses the llama.cpp library to generate text completions with large language models. It supports ingesting images for captioning. +At the moment only CLIP based models are supported. + For settable parameters, and their explanations, see HasLlamaCppInferenceProperties, HasLlamaCppModelProperties and refer to the llama.cpp documentation of [server.cpp](https://github.com/ggerganov/llama.cpp/tree/7d5e8777ae1d21af99d4f95be10db4870720da91/examples/server) diff --git a/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb b/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb index c3175b9ea0babf..a33d9c351ba094 100644 --- a/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb +++ b/examples/python/llama.cpp/llama.cpp_in_Spark_NLP_AutoGGUFVisionModel.ipynb @@ -13,7 +13,8 @@ "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", "- Multimodal inference with llama.cpp was introduced in `Spark NLP 5.6.0`, enabling quantized LLM inference on a wide range of devices. Please make sure you have upgraded to the latest Spark NLP release.\n", - "- You need to use your own `.gguf` model files, which also include the models from the [Hugging Face Models](https://huggingface.co/models?library=gguf)." + "- You need to use your own `.gguf` model files, which also include the models from the [Hugging Face Models](https://huggingface.co/models?library=gguf).", + "- At the moment only CLIP based models are supported." ] }, { diff --git a/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py b/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py index 9d7fd977bd1633..b05150ed3b9905 100755 --- a/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py +++ b/python/sparknlp/annotator/seq2seq/auto_gguf_vision_model.py @@ -19,6 +19,8 @@ class AutoGGUFVisionModel(AnnotatorModel, HasBatchedAnnotate, HasLlamaCppPropert """Multimodal annotator that uses the llama.cpp library to generate text completions with large language models. It supports ingesting images for captioning. + At the moment only CLIP based models are supported. + For settable parameters, and their explanations, see HasLlamaCppInferenceProperties, HasLlamaCppModelProperties and refer to the llama.cpp documentation of `server.cpp `__ diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala index 294854458e9bec..f3de739613ece3 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala @@ -28,6 +28,8 @@ import org.apache.spark.sql.SparkSession /** Multimodal annotator that uses the llama.cpp library to generate text completions with large * language models. It supports ingesting images for captioning. * + * At the moment only CLIP based models are supported. + * * For settable parameters, and their explanations, see [[HasLlamaCppInferenceProperties]], * [[HasLlamaCppModelProperties]] and refer to the llama.cpp documentation of * [[https://github.com/ggerganov/llama.cpp/tree/7d5e8777ae1d21af99d4f95be10db4870720da91/examples/server server.cpp]] From c3dca2d7dc1f46e573ccebb70c9623b25a434ba3 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 12 Feb 2025 01:28:53 +0000 Subject: [PATCH 026/108] update config values on the instance Signed-off-by: Prabod Rathnayaka --- .../nlp/annotators/cv/JanusforMultiModal.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala index 3269d93e37eac8..6c404e1b983346 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala @@ -317,7 +317,7 @@ class JanusForMultiModal(override val uid: String) val imageText = if (annotationImage.text.nonEmpty) annotationImage.text else - "<|user|> \n <|image_placeholder|> This is an image\n <|end|>\n <|assistant|>\n" // default question + "You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\\n\\nUser: Describe image in details\\n\\nAssistant:" // default question Annotation(imageText) }) @@ -589,6 +589,10 @@ trait ReadJanusForMultiModalDLModel extends ReadOpenvinoModel { .setAddedTokens(addedTokens) .setImageToken(imageToken) .setImageTokenLength(imageTokenLength) + .setSize(preprocessorConfig.size) + .setImageMean(preprocessorConfig.image_mean) + .setImageStd(preprocessorConfig.image_std) + .setResample(preprocessorConfig.resample) val modelEngine = if (useOpenvino) @@ -596,7 +600,6 @@ trait ReadJanusForMultiModalDLModel extends ReadOpenvinoModel { else detectedEngine annotatorModel.set(annotatorModel.engine, modelEngine) - detectedEngine match { case Openvino.name => val visionWrapper = From f9bd02d20b96c24f0cfecc635335dad54ebe5f50 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Mon, 22 Apr 2024 13:44:06 +0000 Subject: [PATCH 027/108] added OLMo scala api --- .../scala/com/johnsnowlabs/ml/ai/OLMo.scala | 362 ++++++++++++++ .../ml/ai/util/Generation/Generate.scala | 2 +- .../annotators/seq2seq/OLMoTransformer.scala | 442 ++++++++++++++++++ .../tokenizer/bpe/BpeSpecialTokens.scala | 8 + .../tokenizer/bpe/BpeTokenizer.scala | 7 + .../tokenizer/bpe/OLMoTokenizer.scala | 31 ++ .../nlp/annotators/seq2seq/OLMoTestSpec.scala | 54 +++ 7 files changed, 905 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/OLMoTokenizer.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala b/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala new file mode 100644 index 00000000000000..8cfe84b379b14d --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala @@ -0,0 +1,362 @@ +/* + * Copyright 2017 - 2023 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.ml.ai + +import ai.onnxruntime.{OnnxTensor, OrtEnvironment, OrtSession} +import com.johnsnowlabs.ml.ai.util.Generation.{Generate, GenerationConfig} +import com.johnsnowlabs.ml.onnx.OnnxSession +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.onnx.TensorResources.implicits._ +import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper +import com.johnsnowlabs.nlp.Annotation +import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT +import com.johnsnowlabs.nlp.annotators.common.SentenceSplit +import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{BpeTokenizer, OLMoTokenizer} +import org.tensorflow.{Session, Tensor} + +import scala.collection.JavaConverters._ + +private[johnsnowlabs] class OLMo( + val onnxWrappers: DecoderWrappers, + merges: Map[(String, String), Int], + vocabulary: Map[String, Int], + generationConfig: GenerationConfig) + extends Serializable + with Generate { + + private val onnxSessionOptions: Map[String, String] = new OnnxSession().getSessionOptions + val bpeTokenizer: OLMoTokenizer = BpeTokenizer + .forModel("olmo", merges = merges, vocab = vocabulary, padWithSequenceTokens = false) + .asInstanceOf[OLMoTokenizer] + private val GenerationConfig( + bosTokenId: Int, + paddingTokenId: Int, + eosTokenId: Int, + vocabSize: Int, + beginSuppressTokens, + suppressTokenIds, + forcedDecoderIds) = + generationConfig + + /** Decode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of decoded sentences + */ + def decode(sentences: Array[Array[Int]]): Seq[String] = { + println(sentences.map(_.mkString(" ")).mkString("\n")) + sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt))) + } + + /** Encode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of encoded sentences + */ + def encode(sentences: Seq[Annotation]): Seq[Array[Int]] = { + SentenceSplit + .unpack(sentences) + .map(s => { + val sentWithTask = s + bpeTokenizer + .tokenize(sentWithTask) + .map(bpeTokenizer.encode) + .flatMap(_.map(_.pieceId)) + }) + } + + def tag( + batch: Seq[Array[Int]], + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long], + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int): Array[Array[Int]] = { + val (encoderSession, env) = onnxWrappers.decoder.getSession(onnxSessionOptions) + val ignoreTokenIdsInt = ignoreTokenIds + val expandedDecoderInputsVals = batch + val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray + val maxSentenceLength = sequencesLength.max // - curLen + + val numReturn_sequences = 1 + // from config + + var effectiveBatch_size = 1 + var effectiveBatch_mult = 1 + + if (doSample) { + effectiveBatch_size = expandedDecoderInputsVals.length * numReturn_sequences + effectiveBatch_mult = numReturn_sequences + } else { + effectiveBatch_size = expandedDecoderInputsVals.length + effectiveBatch_mult = 1 + } + + // Run the prompt through the decoder and get the past +// val decoderOutputs = +// generateGreedyOnnx( +// expandedDecoderInputsVals.toArray, +// (encoderSession, env), +// maxOutputLength) + + // dummy tensors for decoder encode state and attention mask + val decoderEncoderStateTensors = Right(OnnxTensor.createTensor(env, Array(0))) + val encoderAttentionMaskTensors = Right(OnnxTensor.createTensor(env, Array(1))) + + // output with beam search + val modelOutputs = generate( + batch, + decoderEncoderStateTensors, + encoderAttentionMaskTensors, + expandedDecoderInputsVals.toArray, + maxOutputLength + maxSentenceLength, + minOutputLength, + doSample, + beamSize, + 1, + temperature, + topK, + topP, + repetitionPenalty, + noRepeatNgramSize, + this.vocabSize, + this.eosTokenId, + this.paddingTokenId, + randomSeed, + ignoreTokenIdsInt, + Right((env, encoderSession)), + applySoftmax = false) + +// decoderOutputs + modelOutputs + } + + def predict( + sentences: Seq[Annotation], + batchSize: Int, + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long] = None, + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int): Seq[Annotation] = { + + val batchDecoder = sentences.grouped(batchSize).toArray.flatMap { batch => + val batchSP = encode(batch) + val spIds = tag( + batchSP, + minOutputLength, + maxOutputLength, + doSample, + temperature, + topK, + topP, + repetitionPenalty, + noRepeatNgramSize, + randomSeed, + ignoreTokenIds, + beamSize, + maxInputLength) + + decode(spIds) + + } + + var sentBegin, nextSentEnd = 0 + val annotations = batchDecoder.zip(sentences).map { case (content, sent) => + nextSentEnd += content.length - 1 + val annots = new Annotation( + annotatorType = DOCUMENT, + begin = sentBegin, + end = nextSentEnd, + result = content, + metadata = sent.metadata) + sentBegin += nextSentEnd + 1 + annots + } + annotations + } + + private def getDecoderOutputsWithPast( + inputIds: Array[Array[Int]], + decoderPast: Map[String, OnnxTensor], + onnxSession: (OrtSession, OrtEnvironment)) + : (Array[Array[Float]], Map[String, OnnxTensor]) = { + val (session, env) = onnxSession + + val lastTokens: Array[Array[Long]] = + inputIds.map { tokenIds => + Array(tokenIds.last.toLong) + } + + val lastTokensTensor: OnnxTensor = + OnnxTensor.createTensor(env, lastTokens) + val decoderAttentionMask: OnnxTensor = + OnnxTensor.createTensor(env, lastTokens.map(_.map(_ => 1L))) + val decoderWithPastInputs: java.util.Map[String, OnnxTensor] = (Map( + OnnxSignatures.decoderInputIDs -> lastTokensTensor, + OnnxSignatures.decoderAttentionMask -> decoderAttentionMask) ++ decoderPast).asJava + val sessionOutput = session.run(decoderWithPastInputs) + val logits = sessionOutput.getFloatArray(OnnxSignatures.decoderOutput) + val decoderPresent = sessionOutput.getOnnxTensors(OnnxSignatures.decoderPresent) + lastTokensTensor.close() + val batchLogits = logits.grouped(vocabSize).toArray + (batchLogits, decoderPresent) + + } + + override def getModelOutput( + encoderInputIds: Seq[Array[Int]], + decoderInputIds: Seq[Array[Int]], + decoderEncoderStateTensors: Either[Tensor, OnnxTensor], + encoderAttentionMaskTensors: Either[Tensor, OnnxTensor], + maxLength: Int, + session: Either[Session, (OrtEnvironment, OrtSession)]): Array[Array[Float]] = { + + session.fold( + tfSession => { + // not implemented yet + Array() + }, + onnxSession => { + val (env, decoderSession) = onnxSession + val decoderOutputs = + getDecoderOutputs(decoderInputIds.toArray, onnxSession = (decoderSession, env)) + decoderOutputs + }) + + } + private def getDecoderOutputs( + inputIds: Array[Array[Int]], + onnxSession: (OrtSession, OrtEnvironment)): (Array[Array[Float]]) = { + val (session, env) = onnxSession + + val inputIdsLong: Array[Array[Long]] = + inputIds.map { tokenIds => tokenIds.map(_.toLong) } + + val inputPositionIDsLong: Array[Array[Long]] = + inputIds.map { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + } + } + + val inputIdsLongTensor: OnnxTensor = + OnnxTensor.createTensor(env, inputIdsLong) + val decoderAttentionMask: OnnxTensor = + OnnxTensor.createTensor(env, inputIdsLong.map(_.map(_ => 1L))) + val decoderPositionIDs: OnnxTensor = + OnnxTensor.createTensor(env, inputPositionIDsLong) + + val decoderInputs: java.util.Map[String, OnnxTensor] = Map( + OnnxSignatures.decoderInputIDs -> inputIdsLongTensor, + OnnxSignatures.decoderAttentionMask -> decoderAttentionMask, + OnnxSignatures.decoderPositionIDs -> decoderPositionIDs).asJava + val sessionOutput = session.run(decoderInputs) + + val sequenceLength = inputIds.head.length + val batchSize = inputIds.length + +// val logits = sessionOutput.getFloatArray(OnnxSignatures.decoderOutput) +// inputIdsLongTensor.close() +// decoderPositionIDs.close() +// decoderAttentionMask.close() +// val batchLogits = logits.grouped(vocabSize).toArray +// batchLogits + + val logitsRaw = sessionOutput.getFloatArray(OnnxSignatures.decoderOutput) + val decoderOutputs = (0 until batchSize).map(i => { + logitsRaw + .slice( + i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize, + i * sequenceLength * vocabSize + sequenceLength * vocabSize) + }) + decoderOutputs.toArray + } + + /** Gets the index with the highest score + * + * @param scores + * Array of Scores to max + * @return + * Index of the highest score + */ + private def argmax(scores: Array[Float]): Int = + scores.zipWithIndex.maxBy { case (score, _) => + score + }._2 + private def greedyGenerationFinished( + decoderIds: Seq[Array[Int]], + eosTokenId: Int, + maxOutputLength: Int): Boolean = + decoderIds.map(_.last).forall(_ == eosTokenId) || decoderIds.head.length == maxOutputLength + + private def generateGreedyOnnx( + inputIds: Array[Array[Int]], + onnxSession: (OrtSession, OrtEnvironment), + maxOutputLength: Int): (Array[Array[Int]]) = { + + val sequencesLength = inputIds.map(x => x.length).toArray + val maxSentenceLength = sequencesLength.max // - curLen + var generatedIds: Array[Array[Int]] = inputIds + while (!greedyGenerationFinished( + generatedIds, + eosTokenId, + maxOutputLength + maxSentenceLength)) { + + val (batchLogits: Array[Array[Float]]) = + Array(getDecoderOutputs(generatedIds, onnxSession).last) + + val nextTokenIds: Array[Int] = batchLogits.map(argmax) + generatedIds = + generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => + currentIds ++ Array(nextId) + } + } + generatedIds + } + + private object OnnxSignatures { + val decoderInputIDs: String = "input_ids" + val decoderAttentionMask: String = "attention_mask" + val decoderPositionIDs: String = "position_ids" + + // create decoder past for 32 layers of key and value eg. past_key_values.0.key and past_key_values.0.value + val decoderPast: Array[String] = (0 until 32) + .flatMap(i => Seq(s"past_key_values.$i.key", s"past_key_values.$i.value")) + .toArray + val decoderOutput: String = "logits" + val decoderPresent: Array[String] = + (0 until 32).flatMap(i => Seq(s"present.$i.key", s"present.$i.value")).toArray + } + +} diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/util/Generation/Generate.scala b/src/main/scala/com/johnsnowlabs/ml/ai/util/Generation/Generate.scala index 24d2ac1d3f6696..912a35409673be 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/util/Generation/Generate.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/util/Generation/Generate.scala @@ -311,7 +311,7 @@ trait Generate { beamIndices(beamIdx(elem)) :+ beamIdx(elem) } currentLength = currentLength + 1 - if (beamScorer.isDone || (expandedInputs.head.length >= maxLength)) { + if (beamScorer.isDone || (expandedInputs.head.length > maxLength)) { break } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala new file mode 100644 index 00000000000000..e080f1ff9e548e --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala @@ -0,0 +1,442 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.seq2seq + +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.ai.OLMo +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.onnx.{OnnxWrapper, ReadOnnxModel, WriteOnnxModel} +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadJsonStringAsset, + loadSentencePieceAsset, + loadTextAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.ml.util.ONNX +import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.ml.tensorflow.sentencepiece.{ + ReadSentencePieceModel, + SentencePieceWrapper, + WriteSentencePieceModel +} +import com.johnsnowlabs.nlp.serialization.MapFeature +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param._ +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession +import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} +import org.json4s._ +import org.json4s.jackson.JsonMethods._ + +/** Phi-2: Textbooks Are All You Need. + * + * Phi-2 is a Transformer with 2.7 billion parameters. It was trained using the same data sources + * as Phi-1.5, augmented with a new data source that consists of various NLP synthetic texts and + * filtered websites (for safety and educational value). When assessed against benchmarks testing + * common sense, language understanding, and logical reasoning, Phi-2 showcased a nearly + * state-of-the-art performance among models with less than 13 billion parameters. + * + * Phi-2 hasn't been fine-tuned through reinforcement learning from human feedback. The intention + * behind crafting this open-source model is to provide the research community with a + * non-restricted small model to explore vital safety challenges, such as reducing toxicity, + * understanding societal biases, enhancing controllability, and more. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val OLMo = OLMoTransformer.pretrained() + * .setInputCols("document") + * .setOutputCol("generation") + * }}} + * The default model is `"OLMo-13b"`, if no name is provided. For available pretrained models + * please see the [[https://sparknlp.org/models?q=OLMo Models Hub]]. + * + * For extended examples of usage, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala OLMoTestSpec]]. + * + * '''References:''' + * - [[https://www.microsoft.com/en-us/research/blog/phi-2-the-surprising-power-of-small-language-models/ Phi-2: Textbooks Are All You Need.]] + * - [[https://huggingface.co/microsoft/phi-2]] + * + * '''Paper Abstract:''' + * + * ''The massive increase in the size of language models to hundreds of billions of parameters + * has unlocked a host of emerging capabilities that have redefined the landscape of natural + * language processing. A question remains whether such emergent abilities can be achieved at a + * smaller scale using strategic choices for training, e.g., data selection.'' + * + * ''Our line of work with the Phi models aims to answer this question by training SLMs that + * achieve performance on par with models of much higher scale (yet still far from the frontier + * models). Our key insights for breaking the conventional language model scaling laws with Phi-2 + * are twofold:'' + * + * ''Firstly, training data quality plays a critical role in model performance. This has been + * known for decades, but we take this insight to its extreme by focusing on โ€œtextbook-qualityโ€ + * data, following upon our prior work โ€œTextbooks Are All You Need.โ€ Our training data mixture + * contains synthetic datasets specifically created to teach the model common sense reasoning and + * general knowledge, including science, daily activities, and theory of mind, among others. We + * further augment our training corpus with carefully selected web data that is filtered based on + * educational value and content quality. Secondly, we use innovative techniques to scale up, + * starting from our 1.3 billion parameter model, Phi-1.5, and embedding its knowledge within the + * 2.7 billion parameter Phi-2. This scaled knowledge transfer not only accelerates training + * convergence but shows clear boost in Phi-2 benchmark scores.'' + * + * '''Note:''' + * + * This is a very computationally expensive module especially on larger sequence. The use of an + * accelerator such as GPU is recommended. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base.DocumentAssembler + * import com.johnsnowlabs.nlp.annotators.seq2seq.OLMoTransformer + * import org.apache.spark.ml.Pipeline + * + * val documentAssembler = new DocumentAssembler() + * .setInputCol("text") + * .setOutputCol("documents") + * + * val OLMo = OLMoTransformer.pretrained("OLMo-7b") + * .setInputCols(Array("documents")) + * .setMinOutputLength(10) + * .setMaxOutputLength(50) + * .setDoSample(false) + * .setTopK(50) + * .setNoRepeatNgramSize(3) + * .setOutputCol("generation") + * + * val pipeline = new Pipeline().setStages(Array(documentAssembler, OLMo)) + * + * val data = Seq( + * "My name is Leonardo." + * ).toDF("text") + * val result = pipeline.fit(data).transform(data) + * + * results.select("generation.result").show(truncate = false) + * +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |result | + * +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[ My name is Leonardo . I am a student of the University of California, Berkeley. I am interested in the field of Artificial Intelligence and its applications in the real world. I have a strong | + * | passion for learning and am always looking for ways to improve my knowledge and skills] | + * +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * }}} + * + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ +class OLMoTransformer(override val uid: String) + extends AnnotatorModel[OLMoTransformer] + with HasBatchedAnnotate[OLMoTransformer] + with ParamsAndFeaturesWritable + with WriteOnnxModel + with HasGeneratorProperties + with HasEngine { + + def this() = this(Identifiable.randomUID("OLMoTRANSFORMER")) + + /** Input annotator type : DOCUMENT + * + * @group param + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(DOCUMENT) + + /** Output annotator type : DOCUMENT + * + * @group param + */ + override val outputAnnotatorType: String = DOCUMENT + + /** @group setParam */ + def setRandomSeed(value: Int): OLMoTransformer.this.type = { + if (randomSeed.isEmpty) { + this.randomSeed = Some(value) + } + this + } + + /** A list of token ids which are ignored in the decoder's output (Default: `Array()`) + * + * @group param + */ + var ignoreTokenIds = new IntArrayParam( + this, + "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output") + + /** @group setParam */ + def setIgnoreTokenIds(tokenIds: Array[Int]): OLMoTransformer.this.type = { + set(ignoreTokenIds, tokenIds) + } + + /** @group getParam */ + def getIgnoreTokenIds: Array[Int] = $(ignoreTokenIds) + + /** Vocabulary used to encode the words to ids with bpeTokenizer.encode + * + * @group param + */ + val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() + + /** @group setParam */ + def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value) + + /** Holding merges.txt coming from RoBERTa model + * + * @group param + */ + val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected() + + /** @group setParam */ + def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value) + + private var _model: Option[Broadcast[OLMo]] = None + + val generationConfig: StructFeature[GenerationConfig] = + new StructFeature(this, "generationConfig").setProtected() + + def setGenerationConfig(value: GenerationConfig): this.type = + set(generationConfig, value) + + def getGenerationConfig: GenerationConfig = $$(generationConfig) + + /** @group setParam */ + def setModelIfNotSet(spark: SparkSession, onnxWrappers: DecoderWrappers): this.type = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new OLMo( + onnxWrappers, + $$(merges), + $$(vocabulary), + generationConfig = getGenerationConfig))) + } + this + } + + /** @group getParam */ + def getModelIfNotSet: OLMo = _model.get.value + + setDefault( + minOutputLength -> 0, + maxOutputLength -> 20, + doSample -> false, + temperature -> 0.6, + topK -> 50, + topP -> 0.9, + repetitionPenalty -> 1.0, + noRepeatNgramSize -> 3, + ignoreTokenIds -> Array(), + batchSize -> 1, + beamSize -> 1, + maxInputLength -> 4096) + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations that correspond to inputAnnotationCols generated by previous annotators if any + * @return + * any number of annotations processed for every input annotation. Not necessary one to one + * relationship + */ + override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { + + val allAnnotations = batchedAnnotations + .filter(_.nonEmpty) + .zipWithIndex + .flatMap { case (annotations, i) => + annotations.filter(_.result.nonEmpty).map(x => (x, i)) + } + val processedAnnotations = if (allAnnotations.nonEmpty) { + this.getModelIfNotSet.predict( + sentences = allAnnotations.map(_._1), + batchSize = $(batchSize), + minOutputLength = $(minOutputLength), + maxOutputLength = $(maxOutputLength), + doSample = $(doSample), + temperature = $(temperature), + topK = $(topK), + topP = $(topP), + repetitionPenalty = $(repetitionPenalty), + noRepeatNgramSize = $(noRepeatNgramSize), + randomSeed = this.randomSeed, + ignoreTokenIds = $(ignoreTokenIds), + beamSize = $(beamSize), + maxInputLength = $(maxInputLength)) + } else { + Seq() + } + Seq(processedAnnotations) + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + getEngine match { + case ONNX.name => + val wrappers = getModelIfNotSet.onnxWrappers + writeOnnxModels( + path, + spark, + Seq((wrappers.decoder, "decoder_model.onnx")), + OLMoTransformer.suffix) + } + } +} + +trait ReadablePretrainedOLMoTransformerModel + extends ParamsAndFeaturesReadable[OLMoTransformer] + with HasPretrained[OLMoTransformer] { + override val defaultModelName: Some[String] = Some("OLMo-7b") + + /** Java compliant-overrides */ + override def pretrained(): OLMoTransformer = super.pretrained() + + override def pretrained(name: String): OLMoTransformer = super.pretrained(name) + + override def pretrained(name: String, lang: String): OLMoTransformer = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): OLMoTransformer = + super.pretrained(name, lang, remoteLoc) +} + +trait ReadOLMoTransformerDLModel extends ReadOnnxModel { + this: ParamsAndFeaturesReadable[OLMoTransformer] => + + override val onnxFile: String = "olmo_onnx" + val suffix: String = "_olmo" + + def readModel(instance: OLMoTransformer, path: String, spark: SparkSession): Unit = { + instance.getEngine match { + case ONNX.name => + val wrappers = + readOnnxModels(path, spark, Seq("decoder_model.onnx"), suffix) + val onnxWrappers = + DecoderWrappers(decoder = wrappers("decoder_model.onnx")) + instance.setModelIfNotSet(spark, onnxWrappers) + case _ => + throw new Exception(notSupportedEngineError) + } + } + + addReader(readModel) + + def loadSavedModel(modelPath: String, spark: SparkSession): OLMoTransformer = { + implicit val formats: DefaultFormats.type = DefaultFormats // for json4 + val (localModelPath, detectedEngine) = + modelSanityCheck(modelPath, isDecoder = true) + val modelConfig: JValue = + parse(loadJsonStringAsset(localModelPath, "config.json")) + + val beginSuppressTokens: Array[Int] = + (modelConfig \ "begin_suppress_tokens").extract[Array[Int]] + + val suppressTokenIds: Array[Int] = + (modelConfig \ "suppress_tokens").extract[Array[Int]] + + val forcedDecoderIds: Array[(Int, Int)] = + (modelConfig \ "forced_decoder_ids").extract[Array[Array[Int]]].map { + case idxWithTokenId: Array[Int] if idxWithTokenId.length == 2 => + (idxWithTokenId(0), idxWithTokenId(1)) + case _ => + throw new Exception( + "Could not extract forced_decoder_ids. Should be a list of tuples with 2 entries.") + } + + def arrayOrNone[T](array: Array[T]): Option[Array[T]] = + if (array.nonEmpty) Some(array) else None + + var bosTokenId = -1 + try { + bosTokenId = (modelConfig \ "bos_token_id").extract[Int] + } catch { + case _: Exception => + println("Could not extract bos_token_id from config.json, assigning default value -1") + } + val eosTokenId = (modelConfig \ "eos_token_id").extract[Int] + val padTokenId = (modelConfig \ "eos_token_id").extract[Int] + val vocabSize = (modelConfig \ "vocab_size").extract[Int] + + val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap + + val bytePairs = loadTextAsset(localModelPath, "merges.txt") + .map(_.split(" ")) + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + + val annotatorModel = new OLMoTransformer() + .setGenerationConfig( + GenerationConfig( + bosTokenId, + padTokenId, + eosTokenId, + vocabSize, + arrayOrNone(beginSuppressTokens), + arrayOrNone(suppressTokenIds), + arrayOrNone(forcedDecoderIds))) + .setVocabulary(vocabs) + .setMerges(bytePairs) + + annotatorModel.set(annotatorModel.engine, detectedEngine) + + detectedEngine match { + case ONNX.name => + val onnxWrapperDecoder = + OnnxWrapper.read( + localModelPath, + zipped = false, + useBundle = true, + modelName = "decoder_model", + dataFileSuffix = ".onnx_data") + + val onnxWrappers = DecoderWrappers(onnxWrapperDecoder) + + annotatorModel + .setModelIfNotSet(spark, onnxWrappers) + + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } + +} + +object OLMoTransformer + extends ReadablePretrainedOLMoTransformerModel + with ReadOLMoTransformerDLModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeSpecialTokens.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeSpecialTokens.scala index 4afb1d5b9bf18c..4e790cf171a1cd 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeSpecialTokens.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeSpecialTokens.scala @@ -137,6 +137,14 @@ private[johnsnowlabs] object SpecialTokens { unkTokenString = "", maskTokenString = "", padTokenString = "") + case "olmo" => + SpecialTokens( + vocab, + startTokenString = "<|endoftext|>", + endTokenString = "<|endoftext|>", + unkTokenString = "<|endoftext|>", + maskTokenString = "<|endoftext|>", + padTokenString = "<|padding|>") case "clip" => SpecialTokens( vocab, diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala index 8c72a8f99d6685..e5d075e31317ad 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala @@ -352,6 +352,13 @@ object BpeTokenizer { modelSpecialTokens(), padWithSequenceTokens, addPrefixSpaceToSentence = addPrefixSpaceToSentence) + case "olmo" => + new OLMoTokenizer( + merges, + vocab, + modelSpecialTokens(), + padWithSequenceTokens, + addPrefixSpaceToSentence = addPrefixSpaceToSentence) case "clip" => new CLIPTokenizer(merges, vocab, modelSpecialTokens()) case "phi2" => diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/OLMoTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/OLMoTokenizer.scala new file mode 100644 index 00000000000000..95f046f5913670 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/OLMoTokenizer.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2017-2023 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.tokenizer.bpe + +class OLMoTokenizer( + merges: Map[(String, String), Int], + vocab: Map[String, Int], + specialTokens: SpecialTokens, + padWithSequenceTokens: Boolean = false, + addPrefixSpaceToSentence: Boolean = false) + extends Gpt2Tokenizer( + merges, + vocab, + specialTokens, + padWithSequenceTokens, + prependString = "ฤ ", + addPrefixSpaceToSentence) diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala new file mode 100644 index 00000000000000..57cd34349c2ea4 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2017-2023 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.seq2seq + +import com.johnsnowlabs.nlp.base.DocumentAssembler +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec + +class OLMoTestSpec extends AnyFlatSpec { + + "phi2" should "should handle temperature=0 correctly and not crash when predicting more than 1 element with doSample=True" taggedAs FastTest in { + // Even tough the Paper states temperature in interval [0,1), using temperature=0 will result in division by 0 error. + // Also DoSample=True may result in infinities being generated and distFiltered.length==0 which results in exception if we don't return 0 instead internally. + val testData = ResourceHelper.spark + .createDataFrame(Seq((1, "My name is Leonardo."))) + .toDF("id", "text") + .repartition(1) + val documentAssembler = new DocumentAssembler() + .setInputCol("text") + .setOutputCol("documents") + + val bart = OLMoTransformer + .loadSavedModel( + "/home/prabod/Projects/ModelZoo/OLMO/onnx_models/allenai/OLMo-1B-hf_int4/", + ResourceHelper.spark) + .setInputCols(Array("documents")) + .setDoSample(false) + .setMaxOutputLength(100) + .setOutputCol("generation") + .setBeamSize(1) + new Pipeline() + .setStages(Array(documentAssembler, bart)) + .fit(testData) + .transform(testData) + .show(truncate = false) + + } +} From 32635d2cdadb9ae593086fd9bc85f0363d92dc55 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Mon, 22 Apr 2024 13:44:39 +0000 Subject: [PATCH 028/108] added OLMo scala api --- src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala b/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala index 8cfe84b379b14d..fd9e330abf8690 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala @@ -59,7 +59,6 @@ private[johnsnowlabs] class OLMo( * Sequence of decoded sentences */ def decode(sentences: Array[Array[Int]]): Seq[String] = { - println(sentences.map(_.mkString(" ")).mkString("\n")) sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt))) } From d52d4e04108a5d34748bec4a4f782a1a250ee2a0 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 24 Apr 2024 12:48:36 +0000 Subject: [PATCH 029/108] added OLMo python API and tests --- python/sparknlp/annotator/seq2seq/__init__.py | 1 + .../annotator/seq2seq/olmo_transformer.py | 326 ++++++++++++++++++ python/sparknlp/internal/__init__.py | 8 +- .../seq2seq/olmo_transformer_test.py | 47 +++ .../annotators/seq2seq/OLMoTransformer.scala | 53 +-- .../nlp/annotators/seq2seq/OLMoTestSpec.scala | 6 +- 6 files changed, 401 insertions(+), 40 deletions(-) create mode 100644 python/sparknlp/annotator/seq2seq/olmo_transformer.py create mode 100644 python/test/annotator/seq2seq/olmo_transformer_test.py diff --git a/python/sparknlp/annotator/seq2seq/__init__.py b/python/sparknlp/annotator/seq2seq/__init__.py index e9c3984c21ecc1..45f44b44ccc400 100644 --- a/python/sparknlp/annotator/seq2seq/__init__.py +++ b/python/sparknlp/annotator/seq2seq/__init__.py @@ -28,3 +28,4 @@ from sparknlp.annotator.seq2seq.qwen_transformer import * from sparknlp.annotator.seq2seq.starcoder_transformer import * from sparknlp.annotator.seq2seq.llama3_transformer import * +from sparknlp.annotator.seq2seq.olmo_transformer import * diff --git a/python/sparknlp/annotator/seq2seq/olmo_transformer.py b/python/sparknlp/annotator/seq2seq/olmo_transformer.py new file mode 100644 index 00000000000000..3e0eb2269c3a83 --- /dev/null +++ b/python/sparknlp/annotator/seq2seq/olmo_transformer.py @@ -0,0 +1,326 @@ +# Copyright 2017-2022 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains classes for the OLMoTransformer.""" + +from sparknlp.common import * + + +class OLMoTransformer(AnnotatorModel, HasBatchedAnnotate, HasEngine): + """OLMo: Open Language Models + + OLMo is a series of Open Language Models designed to enable the science of language models. + The OLMo models are trained on the Dolma dataset. We release all code, checkpoints, logs + (coming soon), and details involved in training these models. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion + object: + + >>> olmo = OLMoTransformer.pretrained() \\ + ... .setInputCols(["document"]) \\ + ... .setOutputCol("generation") + + + The default model is ``"llam2-7b"``, if no name is provided. For available + pretrained models please see the `Models Hub + `__. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``DOCUMENT`` ``DOCUMENT`` + ====================== ====================== + + Parameters + ---------- + configProtoBytes + ConfigProto from tensorflow, serialized into byte array. + minOutputLength + Minimum length of the sequence to be generated, by default 0 + maxOutputLength + Maximum length of output text, by default 20 + doSample + Whether or not to use sampling; use greedy decoding otherwise, by default False + temperature + The value used to module the next token probabilities, by default 1.0 + topK + The number of highest probability vocabulary tokens to keep for + top-k-filtering, by default 50 + topP + Top cumulative probability for vocabulary tokens, by default 1.0 + + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + repetitionPenalty + The parameter for repetition penalty, 1.0 means no penalty. , by default + 1.0 + noRepeatNgramSize + If set to int > 0, all ngrams of that size can only occur once, by + default 0 + ignoreTokenIds + A list of token ids which are ignored in the decoder's output, by + default [] + + Notes + ----- + This is a very computationally expensive module especially on larger + sequence. The use of an accelerator such as GPU is recommended. + + References + ---------- + - `OLMo Project Page. + `__ + - `OLMO GitHub Repository. + `__ + - `OLMo: Accelerating the Science of Language Models + `__ + + **Paper Abstract:** + + *Language models (LMs) have become ubiquitous in both NLP research and in commercial product offerings. + As their commercial importance has surged, the most powerful models have become closed off, gated behind + proprietary interfaces, with important details of their training data, architectures, and development + undisclosed. Given the importance of these details in scientifically studying these models, including + their biases and potential risks, we believe it is essential for the research community to have access + to powerful, truly open LMs. To this end, this technical report details the first release of OLMo, + a state-of-the-art, truly Open Language Model and its framework to build and study the science of + language modeling. Unlike most prior efforts that have only released model weights and inference code, + we release OLMo and the whole framework, including training data and training and evaluation code. + We hope this release will empower and strengthen the open research community and inspire a new wave + of innovation.* + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> documentAssembler = DocumentAssembler() \\ + ... .setInputCol("text") \\ + ... .setOutputCol("documents") + >>> olmo = OLMoTransformer.pretrained("olmo-7b") \\ + ... .setInputCols(["documents"]) \\ + ... .setMaxOutputLength(50) \\ + ... .setOutputCol("generation") + >>> pipeline = Pipeline().setStages([documentAssembler, olmo]) + >>> data = spark.createDataFrame([["My name is Leonardo."]]).toDF("text") + >>> result = pipeline.fit(data).transform(data) + >>> result.select("summaries.generation").show(truncate=False) + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |result | + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |[My name is Leonardo . I am a student of the University of California, Berkeley. I am interested in the field of Artificial Intelligence and its applications in the real world. I have a strong | + | passion for learning and am always looking for ways to improve my knowledge and skills] | + -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + """ + + name = "OLMoTransformer" + + inputAnnotatorTypes = [AnnotatorType.DOCUMENT] + + outputAnnotatorType = AnnotatorType.DOCUMENT + + configProtoBytes = Param(Params._dummy(), "configProtoBytes", + "ConfigProto from tensorflow, serialized into byte array. Get with config_proto.SerializeToString()", + TypeConverters.toListInt) + + minOutputLength = Param(Params._dummy(), "minOutputLength", "Minimum length of the sequence to be generated", + typeConverter=TypeConverters.toInt) + + maxOutputLength = Param(Params._dummy(), "maxOutputLength", "Maximum length of output text", + typeConverter=TypeConverters.toInt) + + doSample = Param(Params._dummy(), "doSample", "Whether or not to use sampling; use greedy decoding otherwise", + typeConverter=TypeConverters.toBoolean) + + temperature = Param(Params._dummy(), "temperature", "The value used to module the next token probabilities", + typeConverter=TypeConverters.toFloat) + + topK = Param(Params._dummy(), "topK", + "The number of highest probability vocabulary tokens to keep for top-k-filtering", + typeConverter=TypeConverters.toInt) + + topP = Param(Params._dummy(), "topP", + "If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or higher are kept for generation", + typeConverter=TypeConverters.toFloat) + + repetitionPenalty = Param(Params._dummy(), "repetitionPenalty", + "The parameter for repetition penalty. 1.0 means no penalty. See `this paper `__ for more details", + typeConverter=TypeConverters.toFloat) + + noRepeatNgramSize = Param(Params._dummy(), "noRepeatNgramSize", + "If set to int > 0, all ngrams of that size can only occur once", + typeConverter=TypeConverters.toInt) + + ignoreTokenIds = Param(Params._dummy(), "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output", + typeConverter=TypeConverters.toListInt) + + def setIgnoreTokenIds(self, value): + """A list of token ids which are ignored in the decoder's output. + + Parameters + ---------- + value : List[int] + The words to be filtered out + """ + return self._set(ignoreTokenIds=value) + + def setConfigProtoBytes(self, b): + """Sets configProto from tensorflow, serialized into byte array. + + Parameters + ---------- + b : List[int] + ConfigProto from tensorflow, serialized into byte array + """ + return self._set(configProtoBytes=b) + + def setMinOutputLength(self, value): + """Sets minimum length of the sequence to be generated. + + Parameters + ---------- + value : int + Minimum length of the sequence to be generated + """ + return self._set(minOutputLength=value) + + def setMaxOutputLength(self, value): + """Sets maximum length of output text. + + Parameters + ---------- + value : int + Maximum length of output text + """ + return self._set(maxOutputLength=value) + + def setDoSample(self, value): + """Sets whether or not to use sampling, use greedy decoding otherwise. + + Parameters + ---------- + value : bool + Whether or not to use sampling; use greedy decoding otherwise + """ + return self._set(doSample=value) + + def setTemperature(self, value): + """Sets the value used to module the next token probabilities. + + Parameters + ---------- + value : float + The value used to module the next token probabilities + """ + return self._set(temperature=value) + + def setTopK(self, value): + """Sets the number of highest probability vocabulary tokens to keep for + top-k-filtering. + + Parameters + ---------- + value : int + Number of highest probability vocabulary tokens to keep + """ + return self._set(topK=value) + + def setTopP(self, value): + """Sets the top cumulative probability for vocabulary tokens. + + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + + Parameters + ---------- + value : float + Cumulative probability for vocabulary tokens + """ + return self._set(topP=value) + + def setRepetitionPenalty(self, value): + """Sets the parameter for repetition penalty. 1.0 means no penalty. + + Parameters + ---------- + value : float + The repetition penalty + + References + ---------- + See `Ctrl: A Conditional Transformer Language Model For Controllable + Generation `__ for more details. + """ + return self._set(repetitionPenalty=value) + + def setNoRepeatNgramSize(self, value): + """Sets size of n-grams that can only occur once. + + If set to int > 0, all ngrams of that size can only occur once. + + Parameters + ---------- + value : int + N-gram size can only occur once + """ + return self._set(noRepeatNgramSize=value) + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.seq2seq.OLMoTransformer", java_model=None): + super(OLMoTransformer, self).__init__(classname=classname, java_model=java_model) + self._setDefault(minOutputLength=0, maxOutputLength=20, doSample=False, temperature=0.6, topK=50, topP=0.9, + repetitionPenalty=1.0, noRepeatNgramSize=0, ignoreTokenIds=[], batchSize=1) + + @staticmethod + def loadSavedModel(folder, spark_session): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + OLMoTransformer + The restored model + """ + from sparknlp.internal import _OLMoLoader + jModel = _OLMoLoader(folder, spark_session._jsparkSession)._java_obj + return OLMoTransformer(java_model=jModel) + + @staticmethod + def pretrained(name="olmo-1b", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default "olmo-7b" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + OLMoTransformer + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(OLMoTransformer, name, lang, remote_loc) diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..f96ba37bcbe57a 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -345,6 +345,10 @@ def __init__(self, path, jspark): ) +class _OLMoLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark): + super(_OLMoLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.seq2seq.OLMoTransformer.loadSavedModel", path, jspark) class _Phi2Loader(ExtendedJavaWrapper): def __init__(self, path, jspark, use_openvino=False): super(_Phi2Loader, self).__init__( @@ -992,8 +996,8 @@ class _AutoGGUFLoader(ExtendedJavaWrapper): def __init__(self, path, jspark): super(_AutoGGUFLoader, self).__init__( "com.johnsnowlabs.nlp.annotators.seq2seq.AutoGGUFModel.loadSavedModel", path, jspark) - - + + class _MxbaiEmbeddingsLoader(ExtendedJavaWrapper): def __init__(self, path, jspark): super(_MxbaiEmbeddingsLoader, self).__init__( diff --git a/python/test/annotator/seq2seq/olmo_transformer_test.py b/python/test/annotator/seq2seq/olmo_transformer_test.py new file mode 100644 index 00000000000000..8c09b3cfa2e4cf --- /dev/null +++ b/python/test/annotator/seq2seq/olmo_transformer_test.py @@ -0,0 +1,47 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +import pytest + +from sparknlp.annotator import * +from sparknlp.base import * +from test.util import SparkContextForTest + + +@pytest.mark.slow +class OLMoTransformerTextGenerationTestSpec(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + + def runTest(self): + data = self.spark.createDataFrame([ + [1, """Leonardo Da Vinci invented the microscope?""".strip().replace("\n", " ")]]).toDF("id", "text") + + document_assembler = DocumentAssembler() \ + .setInputCol("text") \ + .setOutputCol("documents") + + olmo = OLMoTransformer \ + .pretrained() \ + .setMaxOutputLength(50) \ + .setDoSample(False) \ + .setInputCols(["documents"]) \ + .setOutputCol("generation") + + pipeline = Pipeline().setStages([document_assembler, olmo]) + results = pipeline.fit(data).transform(data) + + results.select("generation.result").show(truncate=False) + diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala index e080f1ff9e548e..17ee68463d18b0 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala @@ -44,18 +44,10 @@ import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} import org.json4s._ import org.json4s.jackson.JsonMethods._ -/** Phi-2: Textbooks Are All You Need. +/** OLMo: Open Language Models * - * Phi-2 is a Transformer with 2.7 billion parameters. It was trained using the same data sources - * as Phi-1.5, augmented with a new data source that consists of various NLP synthetic texts and - * filtered websites (for safety and educational value). When assessed against benchmarks testing - * common sense, language understanding, and logical reasoning, Phi-2 showcased a nearly - * state-of-the-art performance among models with less than 13 billion parameters. - * - * Phi-2 hasn't been fine-tuned through reinforcement learning from human feedback. The intention - * behind crafting this open-source model is to provide the research community with a - * non-restricted small model to explore vital safety challenges, such as reducing toxicity, - * understanding societal biases, enhancing controllability, and more. + * OLMo is a series of Open Language Models designed to enable the science of language models. + * The OLMo models are trained on the Dolma dataset. * * Pretrained models can be loaded with `pretrained` of the companion object: * {{{ @@ -63,38 +55,31 @@ import org.json4s.jackson.JsonMethods._ * .setInputCols("document") * .setOutputCol("generation") * }}} - * The default model is `"OLMo-13b"`, if no name is provided. For available pretrained models + * The default model is `"OLMo-1b"`, if no name is provided. For available pretrained models * please see the [[https://sparknlp.org/models?q=OLMo Models Hub]]. * * For extended examples of usage, see * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala OLMoTestSpec]]. * * '''References:''' - * - [[https://www.microsoft.com/en-us/research/blog/phi-2-the-surprising-power-of-small-language-models/ Phi-2: Textbooks Are All You Need.]] - * - [[https://huggingface.co/microsoft/phi-2]] + * - [[https://allenai.org/olmo OLMo Project Page.]] + * - [[https://github.com/allenai/OLMo OLMo GitHub Repository.]] + * - [[https://arxiv.org/pdf/2402.00838.pdf OLMo: Accelerating the Science of Language Models]] * * '''Paper Abstract:''' * - * ''The massive increase in the size of language models to hundreds of billions of parameters - * has unlocked a host of emerging capabilities that have redefined the landscape of natural - * language processing. A question remains whether such emergent abilities can be achieved at a - * smaller scale using strategic choices for training, e.g., data selection.'' - * - * ''Our line of work with the Phi models aims to answer this question by training SLMs that - * achieve performance on par with models of much higher scale (yet still far from the frontier - * models). Our key insights for breaking the conventional language model scaling laws with Phi-2 - * are twofold:'' - * - * ''Firstly, training data quality plays a critical role in model performance. This has been - * known for decades, but we take this insight to its extreme by focusing on โ€œtextbook-qualityโ€ - * data, following upon our prior work โ€œTextbooks Are All You Need.โ€ Our training data mixture - * contains synthetic datasets specifically created to teach the model common sense reasoning and - * general knowledge, including science, daily activities, and theory of mind, among others. We - * further augment our training corpus with carefully selected web data that is filtered based on - * educational value and content quality. Secondly, we use innovative techniques to scale up, - * starting from our 1.3 billion parameter model, Phi-1.5, and embedding its knowledge within the - * 2.7 billion parameter Phi-2. This scaled knowledge transfer not only accelerates training - * convergence but shows clear boost in Phi-2 benchmark scores.'' + * ''Language models (LMs) have become ubiquitous in both NLP research and in commercial product + * offerings. As their commercial importance has surged, the most powerful models have become + * closed off, gated behind proprietary interfaces, with important details of their training + * data, architectures, and development undisclosed. Given the importance of these details in + * scientifically studying these models, including their biases and potential risks, we believe + * it is essential for the research community to have access to powerful, truly open LMs. To this + * end, this technical report details the first release of OLMo, a state-of-the-art, truly Open + * Language Model and its framework to build and study the science of language modeling. Unlike + * most prior efforts that have only released model weights and inference code, we release OLMo + * and the whole framework, including training data and training and evaluation code. We hope + * this release will empower and strengthen the open research community and inspire a new wave of + * innovation.'' * * '''Note:''' * diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala index 57cd34349c2ea4..a1a1ee5365b47d 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala @@ -24,7 +24,7 @@ import org.scalatest.flatspec.AnyFlatSpec class OLMoTestSpec extends AnyFlatSpec { - "phi2" should "should handle temperature=0 correctly and not crash when predicting more than 1 element with doSample=True" taggedAs FastTest in { + "olmo" should "should handle temperature=0 correctly and not crash when predicting more than 1 element with doSample=True" taggedAs FastTest in { // Even tough the Paper states temperature in interval [0,1), using temperature=0 will result in division by 0 error. // Also DoSample=True may result in infinities being generated and distFiltered.length==0 which results in exception if we don't return 0 instead internally. val testData = ResourceHelper.spark @@ -36,9 +36,7 @@ class OLMoTestSpec extends AnyFlatSpec { .setOutputCol("documents") val bart = OLMoTransformer - .loadSavedModel( - "/home/prabod/Projects/ModelZoo/OLMO/onnx_models/allenai/OLMo-1B-hf_int4/", - ResourceHelper.spark) + .pretrained() .setInputCols(Array("documents")) .setDoSample(false) .setMaxOutputLength(100) From 2eedcb3ce1e5594ba8a0ce86f1b7d09270b7ea7c Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 12 Feb 2025 12:01:09 +0000 Subject: [PATCH 030/108] OlMo Notebook and bug fixes Signed-off-by: Prabod Rathnayaka --- .../HuggingFace_ONNX_in_Spark_NLP_OLMO.ipynb | 1217 +++++++++++++++++ .../scala/com/johnsnowlabs/ml/ai/OLMo.scala | 4 +- .../ml/onnx/OnnxSerializeModel.scala | 9 +- .../johnsnowlabs/ml/onnx/OnnxWrapper.scala | 4 +- .../annotators/seq2seq/OLMoTransformer.scala | 19 +- .../nlp/annotators/seq2seq/OLMoTestSpec.scala | 27 +- 6 files changed, 1269 insertions(+), 11 deletions(-) create mode 100644 examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_OLMO.ipynb diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_OLMO.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_OLMO.ipynb new file mode 100644 index 00000000000000..dd2d0b08b4f8f7 --- /dev/null +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_OLMO.ipynb @@ -0,0 +1,1217 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "GB-OotnsS-JG" + }, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_OLMO.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "gRuRMH7QS-JI" + }, + "source": [ + "## Import ONNX OLMO models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- ONNX support was introduced in `Spark NLP 5.0.0`, enabling high performance inference for models.\n", + "- You can import OLMO models via `OLMOModel`. These models are usually under `Text2Text Generation` category and have `OLMO` in their labels\n", + "- This is a very computationally expensive module especially on larger sequence. The use of an accelerator such as GPU is recommended.\n", + "- Reference: [OLMOModel](https://huggingface.co/docs/transformers/en/model_doc/OLMO)\n", + "- Some [example models](https://huggingface.co/models?other=OLMO)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Vd98DUZxS-JJ" + }, + "source": [ + "## Export and Save HuggingFace model\n", + "\n", + "- Let's install `transformers` package with the `onnx` extension and it's dependencies. You don't need `onnx` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.0`. This doesn't mean it won't work with the future releases\n", + "- We will also need `sentencepiece` for tokenization." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "wFf3GagOS-JJ", + "outputId": "78b6529d-afad-414c-baa3-e8087061072f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: optimum in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (1.24.0)\n", + "Requirement already satisfied: sentencepiece in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (0.2.0)\n", + "Requirement already satisfied: onnx in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (1.17.0)\n", + "Requirement already satisfied: onnxruntime in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (1.19.2)\n", + "Collecting ai2-olmo\n", + " Downloading ai2_olmo-0.6.0-py3-none-any.whl.metadata (25 kB)\n", + "Requirement already satisfied: transformers>=4.29 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from optimum) (4.41.0)\n", + "Requirement already satisfied: torch>=1.11 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from optimum) (2.6.0)\n", + "Requirement already satisfied: packaging in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from optimum) (24.2)\n", + "Requirement already satisfied: numpy in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from optimum) (2.0.2)\n", + "Requirement already satisfied: huggingface-hub>=0.8.0 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from optimum) (0.28.1)\n", + "Requirement already satisfied: protobuf>=3.20.2 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from onnx) (3.20.2)\n", + "Requirement already satisfied: coloredlogs in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from onnxruntime) (15.0.1)\n", + "Requirement already satisfied: flatbuffers in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from onnxruntime) (25.2.10)\n", + "Requirement already satisfied: sympy in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from onnxruntime) (1.13.1)\n", + "Collecting numpy (from optimum)\n", + " Using cached numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", + "Collecting ai2-olmo-core==0.1.0 (from ai2-olmo)\n", + " Downloading ai2_olmo_core-0.1.0-py3-none-any.whl.metadata (14 kB)\n", + "Collecting omegaconf (from ai2-olmo)\n", + " Using cached omegaconf-2.3.0-py3-none-any.whl.metadata (3.9 kB)\n", + "Collecting rich (from ai2-olmo)\n", + " Downloading rich-13.9.4-py3-none-any.whl.metadata (18 kB)\n", + "Collecting boto3 (from ai2-olmo)\n", + " Downloading boto3-1.36.18-py3-none-any.whl.metadata (6.7 kB)\n", + "Collecting google-cloud-storage (from ai2-olmo)\n", + " Downloading google_cloud_storage-3.0.0-py2.py3-none-any.whl.metadata (12 kB)\n", + "Requirement already satisfied: tokenizers in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from ai2-olmo) (0.19.1)\n", + "Collecting cached_path>=1.6.2 (from ai2-olmo)\n", + " Downloading cached_path-1.6.7-py3-none-any.whl.metadata (19 kB)\n", + "Collecting importlib_resources (from ai2-olmo)\n", + " Downloading importlib_resources-6.5.2-py3-none-any.whl.metadata (3.9 kB)\n", + "Requirement already satisfied: safetensors in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from ai2-olmo-core==0.1.0->ai2-olmo) (0.5.2)\n", + "Collecting pydantic<3.0,>=2.0 (from ai2-olmo-core==0.1.0->ai2-olmo)\n", + " Downloading pydantic-2.10.6-py3-none-any.whl.metadata (30 kB)\n", + "Requirement already satisfied: requests in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from ai2-olmo-core==0.1.0->ai2-olmo) (2.32.3)\n", + "Requirement already satisfied: filelock<4.0,>=3.4 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from cached_path>=1.6.2->ai2-olmo) (3.17.0)\n", + "Collecting google-cloud-storage (from ai2-olmo)\n", + " Downloading google_cloud_storage-2.19.0-py2.py3-none-any.whl.metadata (9.1 kB)\n", + "Collecting huggingface-hub>=0.8.0 (from optimum)\n", + " Downloading huggingface_hub-0.27.1-py3-none-any.whl.metadata (13 kB)\n", + "Collecting botocore<1.37.0,>=1.36.18 (from boto3->ai2-olmo)\n", + " Downloading botocore-1.36.18-py3-none-any.whl.metadata (5.7 kB)\n", + "Collecting jmespath<2.0.0,>=0.7.1 (from boto3->ai2-olmo)\n", + " Using cached jmespath-1.0.1-py3-none-any.whl.metadata (7.6 kB)\n", + "Collecting s3transfer<0.12.0,>=0.11.0 (from boto3->ai2-olmo)\n", + " Downloading s3transfer-0.11.2-py3-none-any.whl.metadata (1.7 kB)\n", + "Collecting google-auth<3.0dev,>=2.26.1 (from google-cloud-storage->ai2-olmo)\n", + " Downloading google_auth-2.38.0-py2.py3-none-any.whl.metadata (4.8 kB)\n", + "Collecting google-api-core<3.0.0dev,>=2.15.0 (from google-cloud-storage->ai2-olmo)\n", + " Downloading google_api_core-2.24.1-py3-none-any.whl.metadata (3.0 kB)\n", + "Collecting google-cloud-core<3.0dev,>=2.3.0 (from google-cloud-storage->ai2-olmo)\n", + " Using cached google_cloud_core-2.4.1-py2.py3-none-any.whl.metadata (2.7 kB)\n", + "Collecting google-resumable-media>=2.7.2 (from google-cloud-storage->ai2-olmo)\n", + " Downloading google_resumable_media-2.7.2-py2.py3-none-any.whl.metadata (2.2 kB)\n", + "Collecting google-crc32c<2.0dev,>=1.0 (from google-cloud-storage->ai2-olmo)\n", + " Downloading google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.3 kB)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from huggingface-hub>=0.8.0->optimum) (2025.2.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from huggingface-hub>=0.8.0->optimum) (6.0.2)\n", + "Requirement already satisfied: tqdm>=4.42.1 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from huggingface-hub>=0.8.0->optimum) (4.67.1)\n", + "Requirement already satisfied: typing-extensions>=3.7.4.3 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from huggingface-hub>=0.8.0->optimum) (4.12.2)\n", + "Collecting markdown-it-py>=2.2.0 (from rich->ai2-olmo)\n", + " Using cached markdown_it_py-3.0.0-py3-none-any.whl.metadata (6.9 kB)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from rich->ai2-olmo) (2.19.1)\n", + "Requirement already satisfied: networkx in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (3.2.1)\n", + "Requirement already satisfied: jinja2 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (3.1.5)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (12.4.127)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (12.4.127)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (12.4.127)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (9.1.0.70)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (12.4.5.8)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (11.2.1.3)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (10.3.5.147)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (11.6.1.9)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (12.3.1.170)\n", + "Requirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (0.6.2)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (2.21.5)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (12.4.127)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (12.4.127)\n", + "Requirement already satisfied: triton==3.2.0 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from torch>=1.11->optimum) (3.2.0)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from sympy->onnxruntime) (1.3.0)\n", + "Requirement already satisfied: regex!=2019.12.17 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from transformers>=4.29->optimum) (2024.11.6)\n", + "Requirement already satisfied: humanfriendly>=9.1 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from coloredlogs->onnxruntime) (10.0)\n", + "Collecting zipp>=3.1.0 (from importlib_resources->ai2-olmo)\n", + " Downloading zipp-3.21.0-py3-none-any.whl.metadata (3.7 kB)\n", + "Collecting antlr4-python3-runtime==4.9.* (from omegaconf->ai2-olmo)\n", + " Using cached antlr4_python3_runtime-4.9.3-py3-none-any.whl\n", + "Requirement already satisfied: python-dateutil<3.0.0,>=2.1 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from botocore<1.37.0,>=1.36.18->boto3->ai2-olmo) (2.9.0.post0)\n", + "Collecting urllib3<1.27,>=1.25.4 (from botocore<1.37.0,>=1.36.18->boto3->ai2-olmo)\n", + " Downloading urllib3-1.26.20-py2.py3-none-any.whl.metadata (50 kB)\n", + "Collecting googleapis-common-protos<2.0.dev0,>=1.56.2 (from google-api-core<3.0.0dev,>=2.15.0->google-cloud-storage->ai2-olmo)\n", + " Downloading googleapis_common_protos-1.67.0rc1-py2.py3-none-any.whl.metadata (5.1 kB)\n", + "Collecting proto-plus<2.0.0dev,>=1.22.3 (from google-api-core<3.0.0dev,>=2.15.0->google-cloud-storage->ai2-olmo)\n", + " Downloading proto_plus-1.26.0-py3-none-any.whl.metadata (2.2 kB)\n", + "Collecting cachetools<6.0,>=2.0.0 (from google-auth<3.0dev,>=2.26.1->google-cloud-storage->ai2-olmo)\n", + " Downloading cachetools-5.5.1-py3-none-any.whl.metadata (5.4 kB)\n", + "Collecting pyasn1-modules>=0.2.1 (from google-auth<3.0dev,>=2.26.1->google-cloud-storage->ai2-olmo)\n", + " Downloading pyasn1_modules-0.4.1-py3-none-any.whl.metadata (3.5 kB)\n", + "Collecting rsa<5,>=3.1.4 (from google-auth<3.0dev,>=2.26.1->google-cloud-storage->ai2-olmo)\n", + " Using cached rsa-4.9-py3-none-any.whl.metadata (4.2 kB)\n", + "Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich->ai2-olmo)\n", + " Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)\n", + "Collecting annotated-types>=0.6.0 (from pydantic<3.0,>=2.0->ai2-olmo-core==0.1.0->ai2-olmo)\n", + " Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)\n", + "Collecting pydantic-core==2.27.2 (from pydantic<3.0,>=2.0->ai2-olmo-core==0.1.0->ai2-olmo)\n", + " Downloading pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from requests->ai2-olmo-core==0.1.0->ai2-olmo) (3.4.1)\n", + "Requirement already satisfied: idna<4,>=2.5 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from requests->ai2-olmo-core==0.1.0->ai2-olmo) (3.10)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from requests->ai2-olmo-core==0.1.0->ai2-olmo) (2025.1.31)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from jinja2->torch>=1.11->optimum) (3.0.2)\n", + "Collecting pyasn1<0.7.0,>=0.4.6 (from pyasn1-modules>=0.2.1->google-auth<3.0dev,>=2.26.1->google-cloud-storage->ai2-olmo)\n", + " Downloading pyasn1-0.6.1-py3-none-any.whl.metadata (8.4 kB)\n", + "Requirement already satisfied: six>=1.5 in /home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages (from python-dateutil<3.0.0,>=2.1->botocore<1.37.0,>=1.36.18->boto3->ai2-olmo) (1.17.0)\n", + "Downloading ai2_olmo-0.6.0-py3-none-any.whl (144.9 MB)\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m144.9/144.9 MB\u001b[0m \u001b[31m14.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", + "\u001b[?25hDownloading ai2_olmo_core-0.1.0-py3-none-any.whl (56 kB)\n", + "Downloading cached_path-1.6.7-py3-none-any.whl (35 kB)\n", + "Downloading boto3-1.36.18-py3-none-any.whl (139 kB)\n", + "Downloading google_cloud_storage-2.19.0-py2.py3-none-any.whl (131 kB)\n", + "Downloading huggingface_hub-0.27.1-py3-none-any.whl (450 kB)\n", + "Using cached numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.2 MB)\n", + "Downloading rich-13.9.4-py3-none-any.whl (242 kB)\n", + "Downloading importlib_resources-6.5.2-py3-none-any.whl (37 kB)\n", + "Downloading omegaconf-2.3.0-py3-none-any.whl (79 kB)\n", + "Downloading botocore-1.36.18-py3-none-any.whl (13.3 MB)\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m13.3/13.3 MB\u001b[0m \u001b[31m36.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading google_api_core-2.24.1-py3-none-any.whl (160 kB)\n", + "Downloading google_auth-2.38.0-py2.py3-none-any.whl (210 kB)\n", + "Downloading google_cloud_core-2.4.1-py2.py3-none-any.whl (29 kB)\n", + "Downloading google_crc32c-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (37 kB)\n", + "Downloading google_resumable_media-2.7.2-py2.py3-none-any.whl (81 kB)\n", + "Downloading jmespath-1.0.1-py3-none-any.whl (20 kB)\n", + "Using cached markdown_it_py-3.0.0-py3-none-any.whl (87 kB)\n", + "Downloading pydantic-2.10.6-py3-none-any.whl (431 kB)\n", + "Downloading pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB)\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m2.0/2.0 MB\u001b[0m \u001b[31m35.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading s3transfer-0.11.2-py3-none-any.whl (84 kB)\n", + "Downloading zipp-3.21.0-py3-none-any.whl (9.6 kB)\n", + "Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)\n", + "Downloading cachetools-5.5.1-py3-none-any.whl (9.5 kB)\n", + "Downloading googleapis_common_protos-1.67.0rc1-py2.py3-none-any.whl (165 kB)\n", + "Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)\n", + "Downloading proto_plus-1.26.0-py3-none-any.whl (50 kB)\n", + "Downloading pyasn1_modules-0.4.1-py3-none-any.whl (181 kB)\n", + "Using cached rsa-4.9-py3-none-any.whl (34 kB)\n", + "Downloading urllib3-1.26.20-py2.py3-none-any.whl (144 kB)\n", + "Downloading pyasn1-0.6.1-py3-none-any.whl (83 kB)\n", + "Installing collected packages: antlr4-python3-runtime, zipp, urllib3, pydantic-core, pyasn1, proto-plus, omegaconf, numpy, mdurl, jmespath, googleapis-common-protos, google-crc32c, cachetools, annotated-types, rsa, pydantic, pyasn1-modules, markdown-it-py, importlib_resources, google-resumable-media, botocore, s3transfer, rich, huggingface-hub, google-auth, google-api-core, boto3, google-cloud-core, google-cloud-storage, cached_path, ai2-olmo-core, ai2-olmo\n", + " Attempting uninstall: urllib3\n", + " Found existing installation: urllib3 2.3.0\n", + " Uninstalling urllib3-2.3.0:\n", + " Successfully uninstalled urllib3-2.3.0\n", + " Attempting uninstall: numpy\n", + " Found existing installation: numpy 2.0.2\n", + " Uninstalling numpy-2.0.2:\n", + " Successfully uninstalled numpy-2.0.2\n", + " Attempting uninstall: huggingface-hub\n", + " Found existing installation: huggingface-hub 0.28.1\n", + " Uninstalling huggingface-hub-0.28.1:\n", + " Successfully uninstalled huggingface-hub-0.28.1\n", + "Successfully installed ai2-olmo-0.6.0 ai2-olmo-core-0.1.0 annotated-types-0.7.0 antlr4-python3-runtime-4.9.3 boto3-1.36.18 botocore-1.36.18 cached_path-1.6.7 cachetools-5.5.1 google-api-core-2.24.1 google-auth-2.38.0 google-cloud-core-2.4.1 google-cloud-storage-2.19.0 google-crc32c-1.6.0 google-resumable-media-2.7.2 googleapis-common-protos-1.67.0rc1 huggingface-hub-0.27.1 importlib_resources-6.5.2 jmespath-1.0.1 markdown-it-py-3.0.0 mdurl-0.1.2 numpy-1.26.4 omegaconf-2.3.0 proto-plus-1.26.0 pyasn1-0.6.1 pyasn1-modules-0.4.1 pydantic-2.10.6 pydantic-core-2.27.2 rich-13.9.4 rsa-4.9 s3transfer-0.11.2 urllib3-1.26.20 zipp-3.21.0\n" + ] + } + ], + "source": [ + "!pip install -q --upgrade transformers[onnx]==4.41.0\n", + "!pip install optimum sentencepiece onnx onnxruntime ai2-olmo" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GX1TUzkhS-JK" + }, + "source": [ + "- HuggingFace has an extension called Optimum which offers specialized model inference, including ONNX. We can use this to import and export ONNX models with `from_pretrained` and `save_pretrained`.\n", + "- We'll use [allenai/OLMo-1B-hf](https://huggingface.co/allenai/OLMo-1B-hf) model from HuggingFace as an example\n", + "- In addition to `OLMO` we also need to save the tokenizer. This is the same for every model, these are assets needed for tokenization inside Spark NLP.\n", + "- If we want to optimize the model, a GPU will be needed. Make sure to select the correct runtime.\n", + "0" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "-ibF3eK_S-JK" + }, + "outputs": [], + "source": [ + "import transformers\n", + "MODEL_NAME = \"allenai/OLMo-1B-hf\"\n", + "\n", + "\n", + "# Path to store the exported models\n", + "EXPORT_PATH = f\"onnx_models/{MODEL_NAME}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "kDH5EpwnS-JK", + "outputId": "e32328ad-45b3-4d6c-d5d6-d2da88dbbd4a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages/huggingface_hub/file_download.py:795: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", + " warnings.warn(\n", + "config.json: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 632/632 [00:00<00:00, 38.6kB/s]\n", + "model.safetensors: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 4.71G/4.71G [03:24<00:00, 23.1MB/s]\n", + "generation_config.json: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 116/116 [00:00<00:00, 12.9kB/s]\n", + "tokenizer_config.json: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 5.37k/5.37k [00:00<00:00, 698kB/s]\n", + "tokenizer.json: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 2.12M/2.12M [00:00<00:00, 2.45MB/s]\n", + "special_tokens_map.json: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 65.0/65.0 [00:00<00:00, 25.5kB/s]\n", + "/home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages/huggingface_hub/file_download.py:795: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", + " warnings.warn(\n", + "/home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages/transformers/models/olmo/modeling_olmo.py:1039: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if sequence_length != 1:\n", + "Weight deduplication check in the ONNX export requires accelerate. Please install accelerate to run it.\n", + "\t\t-[x] values not close enough, max diff: 0.0007228851318359375 (atol: 0.0001)\n", + "The ONNX export succeeded with the warning: The maximum absolute difference between the output of the reference model and the ONNX exported model is not within the set tolerance 0.0001:\n", + "- logits: max diff = 0.0007228851318359375.\n", + " The exported model was saved at: onnx_models/allenai/OLMo-1B-hf\n" + ] + } + ], + "source": [ + "!optimum-cli export onnx --trust-remote-code --task text-generation --model {MODEL_NAME} {EXPORT_PATH} " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oDAyLDCcS-JL" + }, + "source": [ + "Let's have a look inside these two directories and see what we are dealing with:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "jp2ssmF2S-JL", + "outputId": "7c3379db-18cd-4990-de7e-51e9b4eada8c" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 5001720\n", + "-rw-rw-r-- 1 prabod prabod 646 Feb 12 03:51 config.json\n", + "-rw-rw-r-- 1 prabod prabod 111 Feb 12 03:51 generation_config.json\n", + "-rw-rw-r-- 1 prabod prabod 468660 Feb 12 03:52 model.onnx\n", + "-rw-rw-r-- 1 prabod prabod 5119148032 Feb 12 03:52 model.onnx_data\n", + "-rw-rw-r-- 1 prabod prabod 293 Feb 12 03:51 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 5372 Feb 12 03:51 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 2115417 Feb 12 03:51 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -l {EXPORT_PATH}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TJ-z0eSzS-JL" + }, + "source": [ + "- As you can see, we need to move the sentence piece models `spiece.model` from the tokenizer to assets folder which Spark NLP will look for" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/prabod/anaconda3/envs/olmo/lib/python3.9/site-packages/huggingface_hub/file_download.py:795: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "('onnx_models/allenai/OLMo-1B-hf/assets/tokenizer_config.json',\n", + " 'onnx_models/allenai/OLMo-1B-hf/assets/special_tokens_map.json',\n", + " 'onnx_models/allenai/OLMo-1B-hf/assets/tokenizer.json')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from transformers import AutoModelForCausalLM, AutoTokenizer, AutoConfig\n", + "from pathlib import Path\n", + "model_id = 'allenai/OLMo-1B-hf'\n", + "\n", + "tokenizer = AutoTokenizer.from_pretrained(model_id,trust_remote_code=True)\n", + "config = AutoConfig.from_pretrained(model_id,trust_remote_code=True)\n", + "\n", + "\n", + "ASSETS_PATH = f\"{EXPORT_PATH}/assets\"\n", + "\n", + "\n", + "\n", + "# make sure the directory exists\n", + "Path(ASSETS_PATH).mkdir(parents=True, exist_ok=True)\n", + "\n", + "config.save_pretrained(ASSETS_PATH)\n", + "tokenizer.save_vocabulary(ASSETS_PATH)\n", + "\n", + "tokenizer.save_pretrained(ASSETS_PATH)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "062OnFBIS-JL" + }, + "outputs": [], + "source": [ + "! mkdir -p {EXPORT_PATH}/assets\n", + "! mv -t {EXPORT_PATH}/assets {EXPORT_PATH}/merges.txt" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "xZMCq14PUdrG" + }, + "outputs": [], + "source": [ + "import json\n", + "with open(f\"{ASSETS_PATH}/vocab.json\", \"r\") as F:\n", + " vocab_json = json.load(F)\n", + " vocab = [\"\" for i in range(len(vocab_json))]\n", + " for word in vocab_json:\n", + " vocab[vocab_json[word]] = word\n", + " with open(f\"{ASSETS_PATH}/vocab.txt\", \"w\") as F2:\n", + " F2.writelines(map(lambda x: str(x) + \"\\n\", vocab))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "3fbDIHVFS-JL", + "outputId": "ebe0a435-3c5c-4c20-df51-534397802fbd" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 3716\n", + "-rw-rw-r-- 1 prabod prabod 673 Feb 12 03:59 config.json\n", + "-rw-rw-r-- 1 prabod prabod 456598 Feb 12 03:59 merges.txt\n", + "-rw-rw-r-- 1 prabod prabod 293 Feb 12 03:59 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 5372 Feb 12 03:59 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 2115417 Feb 12 03:59 tokenizer.json\n", + "-rw-rw-r-- 1 prabod prabod 799451 Feb 12 03:59 vocab.json\n", + "-rw-rw-r-- 1 prabod prabod 407614 Feb 12 04:00 vocab.txt\n" + ] + } + ], + "source": [ + "!ls -l {EXPORT_PATH}/assets" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2025-02-12 04:30:03,971 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:03,994 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.0/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:03,995 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:04,016 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.0/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:04,017 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:04,039 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.0/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:04,041 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/self_attn/rotary_emb/MatMul ...\n", + "2025-02-12 04:30:04,042 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:04,043 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/self_attn/MatMul ...\n", + "2025-02-12 04:30:04,045 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:04,046 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:04,047 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:04,048 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:04,073 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.0/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:04,074 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:04,186 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.0/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:04,192 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:04,279 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.0/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:04,283 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.0/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:04,370 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.0/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:04,373 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:04,402 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.1/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:04,403 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:04,422 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.1/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:04,423 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:04,442 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.1/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:04,444 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/self_attn/MatMul ...\n", + "2025-02-12 04:30:04,445 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:04,446 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:04,447 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:04,448 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:04,470 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.1/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:04,471 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:04,569 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.1/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:04,573 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:04,672 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.1/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:04,676 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.1/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:04,775 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.1/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:04,779 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:04,807 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.2/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:04,808 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:04,827 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.2/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:04,828 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:04,848 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.2/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:04,849 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/self_attn/MatMul ...\n", + "2025-02-12 04:30:04,850 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:04,851 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:04,852 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:04,855 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:04,874 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.2/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:04,875 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:04,964 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.2/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:04,968 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:05,057 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.2/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:05,060 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.2/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:05,151 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.2/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:05,155 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:05,183 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.3/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:05,184 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:05,203 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.3/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:05,204 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:05,223 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.3/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:05,224 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/self_attn/MatMul ...\n", + "2025-02-12 04:30:05,225 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:05,226 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:05,227 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:05,228 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:05,250 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.3/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:05,251 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:05,348 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.3/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:05,352 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:05,459 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.3/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:05,464 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.3/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:05,564 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.3/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:05,568 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:05,601 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.4/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:05,602 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:05,623 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.4/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:05,624 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:05,645 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.4/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:05,646 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/self_attn/MatMul ...\n", + "2025-02-12 04:30:05,647 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:05,649 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:05,650 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:05,651 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:05,671 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.4/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:05,672 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:05,768 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.4/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:05,772 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:05,859 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.4/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:05,863 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.4/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:05,952 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.4/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:05,956 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:05,989 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.5/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:05,990 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:06,010 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.5/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:06,011 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:06,032 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.5/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:06,033 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/self_attn/MatMul ...\n", + "2025-02-12 04:30:06,034 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:06,036 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:06,037 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:06,038 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:06,061 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.5/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:06,062 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:06,175 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.5/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:06,182 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:06,268 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.5/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:06,272 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.5/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:06,368 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.5/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:06,375 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:06,403 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.6/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:06,404 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:06,423 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.6/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:06,424 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:06,443 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.6/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:06,445 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/self_attn/MatMul ...\n", + "2025-02-12 04:30:06,446 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:06,447 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:06,448 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:06,449 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:06,469 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.6/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:06,470 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:06,555 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.6/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:06,559 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:06,652 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.6/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:06,655 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.6/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:06,743 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.6/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:06,747 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:06,775 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.7/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:06,776 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:06,795 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.7/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:06,796 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:06,815 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.7/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:06,816 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/self_attn/MatMul ...\n", + "2025-02-12 04:30:06,818 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:06,819 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:06,820 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:06,821 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:06,844 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.7/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:06,846 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:06,947 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.7/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:06,952 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:07,053 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.7/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:07,058 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.7/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:07,161 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.7/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:07,166 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:07,198 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.8/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:07,199 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:07,220 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.8/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:07,221 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:07,241 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.8/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:07,243 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/self_attn/MatMul ...\n", + "2025-02-12 04:30:07,244 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:07,245 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:07,246 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:07,247 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:07,268 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.8/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:07,269 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:07,356 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.8/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:07,360 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:07,445 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.8/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:07,449 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.8/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:07,540 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.8/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:07,544 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:07,571 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.9/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:07,572 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:07,591 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.9/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:07,592 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:07,613 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.9/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:07,615 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/self_attn/MatMul ...\n", + "2025-02-12 04:30:07,616 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:07,617 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:07,618 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:07,619 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:07,640 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.9/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:07,641 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:07,734 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.9/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:07,739 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:07,844 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.9/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:07,849 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.9/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:07,948 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.9/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:07,951 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:07,980 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.10/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:07,981 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:08,001 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.10/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:08,002 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:08,022 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.10/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:08,023 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/self_attn/MatMul ...\n", + "2025-02-12 04:30:08,025 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:08,026 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:08,027 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:08,028 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:08,047 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.10/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:08,048 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:08,135 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.10/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:08,141 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:08,226 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.10/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:08,230 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.10/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:08,315 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.10/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:08,319 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:08,348 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.11/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:08,349 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:08,368 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.11/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:08,369 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:08,388 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.11/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:08,389 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/self_attn/MatMul ...\n", + "2025-02-12 04:30:08,391 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:08,392 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:08,393 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:08,394 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:08,415 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.11/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:08,416 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:08,521 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.11/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:08,525 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:08,630 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.11/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:08,634 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.11/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:08,738 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.11/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:08,742 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:08,775 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.12/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:08,776 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:08,797 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.12/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:08,798 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:08,818 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.12/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:08,820 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/self_attn/MatMul ...\n", + "2025-02-12 04:30:08,821 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:08,822 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:08,823 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:08,824 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:08,846 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.12/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:08,847 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:08,929 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.12/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:08,933 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:09,025 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.12/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:09,029 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.12/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:09,118 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.12/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:09,122 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:09,151 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.13/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:09,152 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:09,171 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.13/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:09,172 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:09,191 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.13/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:09,193 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/self_attn/MatMul ...\n", + "2025-02-12 04:30:09,194 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:09,195 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:09,197 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:09,198 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:09,219 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.13/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:09,220 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:09,308 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.13/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:09,311 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:09,399 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.13/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:09,402 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.13/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:09,489 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.13/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:09,492 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:09,520 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.14/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:09,521 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:09,540 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.14/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:09,541 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:09,560 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.14/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:09,561 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/self_attn/MatMul ...\n", + "2025-02-12 04:30:09,563 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:09,564 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:09,565 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:09,566 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:09,587 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.14/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:09,588 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:09,713 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.14/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:09,717 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:09,842 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.14/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:09,847 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.14/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:09,973 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.14/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:09,976 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:10,004 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.15/self_attn/q_proj/MatMul ...\n", + "2025-02-12 04:30:10,005 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:10,024 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.15/self_attn/k_proj/MatMul ...\n", + "2025-02-12 04:30:10,025 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:10,044 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.15/self_attn/v_proj/MatMul ...\n", + "2025-02-12 04:30:10,046 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/self_attn/MatMul ...\n", + "2025-02-12 04:30:10,047 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:10,048 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/self_attn/MatMul_1 ...\n", + "2025-02-12 04:30:10,050 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - MatMul doesn't have const weight. Skip to quantize\n", + "2025-02-12 04:30:10,051 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:10,072 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.15/self_attn/o_proj/MatMul ...\n", + "2025-02-12 04:30:10,073 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:10,193 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.15/mlp/gate_proj/MatMul ...\n", + "2025-02-12 04:30:10,198 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:10,326 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.15/mlp/up_proj/MatMul ...\n", + "2025-02-12 04:30:10,331 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /model/layers.15/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:10,456 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /model/layers.15/mlp/down_proj/MatMul ...\n", + "2025-02-12 04:30:10,462 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - start to quantize /lm_head/MatMul ...\n", + "2025-02-12 04:30:11,248 onnxruntime.quantization.matmul_4bits_quantizer [INFO] - complete quantization of /lm_head/MatMul ...\n" + ] + } + ], + "source": [ + "import onnx\n", + "# from onnxruntime import quantization as ort_quantization\n", + "from onnxruntime.quantization.matmul_4bits_quantizer import MatMul4BitsQuantizer\n", + "\n", + "Path(f'onnx_models/{model_id}_int4').mkdir(parents=True, exist_ok=True)\n", + "\n", + "model = onnx.load_model(f\"onnx_models/{model_id}/model.onnx\", load_external_data=True)\n", + "quant = MatMul4BitsQuantizer(\n", + " model=model,\n", + " block_size=32,\n", + " is_symmetric=True,\n", + " nodes_to_exclude=[],\n", + ")\n", + "quant.process()\n", + "quant.model.save_model_to_file(f'onnx_models/{model_id}_int4/model.onnx', use_external_data_format=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "model_id = 'allenai/OLMo-1B-hf'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import onnx\n", + "model = onnx.load(f\"onnx_models/{model_id}_int4/model.onnx\")\n", + "EXPORT_PATH = f\"onnx_models/{model_id}_int4\"\n", + "onnx.save_model(model, f\"{EXPORT_PATH}/decoder_model.onnx\", save_as_external_data=True, all_tensors_to_one_file=True, location=\"_olmo_decoder_model.onnx_data\", size_threshold=1024, convert_attribute=False)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!rm -rf {EXPORT_PATH}/model.onnx {EXPORT_PATH}/model.onnx_data" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "#copy the assets\n", + "!cp -r onnx_models/{model_id}/assets onnx_models/{model_id}_int4/assets" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing /home/prabod/Projects/spark-nlp/python/dist/spark_nlp-5.5.3-py2.py3-none-any.whl\n", + "Collecting pyspark==3.2.3\n", + " Using cached pyspark-3.2.3.tar.gz (281.5 MB)\n", + " Preparing metadata (setup.py) ... \u001b[?25ldone\n", + "\u001b[?25hCollecting py4j==0.10.9.5 (from pyspark==3.2.3)\n", + " Using cached py4j-0.10.9.5-py2.py3-none-any.whl.metadata (1.5 kB)\n", + "spark-nlp is already installed with the same version as the provided wheel. Use --force-reinstall to force an installation of the wheel.\n", + "Using cached py4j-0.10.9.5-py2.py3-none-any.whl (199 kB)\n", + "Building wheels for collected packages: pyspark\n", + " Building wheel for pyspark (setup.py) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for pyspark: filename=pyspark-3.2.3-py2.py3-none-any.whl size=281990715 sha256=ec075358b0ed3cc8cae95e6699c93f9e9949e54045ca13ced0d05052e0143361\n", + " Stored in directory: /home/prabod/.cache/pip/wheels/cc/f4/8d/dfbbd536587311afde33711613a0c193f18e7d90b120801108\n", + "Successfully built pyspark\n", + "Installing collected packages: py4j, pyspark\n", + "Successfully installed py4j-0.10.9.5 pyspark-3.2.3\n" + ] + } + ], + "source": [ + "!pip install /home/prabod/Projects/spark-nlp/python/dist/spark_nlp-5.5.3-py2.py3-none-any.whl pyspark==3.2.3" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NZZqEbvvS-JM" + }, + "source": [ + "## Import and Save OLMO in Spark NLP\n", + "\n", + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "SLlypPRaS-JM", + "outputId": "54ab8af5-a1cb-4c29-f982-2f5aac5e6e35" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Installing PySpark 3.2.3 and Spark NLP 5.4.2\n", + "setup Colab for PySpark 3.2.3 and Spark NLP 5.4.2\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m281.5/281.5 MB\u001b[0m \u001b[31m5.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.6/55.6 kB\u001b[0m \u001b[31m3.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m579.5/579.5 kB\u001b[0m \u001b[31m29.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m199.7/199.7 kB\u001b[0m \u001b[31m14.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25h Building wheel for pyspark (setup.py) ... \u001b[?25l\u001b[?25hdone\n" + ] + } + ], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QEy-zFjnS-JM" + }, + "source": [ + "Let's start Spark with Spark NLP included via our simple `start()` function" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "0KOd7hwNS-JM", + "outputId": "8e408b69-db08-42f5-9d14-c163034f9c04" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting spark-nlp==5.5.0rc1\n", + " Downloading spark_nlp-5.5.0rc1-py2.py3-none-any.whl.metadata (55 kB)\n", + "\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/55.8 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m55.8/55.8 kB\u001b[0m \u001b[31m2.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading spark_nlp-5.5.0rc1-py2.py3-none-any.whl (629 kB)\n", + "\u001b[?25l \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m0.0/629.6 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[91mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m\u001b[91mโ•ธ\u001b[0m \u001b[32m624.6/629.6 kB\u001b[0m \u001b[31m25.1 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m629.6/629.6 kB\u001b[0m \u001b[31m17.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hInstalling collected packages: spark-nlp\n", + " Attempting uninstall: spark-nlp\n", + " Found existing installation: spark-nlp 5.4.2\n", + " Uninstalling spark-nlp-5.4.2:\n", + " Successfully uninstalled spark-nlp-5.4.2\n", + "Successfully installed spark-nlp-5.5.0rc1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/lib/python3.10/subprocess.py:1796: RuntimeWarning: os.fork() was called. os.fork() is incompatible with multithreaded code, and JAX is multithreaded, so this will likely lead to a deadlock.\n", + " self.pid = _posixsubprocess.fork_exec(\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Qgl_T39AS-JM" + }, + "source": [ + "- Let's use `loadSavedModel` functon in `OLMOTransformer` which allows us to load the ONNX model\n", + "- Most params will be set automatically. They can also be set later after loading the model in `OLMOTransformer` during runtime, so don't worry about setting them now\n", + "- `loadSavedModel` accepts two params, first is the path to the exported model. The second is the SparkSession that is `spark` variable we previously started via `sparknlp.start()`\n", + "- NOTE: `loadSavedModel` accepts local paths in addition to distributed file systems such as `HDFS`, `S3`, `DBFS`, etc. This feature was introduced in Spark NLP 4.2.2 release. Keep in mind the best and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively.st and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "Ij_8ZwLxS-JM" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Could not extract bos_token_id from config.json, assigning default value -1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING: An illegal reflective access operation has occurred\n", + "WARNING: Illegal reflective access by org.apache.spark.util.SizeEstimator$ (file:/home/prabod/spark/jars/spark-core_2.12-3.3.2.jar) to field java.util.regex.Pattern.pattern\n", + "WARNING: Please consider reporting this to the maintainers of org.apache.spark.util.SizeEstimator$\n", + "WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n", + "WARNING: All illegal access operations will be denied in a future release\n" + ] + } + ], + "source": [ + "from sparknlp.annotator import *\n", + "\n", + "olmo = OLMoTransformer.loadSavedModel(EXPORT_PATH, spark)\\\n", + " .setInputCols([\"documents\"])\\\n", + " .setMaxOutputLength(100)\\\n", + " .setDoSample(False)\\\n", + " .setOutputCol(\"generation\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "v_eeGHNZS-JM" + }, + "source": [ + "Let's save it on disk so it is easier to be moved around and also be used later via `.load` function" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "0rmW0bXLS-JM" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "olmo.write().overwrite().save(f\"/tmp/{MODEL_NAME}_spark_nlp_int4\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VnmGJlakS-JM" + }, + "source": [ + "Let's clean up stuff we don't need anymore" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "id": "kWkdSCjIS-JN" + }, + "outputs": [], + "source": [ + "!rm -rf {EXPORT_PATH}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "I9YtKl-aS-JN" + }, + "source": [ + "Awesome ๐Ÿ˜Ž !\n", + "\n", + "This is your ONNX OLMO model from HuggingFace ๐Ÿค— loaded and saved by Spark NLP ๐Ÿš€" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "9nbzEjwWS-JN", + "outputId": "4b20ba7c-41c5-440f-89c8-fd4e6a0ec541" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 1121168\n", + "-rw-r--r-- 1 prabod prabod 496159 Feb 12 11:54 decoder_model.onnx\n", + "drwxr-xr-x 5 prabod prabod 4096 Feb 12 11:54 fields\n", + "drwxr-xr-x 2 prabod prabod 4096 Feb 12 11:54 metadata\n", + "-rw-r--r-- 1 prabod prabod 1147568128 Feb 12 11:54 _olmo_decoder_model.onnx_data\n" + ] + } + ], + "source": [ + "! ls -l /tmp/{MODEL_NAME}_spark_nlp_int4" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lcNqKR7mS-JN" + }, + "source": [ + "Now let's see how we can use it on other machines, clusters, or any place you wish to use your new and shiny OLMO model ๐Ÿ˜Š" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "DZyaiumUS-JN", + "outputId": "d7db52cb-b85d-4d9a-fd94-24e5b0af7f4b" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using CPUs\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Stage 21:======================================================> (30 + 1) / 31]\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|text |document |generation |\n", + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|Transfer learning, where a model is first pre-trained on a data-rich task before being fine-tuned on a downstream task, has emerged as a powerful technique in natural language processing (NLP). The effectiveness of transfer learning has given rise to a diversity of approaches, methodology, and practice. In this paper, we explore the landscape of transfer learning techniques for NLP by introducing a unified framework that converts all text-based language problems into a text-to-text format. Our systematic study compares pre-training objectives, architectures, unlabeled data sets, transfer approaches, and other factors on dozens of language understanding tasks. By combining the insights from our exploration with scale and our new Colossal Clean Crawled Corpus, we achieve state-of-the-art results on many benchmarks covering summarization, question answering, text classification, and more. To facilitate future work on transfer learning for NLP, we release our data set, pre-trained models, and code.|[{document, 0, 1008, Transfer learning, where a model is first pre-trained on a data-rich task before being fine-tuned on a downstream task, has emerged as a powerful technique in natural language processing (NLP). The effectiveness of transfer learning has given rise to a diversity of approaches, methodology, and practice. In this paper, we explore the landscape of transfer learning techniques for NLP by introducing a unified framework that converts all text-based language problems into a text-to-text format. Our systematic study compares pre-training objectives, architectures, unlabeled data sets, transfer approaches, and other factors on dozens of language understanding tasks. By combining the insights from our exploration with scale and our new Colossal Clean Crawled Corpus, we achieve state-of-the-art results on many benchmarks covering summarization, question answering, text classification, and more. To facilitate future work on transfer learning for NLP, we release our data set, pre-trained models, and code., {sentence -> 0}, []}]|[{document, 0, 1195, Transfer learning , where a model is first pre - trained on a data - rich task before being fine - tuned on a downstream task , has emerged as a powerful technique in natural language processing ( NLP ). The effectiveness of transfer learning has given rise to a diversity of approaches , methodology , and practice . In this paper , we explore the landscape of transfer learning techniques for NLP by introducing a unified framework that converts all text - based language problems into a text - to - text format . Our systematic study compares pre - training objectives , architectures , unlabeled data sets , transfer approaches , and other factors on dozens of language understanding tasks . By combining the insights from our exploration with scale and our new Colossal Clean Crawled Corpus , we achieve state - of - the - art results on many benchmarks covering summarization , question answering , text classification , and more . To facilitate future work on transfer learning for NLP , we release our data set , pre - trained models , and code . We also release the Colossala testset and a full report on our results , which we provide for researchers . The paper is available at https ., {sentence -> 0}, []}]|\n", + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "import sparknlp\n", + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.ml import Pipeline\n", + "\n", + "test_data = spark.createDataFrame([\n", + " [\"Transfer learning, where a model is first pre-trained on a data-rich task before being fine-tuned on a \" +\n", + " \"downstream task, has emerged as a powerful technique in natural language processing (NLP). The effectiveness\" +\n", + " \" of transfer learning has given rise to a diversity of approaches, methodology, and practice. In this \" +\n", + " \"paper, we explore the landscape of transfer learning techniques for NLP by introducing a unified framework \" +\n", + " \"that converts all text-based language problems into a text-to-text format. Our systematic study compares \" +\n", + " \"pre-training objectives, architectures, unlabeled data sets, transfer approaches, and other factors on dozens \" +\n", + " \"of language understanding tasks. By combining the insights from our exploration with scale and our new \" +\n", + " \"Colossal Clean Crawled Corpus, we achieve state-of-the-art results on many benchmarks covering \" +\n", + " \"summarization, question answering, text classification, and more. To facilitate future work on transfer \" +\n", + " \"learning for NLP, we release our data set, pre-trained models, and code.\"]\n", + "]).toDF(\"text\")\n", + "\n", + "\n", + "document_assembler = DocumentAssembler() \\\n", + " .setInputCol(\"text\")\\\n", + " .setOutputCol(\"document\")\n", + "\n", + "olmo = OLMoTransformer.load(f\"file:///tmp/{MODEL_NAME}_spark_nlp_int4\")\\\n", + " .setInputCols([\"document\"])\\\n", + " .setMaxOutputLength(50)\\\n", + " .setDoSample(True)\\\n", + " .setTopK(50)\\\n", + " .setTemperature(0)\\\n", + " .setBatchSize(5)\\\n", + " .setNoRepeatNgramSize(3)\\\n", + " .setOutputCol(\"generation\")\n", + "\n", + "pipeline = Pipeline().setStages([document_assembler, olmo])\n", + "\n", + "result = pipeline.fit(test_data).transform(test_data)\n", + "result.show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "uTnIQ3HKS-JN" + }, + "source": [ + "That's it! You can now go wild and use hundreds of OLMO models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "olmo", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.21" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala b/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala index fd9e330abf8690..4ac08acc05d7ae 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/OLMo.scala @@ -26,6 +26,7 @@ import com.johnsnowlabs.nlp.Annotation import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT import com.johnsnowlabs.nlp.annotators.common.SentenceSplit import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{BpeTokenizer, OLMoTokenizer} +import org.intel.openvino.InferRequest import org.tensorflow.{Session, Tensor} import scala.collection.JavaConverters._ @@ -239,7 +240,8 @@ private[johnsnowlabs] class OLMo( decoderEncoderStateTensors: Either[Tensor, OnnxTensor], encoderAttentionMaskTensors: Either[Tensor, OnnxTensor], maxLength: Int, - session: Either[Session, (OrtEnvironment, OrtSession)]): Array[Array[Float]] = { + session: Either[Session, (OrtEnvironment, OrtSession)], + ovInferRequest: Option[InferRequest]): Array[Array[Float]] = { session.fold( tfSession => { diff --git a/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxSerializeModel.scala b/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxSerializeModel.scala index e985f2b0bcac99..27250cd5fceff6 100644 --- a/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxSerializeModel.scala +++ b/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxSerializeModel.scala @@ -98,7 +98,11 @@ trait ReadOnnxModel { val fsPath = new Path(path, localModelFile).toString val onnxDataFile: Option[String] = if (modelName.isDefined && dataFilePostfix.isDefined) { - Some(fsPath.replaceAll(modelName.get, s"${suffix}_${modelName.get}${dataFilePostfix.get}")) + var modelNameWithoutSuffix = modelName.get.replace(".onnx", "") + Some( + fsPath.replaceAll( + modelName.get, + s"${suffix}_${modelNameWithoutSuffix}${dataFilePostfix.get}")) } else None if (onnxDataFile.isDefined) { @@ -117,7 +121,8 @@ trait ReadOnnxModel { zipped = zipped, useBundle = useBundle, modelName = if (modelName.isDefined) modelName.get else onnxFile, - onnxFileSuffix = Some(suffix)) + onnxFileSuffix = Some(suffix), + dataFileSuffix = dataFilePostfix) onnxWrapper diff --git a/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxWrapper.scala index 6e748faa72ee63..1b5131446a944e 100644 --- a/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxWrapper.scala +++ b/src/main/scala/com/johnsnowlabs/ml/onnx/OnnxWrapper.scala @@ -134,7 +134,9 @@ object OnnxWrapper { val onnxDataFileExist: Boolean = { if (onnxFileSuffix.isDefined && dataFileSuffix.isDefined) { - val onnxDataFilePath = s"${onnxFileSuffix.get}_$modelName${dataFileSuffix.get}" + var modelNameWithoutSuffix = modelName.replace(".onnx", "") + val onnxDataFilePath = + s"${onnxFileSuffix.get}_$modelNameWithoutSuffix${dataFileSuffix.get}" onnxDataFile = Paths.get(parentDir, onnxDataFilePath).toFile onnxDataFile.exists() } else false diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala index 17ee68463d18b0..e12c8ec0376d2f 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala @@ -320,16 +320,23 @@ trait ReadablePretrainedOLMoTransformerModel trait ReadOLMoTransformerDLModel extends ReadOnnxModel { this: ParamsAndFeaturesReadable[OLMoTransformer] => - override val onnxFile: String = "olmo_onnx" + override val onnxFile: String = "decoder_model.onnx" val suffix: String = "_olmo" def readModel(instance: OLMoTransformer, path: String, spark: SparkSession): Unit = { instance.getEngine match { case ONNX.name => - val wrappers = - readOnnxModels(path, spark, Seq("decoder_model.onnx"), suffix) + val wrapper = + readOnnxModel( + path, + spark, + suffix, + zipped = true, + useBundle = false, + modelName = Some("decoder_model.onnx"), + dataFilePostfix = Some(".onnx_data")) val onnxWrappers = - DecoderWrappers(decoder = wrappers("decoder_model.onnx")) + DecoderWrappers(decoder = wrapper) instance.setModelIfNotSet(spark, onnxWrappers) case _ => throw new Exception(notSupportedEngineError) @@ -402,11 +409,13 @@ trait ReadOLMoTransformerDLModel extends ReadOnnxModel { case ONNX.name => val onnxWrapperDecoder = OnnxWrapper.read( + spark, localModelPath, zipped = false, useBundle = true, modelName = "decoder_model", - dataFileSuffix = ".onnx_data") + dataFileSuffix = Some(".onnx_data"), + onnxFileSuffix = Some(suffix)) val onnxWrappers = DecoderWrappers(onnxWrapperDecoder) diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala index a1a1ee5365b47d..55cfaffa6f2474 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala @@ -24,7 +24,7 @@ import org.scalatest.flatspec.AnyFlatSpec class OLMoTestSpec extends AnyFlatSpec { - "olmo" should "should handle temperature=0 correctly and not crash when predicting more than 1 element with doSample=True" taggedAs FastTest in { + "olmo" should "should handle temperature=0 correctly and not crash when predicting more than 1 element with doSample=True" taggedAs SlowTest in { // Even tough the Paper states temperature in interval [0,1), using temperature=0 will result in division by 0 error. // Also DoSample=True may result in infinities being generated and distFiltered.length==0 which results in exception if we don't return 0 instead internally. val testData = ResourceHelper.spark @@ -42,8 +42,31 @@ class OLMoTestSpec extends AnyFlatSpec { .setMaxOutputLength(100) .setOutputCol("generation") .setBeamSize(1) - new Pipeline() + + val pipeline = new Pipeline() .setStages(Array(documentAssembler, bart)) + + val pipelineModel = pipeline.fit(testData) + + pipelineModel + .transform(testData) + .show(truncate = false) + + pipelineModel + .transform(testData) + .show(truncate = false) + + pipelineModel.stages.last + .asInstanceOf[OLMoTransformer] + .write + .overwrite() + .save("/tmp/olmo-1b-4bit-model") + + val loadedLLAMA3 = OLMoTransformer.load("/tmp/olmo-1b-4bit-model") + + val loadedPipeline = new Pipeline().setStages(Array(documentAssembler, loadedLLAMA3)) + + loadedPipeline .fit(testData) .transform(testData) .show(truncate = false) From 05cbe8b3503e91dc5d720d0646a3138b681c5a7d Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 12 Feb 2025 12:35:45 +0000 Subject: [PATCH 031/108] update default name and documentation Signed-off-by: Prabod Rathnayaka --- .../en/transformer_entries/OLMoTransformer.md | 135 ++++++++++++++++++ .../annotators/seq2seq/OLMoTransformer.scala | 6 +- .../nlp/pretrained/ResourceDownloader.scala | 3 +- 3 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 docs/en/transformer_entries/OLMoTransformer.md diff --git a/docs/en/transformer_entries/OLMoTransformer.md b/docs/en/transformer_entries/OLMoTransformer.md new file mode 100644 index 00000000000000..77f7235481d9c4 --- /dev/null +++ b/docs/en/transformer_entries/OLMoTransformer.md @@ -0,0 +1,135 @@ +{%- capture title -%} +OLMoTransformer +{%- endcapture -%} + +{%- capture description -%} +OLMo, a series of Open Language Models, is designed to enable the science of language models. These models are trained on the Dolma dataset, offering open-source capabilities for language model research and application. The OLMo models support various NLP tasks including text generation, summarization, and more. + +Pretrained models can be loaded using the `pretrained` method from the companion object: + + +```scala +val olmo = OLMoTransformer.pretrained() + .setInputCols("document") + .setOutputCol("generation") +``` + +The default model is `"olmo_1b_int4"`, if no name is provided. + +For available pretrained models please see the +[Models Hub](https://sparknlp.org/models?q=OLMo). + +For extended examples of usage, see +[OLMoTestSpec](https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTestSpec.scala). + +**Sources** : +[OLMo Project Page](https://allenai.org/olmo) +[OLMo GitHub Repository](https://github.com/allenai/OLMo) +[OLMo: Accelerating the Science of Language Models (Paper)](https://arxiv.org/pdf/2402.00838.pdf) + +**Paper abstract** + +*Language models (LMs) have become ubiquitous in both NLP research and commercial products. +As their commercial importance has surged, the most powerful models have become proprietary, +limiting scientific study. OLMo addresses this gap by offering an open-source framework, +including training data, models, and code. This initiative aims to empower the research community, +fostering transparency and innovation in language model development.* +{%- endcapture -%} + +{%- capture input_anno -%} +DOCUMENT +{%- endcapture -%} + +{%- capture output_anno -%} +DOCUMENT +{%- endcapture -%} + +{%- capture python_example -%} +import sparknlp +from sparknlp.base import * +from sparknlp.annotator import * +from pyspark.ml import Pipeline + +# Document Assembler +document_assembler = DocumentAssembler() \ +.setInputCol("text") \ +.setOutputCol("document") + +# OLMo Transformer +olmo = OLMoTransformer.pretrained("olmo_1b_int4") \ +.setInputCols(["document"]) \ +.setMinOutputLength(10) \ +.setMaxOutputLength(50) \ +.setDoSample(False) \ +.setTopK(50) \ +.setNoRepeatNgramSize(3) \ +.setOutputCol("generation") + +# Pipeline +pipeline = Pipeline(stages=[document_assembler, olmo]) + +# Sample Data +data = spark.createDataFrame([["My name is Leonardo."]]).toDF("text") +result = pipeline.fit(data).transform(data) + +# Display Results +result.select("generation.result").show(truncate=False) + +{%- endcapture -%} + +{%- capture scala_example -%} +import spark.implicits._ +import com.johnsnowlabs.nlp.base.DocumentAssembler +import com.johnsnowlabs.nlp.annotators.seq2seq.OLMoTransformer +import org.apache.spark.ml.Pipeline + +// Document Assembler +val documentAssembler = new DocumentAssembler() +.setInputCol("text") +.setOutputCol("document") + +// OLMo Transformer +val olmo = OLMoTransformer.pretrained("olmo_1b_int4") +.setInputCols(Array("document")) +.setMinOutputLength(10) +.setMaxOutputLength(50) +.setDoSample(false) +.setTopK(50) +.setNoRepeatNgramSize(3) +.setOutputCol("generation") + +// Pipeline +val pipeline = new Pipeline().setStages(Array(documentAssembler, olmo)) + +// Sample Data +val data = Seq("My name is Leonardo.").toDF("text") +val result = pipeline.fit(data).transform(data) + +// Display Results +result.select("generation.result").show(truncate = false) + +{%- endcapture -%} + +{%- capture api_link -%} +[OLMoTransformer](/api/com/johnsnowlabs/nlp/seq2seq/OLMoTransformer) +{%- endcapture -%} + +{%- capture python_api_link -%} +[OLMoTransformer](/api/python/reference/autosummary/sparknlp/annotator/seq2seq/olmo_transformer/index.html#sparknlp.annotator.seq2seq.olmo_transformer.OLMoTransformer) +{%- endcapture -%} + +{%- capture source_link -%} +[OLMoTransformer](https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/seq2seq/OLMoTransformer.scala) +{%- endcapture -%} + +{% include templates/anno_template.md +title=title +description=description +input_anno=input_anno +output_anno=output_anno +python_example=python_example +scala_example=scala_example +api_link=api_link +python_api_link=python_api_link +source_link=source_link +%} \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala index e12c8ec0376d2f..a5afd467478eaf 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/OLMoTransformer.scala @@ -55,7 +55,7 @@ import org.json4s.jackson.JsonMethods._ * .setInputCols("document") * .setOutputCol("generation") * }}} - * The default model is `"OLMo-1b"`, if no name is provided. For available pretrained models + * The default model is `"olmo_1b_int4"`, if no name is provided. For available pretrained models * please see the [[https://sparknlp.org/models?q=OLMo Models Hub]]. * * For extended examples of usage, see @@ -97,7 +97,7 @@ import org.json4s.jackson.JsonMethods._ * .setInputCol("text") * .setOutputCol("documents") * - * val OLMo = OLMoTransformer.pretrained("OLMo-7b") + * val OLMo = OLMoTransformer.pretrained("olmo_1b_int4") * .setInputCols(Array("documents")) * .setMinOutputLength(10) * .setMaxOutputLength(50) @@ -303,7 +303,7 @@ class OLMoTransformer(override val uid: String) trait ReadablePretrainedOLMoTransformerModel extends ParamsAndFeaturesReadable[OLMoTransformer] with HasPretrained[OLMoTransformer] { - override val defaultModelName: Some[String] = Some("OLMo-7b") + override val defaultModelName: Some[String] = Some("olmo_1b_int4") /** Java compliant-overrides */ override def pretrained(): OLMoTransformer = super.pretrained() diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala index 0e457d4d6e20df..b2164805efd415 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala @@ -697,7 +697,8 @@ object PythonResourceDownloader { "NLLBTransformer" -> NLLBTransformer, "Phi3Transformer" -> Phi3Transformer, "QwenTransformer" -> QwenTransformer, - "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings) + "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings, + "OLMoTransformer" -> OLMoTransformer) // List pairs of types such as the one with key type can load a pretrained model from the value type val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering") From 408958a9671591cc644d765c520be898dd346f94 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 12 Feb 2025 23:06:23 +0000 Subject: [PATCH 032/108] update default name Signed-off-by: Prabod Rathnayaka --- python/sparknlp/annotator/seq2seq/olmo_transformer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/sparknlp/annotator/seq2seq/olmo_transformer.py b/python/sparknlp/annotator/seq2seq/olmo_transformer.py index 3e0eb2269c3a83..eb1b63d71cdcf1 100644 --- a/python/sparknlp/annotator/seq2seq/olmo_transformer.py +++ b/python/sparknlp/annotator/seq2seq/olmo_transformer.py @@ -31,7 +31,7 @@ class OLMoTransformer(AnnotatorModel, HasBatchedAnnotate, HasEngine): ... .setOutputCol("generation") - The default model is ``"llam2-7b"``, if no name is provided. For available + The default model is ``"olmo_1b_int4"``, if no name is provided. For available pretrained models please see the `Models Hub `__. @@ -304,7 +304,7 @@ def loadSavedModel(folder, spark_session): return OLMoTransformer(java_model=jModel) @staticmethod - def pretrained(name="olmo-1b", lang="en", remote_loc=None): + def pretrained(name="olmo_1b_int4", lang="en", remote_loc=None): """Downloads and loads a pretrained model. Parameters From a369ce9918f6585eceecaf5d478e2ca0c4fd8221 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 19 Sep 2024 04:34:20 +0000 Subject: [PATCH 033/108] Phi3V preprocessing utils --- .../cv/util/transform/Phi3vUtils.scala | 342 ++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala new file mode 100644 index 00000000000000..fe50bdbc7a962b --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala @@ -0,0 +1,342 @@ +package com.johnsnowlabs.nlp.annotators.cv.util.transform +import java.awt.image.BufferedImage +import java.awt.{Color, Graphics2D} +import scala.collection.mutable.ListBuffer +import scala.collection.mutable.ArrayBuffer + +import ImageResizeUtils.resizeBufferedImage + +class Phi3vUtils { + // padding image + + def padding_336(image: BufferedImage): BufferedImage = { + // Get the current width and height of the image + val width = image.getWidth + val height = image.getHeight + + // Calculate the target height (multiple of 336) + val targetHeight = Math.ceil(height.toDouble / 336).toInt * 336 + + // Calculate the padding for top and bottom + val topPadding = (targetHeight - height) / 2 + val bottomPadding = targetHeight - height - topPadding + + // No padding for left and right + val leftPadding = 0 + val rightPadding = 0 + + // Create a new BufferedImage with the padded dimensions + val paddedImage = new BufferedImage(width, targetHeight, BufferedImage.TYPE_INT_RGB) + + // Create Graphics2D object to draw the padded image + val g2d: Graphics2D = paddedImage.createGraphics() + + // Set white background for the padding (fill) + g2d.setColor(Color.WHITE) + g2d.fillRect(0, 0, width, targetHeight) + + // Draw the original image onto the center of the new padded image + g2d.drawImage(image, leftPadding, topPadding, null) + + // Dispose of the Graphics2D context + g2d.dispose() + + // Return the new padded image + paddedImage + } + + def transposeImage(img: BufferedImage): BufferedImage = { + val transposedImage = new BufferedImage(img.getHeight, img.getWidth, img.getType) + val g2d = transposedImage.createGraphics() + + g2d.rotate(Math.PI / 2) + g2d.translate(0, -img.getHeight) + g2d.drawImage(img, 0, 0, null) + g2d.dispose() + + transposedImage + } + + def calc_padded_size(width: Int, height: Int, padding_unit: Int = 336): (Int, Int) = { + val target_height = Math.ceil(height / padding_unit).intValue * padding_unit + val top_padding = Math.ceil((target_height - height) / 2).intValue + val bottom_padding = target_height - height - top_padding + val left_padding = 0 + val right_padding = 0 + val padded_width = width + left_padding + right_padding + val padded_height = height + top_padding + bottom_padding + (padded_width, padded_height) + } + + def HD_transform(img: BufferedImage, hdNum: Int = 16): BufferedImage = { + var width = img.getWidth + var height = img.getHeight + var transposed = false + + // Transpose the image if width is smaller than height + var transformedImg = img + if (width < height) { + transformedImg = transposeImage(transformedImg) + transposed = true + width = transformedImg.getWidth + height = transformedImg.getHeight + } + + val ratio = width.toDouble / height.toDouble + var scale = 1 + + // Calculate the scaling factor + while (scale * math.ceil(scale / ratio) <= hdNum) { + scale += 1 + } + scale -= 1 + + // New dimensions + val newWidth = (scale * 336).toInt + val newHeight = (newWidth / ratio).toInt + + // Resize the image + transformedImg = resizeBufferedImage(newWidth, newHeight, 2)(transformedImg) + + // Apply padding to make the image 336x336 + transformedImg = padding_336(transformedImg) + + // Transpose back if needed + if (transposed) { + transformedImg = transposeImage(transformedImg) + } + + transformedImg + } + + // Function to extract a subimage and reset position information + def getNewSubimage( + image: BufferedImage, + x: Int, + y: Int, + width: Int, + height: Int): BufferedImage = { + // Create a new BufferedImage to store the subimage + val subImage = new BufferedImage(width, height, image.getType) + + // Create a Graphics2D object to draw the subimage + val g2d: Graphics2D = subImage.createGraphics() + + // Draw the original image's subimage into the new BufferedImage + g2d.drawImage(image, 0, 0, width, height, x, y, x + width, y + height, null) + + // Dispose the graphics context to free up resources + g2d.dispose() + + // Return the new subimage with reset position information + subImage + } + + // Function to calculate the shapes (height and width of the image) + def calculateShapes(images: List[BufferedImage]): List[(Int, Int)] = { + images.map(img => (img.getHeight, img.getWidth)) + } + + // Function to calculate the number of image tokens + def calculateImageTokens(shapes: List[(Int, Int)]): List[Int] = { + shapes.map { case (h, w) => + ((h / 336) * (w / 336) + 1) * 144 + 1 + ((h / 336 + 1) * 12) + } + } + + // Function to reshape the images (assuming each image is already HD transformed) + def reshapeImages( + images: List[BufferedImage], + shapes: List[(Int, Int)]): List[List[BufferedImage]] = { + images.zip(shapes).map { case (img, (h, w)) => + val numH = h / 336 + val numW = w / 336 + val reshapedImages = new ListBuffer[BufferedImage] + + // Splitting the image into 336x336 crops + for (i <- 0 until numH; j <- 0 until numW) { + val crop = getNewSubimage(img, j * 336, i * 336, 336, 336) + reshapedImages += crop + } + reshapedImages.toList + } + } + + // Function to concatenate global and local images (manually) + def concatenateImages( + globalImage: BufferedImage, + localImages: List[BufferedImage]): BufferedImage = { + println(localImages.size) + val totalWidth = 336 * localImages.size + 336 + val totalHeight = 336 + val concatenatedImage = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_INT_RGB) + val g2d: Graphics2D = concatenatedImage.createGraphics() + + // Draw global image first + g2d.drawImage(globalImage, 0, 0, null) + + // Draw each local image next to the global image + localImages.zipWithIndex.foreach { case (localImage, index) => + g2d.drawImage(localImage, (index + 1) * 336, 0, null) + } + + g2d.dispose() + concatenatedImage + } + + // Function to pad the images to a specified number of crops (maxNumCrops) + def padToMaxNumCrops(image: BufferedImage, maxNumCrops: Int): BufferedImage = { + val width = image.getWidth + val height = image.getHeight + + // If the number of crops is less than maxNumCrops, pad with white + val targetWidth = 336 * maxNumCrops + val paddedImage = new BufferedImage(targetWidth, height, BufferedImage.TYPE_INT_RGB) + val g2d: Graphics2D = paddedImage.createGraphics() + + // Fill with white background + g2d.setColor(Color.WHITE) + g2d.fillRect(0, 0, targetWidth, height) + + // Draw the original image onto the white background + g2d.drawImage(image, 0, 0, null) + g2d.dispose() + + paddedImage + } + + // Main function that processes the HD transformed images + def processHdImages( + hdImages: List[BufferedImage], + numCrops: Int): (List[BufferedImage], List[(Int, Int)], List[Int]) = { + // Step 1: Create global images (resize to 336x336) + // val resizeGlobal = + val globalImages = hdImages.map(resizeBufferedImage(336, 336, 3)) + + // Step 2: Calculate shapes [(h, w)] where h, w are multiples of 336 + val shapes = calculateShapes(hdImages) + + // Step 3: Calculate number of image tokens + val numImgTokens = calculateImageTokens(shapes) + + // Step 4: Reshape the HD images into 336x336 crops + val reshapedHdImages = reshapeImages(hdImages, shapes) + + // Step 5: Concatenate global and local images + val concatenatedImages = + globalImages.zip(reshapedHdImages).map { case (globalImage, localImages) => + concatenateImages(globalImage, localImages) + } + + // Step 6: Pad to max_num_crops if necessary + val paddedImages = concatenatedImages.map(padToMaxNumCrops(_, numCrops + 1)) + // val paddedImages = concatenatedImages + // Return the transformed images, their sizes, and the number of image tokens + (paddedImages, shapes, numImgTokens) + } + + // Function to normalize pixel values of an image crop + def normalizeImageCrop( + imgCrop: Array[Array[Array[Int]]], + mean: Array[Double], + std: Array[Double]): Array[Array[Array[Float]]] = { + val channels = imgCrop.length + val height = imgCrop(0).length + val width = imgCrop(0)(0).length + + // Create a 3D array for normalized values + val normalizedCrop = Array.ofDim[Float](channels, height, width) + + for (c <- 0 until channels) { + for (y <- 0 until height) { + for (x <- 0 until width) { + // Normalize the pixel value: (value - mean) / std + normalizedCrop(c)(y)(x) = (imgCrop(c)(y)(x) / 255.0 - mean(c)).toFloat / std(c).toFloat + } + } + } + + normalizedCrop + } + + // Helper function to convert a BufferedImage crop to a 3D array (3, 336, 336) for RGB channels + def imageCropToArray(imgCrop: BufferedImage): Array[Array[Array[Int]]] = { + val height = imgCrop.getHeight + val width = imgCrop.getWidth + + // Create a 3D array for RGB channels + val channels = 3 + val cropArray = Array.ofDim[Int](channels, height, width) + + for (y <- 0 until height; x <- 0 until width) { + val color = new java.awt.Color(imgCrop.getRGB(x, y)) + cropArray(0)(y)(x) = color.getRed // Red channel + cropArray(1)(y)(x) = color.getGreen // Green channel + cropArray(2)(y)(x) = color.getBlue // Blue channel + } + + cropArray + } + + // Function to split an image into 336x336 crops, convert to a 3D array, and normalize if required + def splitImageToCrops( + image: BufferedImage, + cropSize: Int = 336, + normalize: Boolean = false, + mean: Array[Double] = Array(0.48145466, 0.4578275, 0.40821073), + std: Array[Double] = Array(0.26862954, 0.26130258, 0.27577711)) + : Array[Array[Array[Array[Float]]]] = { + val height = image.getHeight + val width = image.getWidth + + // Number of crops along height and width + val numHCrops = height / cropSize + val numWCrops = width / cropSize + + // Store the crops in a 4D array (numCrops, 3, 336, 336) + val cropsBuffer = ArrayBuffer[Array[Array[Array[Float]]]]() + + for (i <- 0 until numHCrops) { + for (j <- 0 until numWCrops) { + // Extract a crop of 336x336 + val imgCrop = image.getSubimage(j * cropSize, i * cropSize, cropSize, cropSize) + // Convert the crop to a 3D array (3, 336, 336) + val cropArray = imageCropToArray(imgCrop) + + // Normalize the crop if the option is enabled + val normalizedCrop = if (normalize) { + normalizeImageCrop(cropArray, mean, std) + } else { + // Convert Int array to Double array if normalization is off + cropArray.map(_.map(_.map(_.toFloat / 255.0.toFloat))) + } + + cropsBuffer.append(normalizedCrop) + } + } + + // Convert ArrayBuffer to an array + cropsBuffer.toArray + } + + // Function to convert processedImages (BufferedImages) into a 5D array (b, h//336 * w//336, 3, 336, 336) + def processedImagesTo5DArray( + processedImages: List[BufferedImage], + normalize: Boolean = false, + mean: Array[Double] = Array(0.48145466, 0.4578275, 0.40821073), + std: Array[Double] = Array(0.26862954, 0.26130258, 0.27577711)) + : Array[Array[Array[Array[Array[Float]]]]] = { + // Store the 5D array (b, h//336 * w//336, 3, 336, 336) + val batchBuffer = ArrayBuffer[Array[Array[Array[Array[Float]]]]]() + + // Process each image in the batch + processedImages.foreach { img => + // Split the image into crops, convert each crop into a 3D array, and normalize if required + val imageCropsArray = splitImageToCrops(img, normalize = normalize, mean = mean, std = std) + batchBuffer.append(imageCropsArray) + } + + // Convert ArrayBuffer to array (b, numCrops, 3, 336, 336) + batchBuffer.toArray + } +} From 6896a020bec031af6f751f079b759b4080985263 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 23 Oct 2024 11:25:22 +0000 Subject: [PATCH 034/108] added phi3v --- .../scala/com/johnsnowlabs/ml/ai/Phi3V.scala | 413 ++++++++++++++++++ .../ml/openvino/OpenvinoWrapper.scala | 5 + .../cv/util/transform/Phi3vUtils.scala | 55 ++- 3 files changed, 456 insertions(+), 17 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala b/src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala new file mode 100644 index 00000000000000..638ee840931aef --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala @@ -0,0 +1,413 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.ml.ai + +import breeze.optimize.BatchSize +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.Phi3VWrappers +import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper +import com.johnsnowlabs.ml.tensorflow.sign.{ModelSignatureConstants, ModelSignatureManager} +import com.johnsnowlabs.ml.tensorflow.{TensorResources, TensorflowWrapper} +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils +import com.johnsnowlabs.nlp.annotators.cv.util.transform.ImageResizeUtils +import com.johnsnowlabs.nlp.annotators.cv.util.transform.Phi3vUtils +import org.intel.openvino.InferRequest + +import scala.collection.JavaConverters._ + +private[johnsnowlabs] class Phi3V( + val openvinoWrapper: Option[Phi3VWrappers], + val onnxWrappers: Option[DecoderWrappers], + preprocessor: Preprocessor, + val spp: SentencePieceWrapper, + generationConfig: GenerationConfig) + extends Serializable { + + private def sessionWarmup(): Unit = { + val image = + ImageIOUtils.loadImage(getClass.getResourceAsStream("/image/ox.JPEG")) + val bytes = ImageIOUtils.bufferedImageToByte(image.get) + val images = + Array(AnnotationImage("image", "ox.JPEG", 265, 360, 3, 16, bytes, Map("image" -> "0"))) +// val encoded = encode(images, preprocessor) +// tag(encoded) + } + + val detectedEngine: String = + if (onnxWrappers.isDefined) ONNX.name + else if (openvinoWrapper.isDefined) Openvino.name + else Openvino.name + + private val GenerationConfig( + bosTokenId: Int, + paddingTokenId: Int, + eosTokenId: Int, + vocabSize: Int, + beginSuppressTokens, + suppressTokenIds, + forcedDecoderIds) = + generationConfig + + private val pieceSize = spp.getSppModel.getPieceSize + + /** Decode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of decoded sentences + */ + def decode(sentences: Array[Array[Int]]): Seq[String] = { + sentences.map { s => + val filteredPieceIds = s.filter(x => x <= pieceSize) + spp.getSppModel.decodeIds(filteredPieceIds.map(_.toInt): _*) + } + } + def encode( + imageAnnotations: Seq[AnnotationImage], + sentences: Seq[Annotation], + numOfCrops: Int = 17): ( + Seq[Array[Int]], + (Array[Array[Array[Array[Array[Float]]]]], Array[Array[Int]], List[Int])) = { + val preprocessedImages = preprocessImage(imageAnnotations, numOfCrops) + val encodedText = encodeText(sentences).toArray + + (encodedText, preprocessedImages) + } + + def tag( + batch: Seq[Array[Int]], + images: (Array[Array[Array[Array[Array[Float]]]]], Array[Array[Int]]), + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long], + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int, + stopTokenIds: Array[Int], + numOfCrops: Int = 17): Array[Array[Int]] = { + + val (pixelValues, imageSizes) = images + val ignoreTokenIdsInt = ignoreTokenIds + val expandedDecoderInputsVals = batch + val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray + val maxSentenceLength = sequencesLength.max // - curLen +// val pixelValues = images._1 +// val imageSizes = images._2 + val numReturn_sequences = 1 + // from config + + var effectiveBatch_size = 1 + var effectiveBatch_mult = 1 + + if (doSample) { + effectiveBatch_size = expandedDecoderInputsVals.length * numReturn_sequences + effectiveBatch_mult = numReturn_sequences + } else { + effectiveBatch_size = expandedDecoderInputsVals.length + effectiveBatch_mult = 1 + } + + val inferRequestWTE = openvinoWrapper.get.wte.getCompiledModel().create_infer_request() + val inferRequestReshape = + openvinoWrapper.get.reshape.getCompiledModel().create_infer_request() + val inferRequestLanguageModel = + openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request() + + val generatedIds = generateGreedy( + batch.toArray, + batch.toArray, + pixelValues, + imageSizes, + maxOutputLength, + numOfCrops, + inferRequestWTE, + inferRequestReshape, + inferRequestLanguageModel) + generatedIds + } + + def generateGreedy( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Array[Float]]]]], + imageSizes: Array[Array[Int]], + maxOutputLength: Int, + numOfCrops: Int, + inferRequestWTE: InferRequest, + inferRequestReshape: InferRequest, + inferRequestLanguageModel: InferRequest): Array[Array[Int]] = { + + var generatedIds: Array[Array[Int]] = Array() + var decoderInputIdsCopied = decoderInputIds + while (!greedyGenerationFinished( + generatedIds.map(_.map(_.toInt)), + eosTokenId, + maxOutputLength)) { + val decoderOutputs = getModelOutputs( + encoderInputIds, + decoderInputIdsCopied, + pixelValues, + imageSizes, + numOfCrops, + inferRequestWTE, + inferRequestReshape, + inferRequestLanguageModel) + + val nextTokenIds = decoderOutputs.map { scores => + argmax(scores) + } + + generatedIds = + generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => + currentIds ++ Array(nextId) + } + + // extend decoder input ids + decoderInputIdsCopied = + decoderInputIdsCopied.zip(nextTokenIds).map { case (currentIds, nextId) => + currentIds ++ Array(nextId) + } + } + generatedIds + } + + def predict( + sentences: Seq[Annotation], + imageAnnotations: Seq[AnnotationImage], + batchSize: Int, + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long] = None, + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int): Seq[Annotation] = { + + val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences) + val (pixelValues, imageSizes, imgTokens) = preprocessedImages + val tagged = tag( + encodedText, + (pixelValues, imageSizes), + minOutputLength, + maxOutputLength, + doSample, + temperature, + topK, + topP, + repetitionPenalty, + noRepeatNgramSize, + randomSeed, + ignoreTokenIds, + beamSize, + maxInputLength, + Array(eosTokenId)) + val decoded = decode(tagged) + var sentBegin, nextSentEnd = 0 + val annotations = decoded.map { content => + nextSentEnd += content.length - 1 + val annots = new Annotation( + annotatorType = DOCUMENT, + begin = sentBegin, + end = nextSentEnd, + result = content, + metadata = Map()) + sentBegin += nextSentEnd + 1 + annots + } + annotations + } + + def getModelOutputs( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Array[Float]]]]], + imageSizes: Array[Array[Int]], + numOfCrops: Int, + inferRequestWTE: InferRequest, + inferRequestReshape: InferRequest, + inferRequestLanguageModel: InferRequest): Array[Array[Float]] = { + + val imageEmbeddings = getImageEmbeddings( + encoderInputIds, + decoderInputIds, + pixelValues, + imageSizes, + numOfCrops, + inferRequestReshape, + inferRequestWTE) + + val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + val posIdsLong = decoderInputIds.flatMap { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + } + } + (inpIdsLong, posIdsLong) + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + val posIdsLong = decoderInputIds.map { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + }.last + } + (inpIdsLong, posIdsLong) + } + val attentionMask: Array[Long] = + decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) } + + val batchSize: Int = decoderInputIds.length + val beamIdx: Array[Int] = new Array[Int](batchSize) + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + + val decoderAttentionMask: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask) + val decoderPositionIDs: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputPositionIDsLong) + val beamIdxTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize), beamIdx) + + inferRequestLanguageModel.set_tensor("inputs_embeds", imageEmbeddings) + inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask) + inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs) + inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor) + + inferRequestLanguageModel.infer() + + val result = inferRequestLanguageModel.get_tensor("logits") + val logitsRaw = result.data() + + val sequenceLength = inputIdsLong.length / batchSize + val decoderOutputs = (0 until batchSize).map(i => { + logitsRaw + .slice( + i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize, + i * sequenceLength * vocabSize + sequenceLength * vocabSize) + }) + decoderOutputs.toArray + } + + def encodeText(sentences: Seq[Annotation]): Seq[Array[Int]] = { + sentences.map(s => { + val sentWithTask = "_" + s.result + Array(bosTokenId) ++ spp.getSppModel.encodeAsIds(sentWithTask) + }) + } + + private def argmax(scores: Array[Float]): Int = + scores.zipWithIndex.maxBy { case (score, _) => + score + }._2 + + private def greedyGenerationFinished( + decoderIds: Seq[Array[Int]], + eosTokenId: Int, + maxOutputLength: Int): Boolean = + decoderIds.map(_.last).forall(_ == eosTokenId) || decoderIds.head.length == maxOutputLength + + def preprocessImage(imageAnnotations: Seq[AnnotationImage], numOfCrops: Int = 17) + : (Array[Array[Array[Array[Array[Float]]]]], Array[Array[Int]], List[Int]) = { + + val hdTransformedImage = imageAnnotations + .map(annotations => { + val bufferedImage = ImageIOUtils.byteToBufferedImage( + bytes = annotations.result, + w = annotations.width, + h = annotations.height, + nChannels = annotations.nChannels) + + Phi3vUtils.HDTransform(bufferedImage, numOfCrops) + }) + .toList + val (processedImages, imageSizes, imgTokens) = + Phi3vUtils.processHdImages(hdTransformedImage, numOfCrops) + val pixelValues = + Phi3vUtils.processedImagesTo5DArray(processedImages, normalize = true) + (pixelValues, imageSizes, imgTokens) + } + + def getImageEmbeddings( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Array[Float]]]]], + imageSizes: Array[Array[Int]], + numOfCrops: Int, + inferRequestReshape: InferRequest, + inferRequestWTE: InferRequest): org.intel.openvino.Tensor = { + val inputIdsLong: Array[Long] = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + + inpIdsLong + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + inpIdsLong + } + val batchSize: Int = decoderInputIds.length + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + val inputIdsLongTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputIdsLong) + + val imageEmbeddings: org.intel.openvino.Tensor = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + val pixelValuesTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor( + Array(batchSize, numOfCrops, 3, 336, 336), + pixelValues.flatten.flatten.flatten.flatten.map(_.toFloat)) + + val imageSizesTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize, 2), imageSizes.flatten.map(_.toFloat)) + inferRequestReshape.set_tensor("input_ids", inputIdsLongTensor) + inferRequestReshape.set_tensor("pixel_values", pixelValuesTensor) + inferRequestReshape.set_tensor("image_sizes", imageSizesTensor) + + inferRequestReshape.infer() + + inferRequestReshape.get_output_tensor() + + } else { + inferRequestWTE.set_tensor("input_ids", inputIdsLongTensor) + + inferRequestWTE.infer() + + inferRequestReshape.get_output_tensor() + } + imageEmbeddings + } + +} diff --git a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala index 0c2f65d4315e4e..ee7fb8a6173173 100644 --- a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala +++ b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala @@ -218,4 +218,9 @@ object OpenvinoWrapper { decoderWithPast: OpenvinoWrapper) case class DecoderWrappers(decoder: OpenvinoWrapper) case class EncoderDecoderWithoutPastWrappers(encoder: OpenvinoWrapper, decoder: OpenvinoWrapper) + + case class Phi3VWrappers( + wte: OpenvinoWrapper, + reshape: OpenvinoWrapper, + languageModel: OpenvinoWrapper) } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala index fe50bdbc7a962b..49c709ba1d09f3 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala @@ -6,7 +6,7 @@ import scala.collection.mutable.ArrayBuffer import ImageResizeUtils.resizeBufferedImage -class Phi3vUtils { +private[johnsnowlabs] object Phi3vUtils { // padding image def padding_336(image: BufferedImage): BufferedImage = { @@ -68,7 +68,7 @@ class Phi3vUtils { (padded_width, padded_height) } - def HD_transform(img: BufferedImage, hdNum: Int = 16): BufferedImage = { + def HDTransform(img: BufferedImage, hdNum: Int = 16): BufferedImage = { var width = img.getWidth var height = img.getHeight var transposed = false @@ -133,22 +133,45 @@ class Phi3vUtils { } // Function to calculate the shapes (height and width of the image) - def calculateShapes(images: List[BufferedImage]): List[(Int, Int)] = { - images.map(img => (img.getHeight, img.getWidth)) + def calculateShapes(images: List[BufferedImage]): Array[Array[Int]] = { + images.map(img => Array(img.getHeight, img.getWidth)).toArray } // Function to calculate the number of image tokens - def calculateImageTokens(shapes: List[(Int, Int)]): List[Int] = { - shapes.map { case (h, w) => +// def calculateImageTokens(shapes: List[(Int, Int)]): List[Int] = { +// shapes.map { case (h, w) => +// ((h / 336) * (w / 336) + 1) * 144 + 1 + ((h / 336 + 1) * 12) +// } +// } + + def calculateImageTokens(shapes: Array[Array[Int]]): List[Int] = { + shapes.map { case Array(h, w) => ((h / 336) * (w / 336) + 1) * 144 + 1 + ((h / 336 + 1) * 12) - } + }.toList } // Function to reshape the images (assuming each image is already HD transformed) +// def reshapeImages( +// images: List[BufferedImage], +// shapes: List[(Int, Int)]): List[List[BufferedImage]] = { +// images.zip(shapes).map { case (img, (h, w)) => +// val numH = h / 336 +// val numW = w / 336 +// val reshapedImages = new ListBuffer[BufferedImage] +// +// // Splitting the image into 336x336 crops +// for (i <- 0 until numH; j <- 0 until numW) { +// val crop = getNewSubimage(img, j * 336, i * 336, 336, 336) +// reshapedImages += crop +// } +// reshapedImages.toList +// } +// } + def reshapeImages( images: List[BufferedImage], - shapes: List[(Int, Int)]): List[List[BufferedImage]] = { - images.zip(shapes).map { case (img, (h, w)) => + shapes: Array[Array[Int]]): List[List[BufferedImage]] = { + images.zip(shapes).map { case (img, Array(h, w)) => val numH = h / 336 val numW = w / 336 val reshapedImages = new ListBuffer[BufferedImage] @@ -208,7 +231,7 @@ class Phi3vUtils { // Main function that processes the HD transformed images def processHdImages( hdImages: List[BufferedImage], - numCrops: Int): (List[BufferedImage], List[(Int, Int)], List[Int]) = { + numCrops: Int): (List[BufferedImage], Array[Array[Int]], List[Int]) = { // Step 1: Create global images (resize to 336x336) // val resizeGlobal = val globalImages = hdImages.map(resizeBufferedImage(336, 336, 3)) @@ -230,8 +253,6 @@ class Phi3vUtils { // Step 6: Pad to max_num_crops if necessary val paddedImages = concatenatedImages.map(padToMaxNumCrops(_, numCrops + 1)) - // val paddedImages = concatenatedImages - // Return the transformed images, their sizes, and the number of image tokens (paddedImages, shapes, numImgTokens) } @@ -285,7 +306,7 @@ class Phi3vUtils { normalize: Boolean = false, mean: Array[Double] = Array(0.48145466, 0.4578275, 0.40821073), std: Array[Double] = Array(0.26862954, 0.26130258, 0.27577711)) - : Array[Array[Array[Array[Float]]]] = { + : (Array[Array[Array[Array[Float]]]], Int) = { val height = image.getHeight val width = image.getWidth @@ -316,7 +337,7 @@ class Phi3vUtils { } // Convert ArrayBuffer to an array - cropsBuffer.toArray + (cropsBuffer.toArray, numHCrops * numWCrops) } // Function to convert processedImages (BufferedImages) into a 5D array (b, h//336 * w//336, 3, 336, 336) @@ -325,14 +346,14 @@ class Phi3vUtils { normalize: Boolean = false, mean: Array[Double] = Array(0.48145466, 0.4578275, 0.40821073), std: Array[Double] = Array(0.26862954, 0.26130258, 0.27577711)) - : Array[Array[Array[Array[Array[Float]]]]] = { + : (Array[Array[Array[Array[Array[Float]]]]]) = { // Store the 5D array (b, h//336 * w//336, 3, 336, 336) val batchBuffer = ArrayBuffer[Array[Array[Array[Array[Float]]]]]() - // Process each image in the batch processedImages.foreach { img => // Split the image into crops, convert each crop into a 3D array, and normalize if required - val imageCropsArray = splitImageToCrops(img, normalize = normalize, mean = mean, std = std) + val (imageCropsArray, numCrops) = + splitImageToCrops(img, normalize = normalize, mean = mean, std = std) batchBuffer.append(imageCropsArray) } From 621411be9f795d8216fb4909bad085328f947c42 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Mon, 28 Oct 2024 13:05:30 +0000 Subject: [PATCH 035/108] add phi3v scala API Signed-off-by: Prabod Rathnayaka --- .../scala/com/johnsnowlabs/ml/ai/Phi3V.scala | 174 ++++-- .../ml/util/LoadExternalModel.scala | 58 +- .../nlp/annotators/cv/Phi3Vision.scala | 521 ++++++++++++++++++ .../tokenizer/bpe/BpeTokenizer.scala | 11 +- .../tokenizer/bpe/Phi3VisionTokenizer.scala | 112 ++++ .../annotators/cv/Phi3VisionTestSpec.scala | 184 +++++++ 6 files changed, 991 insertions(+), 69 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala b/src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala index 638ee840931aef..f00ae3e2e39810 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/Phi3V.scala @@ -20,38 +20,33 @@ import breeze.optimize.BatchSize import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.Phi3VWrappers -import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper -import com.johnsnowlabs.ml.tensorflow.sign.{ModelSignatureConstants, ModelSignatureManager} -import com.johnsnowlabs.ml.tensorflow.{TensorResources, TensorflowWrapper} +import com.johnsnowlabs.nlp.annotators.common.Sentence import com.johnsnowlabs.ml.util.{ONNX, Openvino} import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.annotators.common.SentenceSplit import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils import com.johnsnowlabs.nlp.annotators.cv.util.transform.ImageResizeUtils import com.johnsnowlabs.nlp.annotators.cv.util.transform.Phi3vUtils +import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{ + BpeTokenizer, + Phi3VisionTokenizer, + SpecialTokens +} import org.intel.openvino.InferRequest import scala.collection.JavaConverters._ private[johnsnowlabs] class Phi3V( - val openvinoWrapper: Option[Phi3VWrappers], val onnxWrappers: Option[DecoderWrappers], - preprocessor: Preprocessor, - val spp: SentencePieceWrapper, + val openvinoWrapper: Option[Phi3VWrappers], + merges: Map[(String, String), Int], + vocabulary: Map[String, Int], + addedTokens: Map[String, Int], generationConfig: GenerationConfig) extends Serializable { - private def sessionWarmup(): Unit = { - val image = - ImageIOUtils.loadImage(getClass.getResourceAsStream("/image/ox.JPEG")) - val bytes = ImageIOUtils.bufferedImageToByte(image.get) - val images = - Array(AnnotationImage("image", "ox.JPEG", 265, 360, 3, 16, bytes, Map("image" -> "0"))) -// val encoded = encode(images, preprocessor) -// tag(encoded) - } - val detectedEngine: String = if (onnxWrappers.isDefined) ONNX.name else if (openvinoWrapper.isDefined) Openvino.name @@ -66,8 +61,27 @@ private[johnsnowlabs] class Phi3V( suppressTokenIds, forcedDecoderIds) = generationConfig - - private val pieceSize = spp.getSppModel.getPieceSize + val reversedVocabulary: Map[Int, String] = vocabulary.map(_.swap) + + val specialTokens: SpecialTokens = SpecialTokens( + vocabulary, + startTokenString = reversedVocabulary(bosTokenId), + endTokenString = reversedVocabulary(eosTokenId), + unkTokenString = reversedVocabulary(eosTokenId), + maskTokenString = reversedVocabulary(eosTokenId), + padTokenString = reversedVocabulary(paddingTokenId), + additionalStrings = addedTokens.keys.toArray) + + val bpeTokenizer: Phi3VisionTokenizer = BpeTokenizer + .forModel( + "phi3v", + merges = merges, + vocab = vocabulary, + specialTokens = Some(specialTokens), + addPrefixSpaceToSentence = true, + alwaysAddPrefix = false, + prependString = "") + .asInstanceOf[Phi3VisionTokenizer] /** Decode a sequence of sentences * @param sentences @@ -76,26 +90,86 @@ private[johnsnowlabs] class Phi3V( * Sequence of decoded sentences */ def decode(sentences: Array[Array[Int]]): Seq[String] = { - sentences.map { s => - val filteredPieceIds = s.filter(x => x <= pieceSize) - spp.getSppModel.decodeIds(filteredPieceIds.map(_.toInt): _*) + sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt))) + } + + /** Encode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of encoded sentences + */ + def encodeText(sentences: Seq[Annotation], imgTokenLen: List[Int]): Seq[Array[Int]] = { + + val pattern = raw"<\|image_\d+\|>".r + + // raise an error if the pattern is not found in the text + if (pattern.findFirstIn(sentences.head.result).isEmpty) { + throw new IllegalArgumentException( + "The pattern <\\|image_\\d+\\|> is not found in the text") } + + // split the sentences into chunks based on the pattern and tokenize them + // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)] + val promptChunks = sentences + .map(s => { + val sentWithTask = s.result + var offsetLength = 0 + pattern + .split(sentWithTask) + .zipWithIndex + .map(s => { + val sentenceWithTask = Sentence( + content = s._1, + start = offsetLength, + end = offsetLength + s._1.length, + index = s._2) + offsetLength += s._1.length + bpeTokenizer + .tokenize(sentenceWithTask) + .map(bpeTokenizer.encode) + .flatMap(_.map(_.pieceId)) + }) + }) + + // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]] + val tokens = promptChunks + .zip(imgTokenLen) + .map(s => { + val (promptChunk, imgTokenLen) = s + val imgPaddingTokens = Array.fill(imgTokenLen)(-1) + val combinedChunks = promptChunk + .map(_.toArray) + .reduce(_ ++ imgPaddingTokens ++ _) + Array(bosTokenId) ++ combinedChunks ++ Array(eosTokenId) + }) + +// val tokens = SentenceSplit +// .unpack(sentences) +// .map(s => { +// val sentWithTask = s +// bpeTokenizer +// .tokenize(sentWithTask) +// .map(bpeTokenizer.encode) +// .flatMap(_.map(_.pieceId)) +// }) + tokens } def encode( imageAnnotations: Seq[AnnotationImage], sentences: Seq[Annotation], - numOfCrops: Int = 17): ( + numOfCrops: Int = 16): ( Seq[Array[Int]], (Array[Array[Array[Array[Array[Float]]]]], Array[Array[Int]], List[Int])) = { val preprocessedImages = preprocessImage(imageAnnotations, numOfCrops) - val encodedText = encodeText(sentences).toArray + val encodedText = encodeText(sentences, preprocessedImages._3).toArray (encodedText, preprocessedImages) } def tag( batch: Seq[Array[Int]], - images: (Array[Array[Array[Array[Array[Float]]]]], Array[Array[Int]]), + images: (Array[Array[Array[Array[Array[Float]]]]], Array[Array[Int]], List[Int]), minOutputLength: Int, maxOutputLength: Int, doSample: Boolean, @@ -109,9 +183,9 @@ private[johnsnowlabs] class Phi3V( beamSize: Int, maxInputLength: Int, stopTokenIds: Array[Int], - numOfCrops: Int = 17): Array[Array[Int]] = { + numOfCrops: Int = 16): Array[Array[Int]] = { - val (pixelValues, imageSizes) = images + val (pixelValues, imageSizes, imgTokens) = images val ignoreTokenIdsInt = ignoreTokenIds val expandedDecoderInputsVals = batch val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray @@ -164,10 +238,7 @@ private[johnsnowlabs] class Phi3V( var generatedIds: Array[Array[Int]] = Array() var decoderInputIdsCopied = decoderInputIds - while (!greedyGenerationFinished( - generatedIds.map(_.map(_.toInt)), - eosTokenId, - maxOutputLength)) { + while (!greedyGenerationFinished(generatedIds, eosTokenId, maxOutputLength)) { val decoderOutputs = getModelOutputs( encoderInputIds, decoderInputIdsCopied, @@ -182,10 +253,14 @@ private[johnsnowlabs] class Phi3V( argmax(scores) } - generatedIds = - generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => - currentIds ++ Array(nextId) - } + if (generatedIds.isEmpty) { + generatedIds = nextTokenIds.map(Array(_)) + } else { + generatedIds = + generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => + currentIds ++ Array(nextId) + } + } // extend decoder input ids decoderInputIdsCopied = @@ -217,7 +292,7 @@ private[johnsnowlabs] class Phi3V( val (pixelValues, imageSizes, imgTokens) = preprocessedImages val tagged = tag( encodedText, - (pixelValues, imageSizes), + preprocessedImages, minOutputLength, maxOutputLength, doSample, @@ -232,6 +307,7 @@ private[johnsnowlabs] class Phi3V( maxInputLength, Array(eosTokenId)) val decoded = decode(tagged) + var sentBegin, nextSentEnd = 0 val annotations = decoded.map { content => nextSentEnd += content.length - 1 @@ -320,13 +396,6 @@ private[johnsnowlabs] class Phi3V( decoderOutputs.toArray } - def encodeText(sentences: Seq[Annotation]): Seq[Array[Int]] = { - sentences.map(s => { - val sentWithTask = "_" + s.result - Array(bosTokenId) ++ spp.getSppModel.encodeAsIds(sentWithTask) - }) - } - private def argmax(scores: Array[Float]): Int = scores.zipWithIndex.maxBy { case (score, _) => score @@ -335,10 +404,17 @@ private[johnsnowlabs] class Phi3V( private def greedyGenerationFinished( decoderIds: Seq[Array[Int]], eosTokenId: Int, - maxOutputLength: Int): Boolean = - decoderIds.map(_.last).forall(_ == eosTokenId) || decoderIds.head.length == maxOutputLength + maxOutputLength: Int): Boolean = { + if (decoderIds.isEmpty) { + false + } else { + decoderIds.forall { ids => + ids.length >= maxOutputLength || ids.last == eosTokenId + } + } + } - def preprocessImage(imageAnnotations: Seq[AnnotationImage], numOfCrops: Int = 17) + def preprocessImage(imageAnnotations: Seq[AnnotationImage], numOfCrops: Int = 16) : (Array[Array[Array[Array[Array[Float]]]]], Array[Array[Int]], List[Int]) = { val hdTransformedImage = imageAnnotations @@ -387,11 +463,11 @@ private[johnsnowlabs] class Phi3V( if (encoderInputIds.head.length == decoderInputIds.head.length) { val pixelValuesTensor: org.intel.openvino.Tensor = new org.intel.openvino.Tensor( - Array(batchSize, numOfCrops, 3, 336, 336), + Array(batchSize, numOfCrops + 1, 3, 336, 336), pixelValues.flatten.flatten.flatten.flatten.map(_.toFloat)) val imageSizesTensor: org.intel.openvino.Tensor = - new org.intel.openvino.Tensor(Array(batchSize, 2), imageSizes.flatten.map(_.toFloat)) + new org.intel.openvino.Tensor(Array(batchSize, 2), imageSizes.flatten.map(_.toLong)) inferRequestReshape.set_tensor("input_ids", inputIdsLongTensor) inferRequestReshape.set_tensor("pixel_values", pixelValuesTensor) inferRequestReshape.set_tensor("image_sizes", imageSizesTensor) @@ -401,11 +477,11 @@ private[johnsnowlabs] class Phi3V( inferRequestReshape.get_output_tensor() } else { - inferRequestWTE.set_tensor("input_ids", inputIdsLongTensor) + inferRequestWTE.set_input_tensor(inputIdsLongTensor) inferRequestWTE.infer() - inferRequestReshape.get_output_tensor() + inferRequestWTE.get_output_tensor() } imageEmbeddings } diff --git a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala index cd0761f0f9daa3..1b48fdf2c7b6d5 100644 --- a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala +++ b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala @@ -18,6 +18,7 @@ package com.johnsnowlabs.ml.util import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper import com.johnsnowlabs.nlp.util.io.{ExternalResource, ReadAs, ResourceHelper} +import org.glassfish.jersey.internal.inject.Custom import java.io.File import java.nio.file.Paths @@ -103,22 +104,39 @@ object LoadExternalModel { } - def isOpenvinoModel(modelPath: String, isEncoderDecoder: Boolean): Boolean = { - if (isEncoderDecoder) { - val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml") - val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin") - val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml") - val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin") - val ovDecoderModelWithPastXml = new File(modelPath, s"${Openvino.decoderModelWithPast}.xml") - val ovDecoderModelWithPastBin = new File(modelPath, s"${Openvino.decoderModelWithPast}.bin") - - ovEncoderModelXml.exists() && ovEncoderModelBin.exists() && - ovDecoderModelXml.exists() && ovDecoderModelBin.exists() && - ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists() + def isOpenvinoModel( + modelPath: String, + isEncoderDecoder: Boolean, + custom: Option[List[String]] = None): Boolean = { + + if (custom.isDefined) { + for (model <- custom.get) { + val ovModelXml = new File(modelPath, s"${model}.xml") + val ovModelBin = new File(modelPath, s"${model}.bin") + if (!ovModelXml.exists() || !ovModelBin.exists()) { + return false + } + } + true } else { - val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml") - val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin") - modelXml.exists() && modelBin.exists() + if (isEncoderDecoder) { + val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml") + val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin") + val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml") + val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin") + val ovDecoderModelWithPastXml = + new File(modelPath, s"${Openvino.decoderModelWithPast}.xml") + val ovDecoderModelWithPastBin = + new File(modelPath, s"${Openvino.decoderModelWithPast}.bin") + + ovEncoderModelXml.exists() && ovEncoderModelBin.exists() && + ovDecoderModelXml.exists() && ovDecoderModelBin.exists() && + ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists() + } else { + val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml") + val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin") + modelXml.exists() && modelBin.exists() + } } } @@ -126,7 +144,8 @@ object LoadExternalModel { modelPath: String, isEncoderDecoder: Boolean = false, withPast: Boolean = false, - isDecoder: Boolean = false): String = { + isDecoder: Boolean = false, + custom: Option[List[String]] = None): String = { /** Check if the path is correct */ val f = new File(modelPath) @@ -146,7 +165,7 @@ object LoadExternalModel { val onnxModelExist = isOnnxModel(modelPath, isEncoderDecoder, withPast, isDecoder) /*Openvino required model files*/ - val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder) + val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder, custom) if (tfSavedModelExist) { TensorFlow.name @@ -176,10 +195,11 @@ object LoadExternalModel { path: String, isEncoderDecoder: Boolean = false, withPast: Boolean = false, - isDecoder: Boolean = false): (String, String) = { + isDecoder: Boolean = false, + custom: Option[List[String]] = None): (String, String) = { val localPath: String = ResourceHelper.copyToLocal(path) - (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder)) + (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder, custom)) } def loadTextAsset(assetPath: String, assetName: String): Array[String] = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala new file mode 100644 index 00000000000000..f57c90662e23de --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala @@ -0,0 +1,521 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.ai.Phi3V +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadJsonStringAsset, + loadTextAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.ml.util.{Openvino} +import com.johnsnowlabs.nlp.AnnotatorType.{DOCUMENT, IMAGE} +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.annotators.RegexTokenizer +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.nlp.annotators.sbd.pragmatic.SentenceDetector +import org.json4s.{DefaultFormats, JValue} +import org.json4s.jackson.JsonMethods.parse +//import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{BertTokenizer, SpecialTokens} +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.Phi3VWrappers +import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntArrayParam, IntParam} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +/** Phi3Vision can load BLIP models for visual question answering. The model consists of a vision + * encoder, a text encoder as well as a text decoder. The vision encoder will encode the input + * image, the text encoder will encode the input question together with the encoding of the + * image, and the text decoder will output the answer to the question. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val visualQAClassifier = Phi3Vision.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * }}} + * The default model is `"blip_vqa_base"`, if no name is provided. + * + * For available pretrained models please see the + * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTest.scala]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val imageDF: DataFrame = ResourceHelper.spark.read + * .format("image") + * .option("dropInvalid", value = true) + * .load(imageFolder) + * + * val testDF: DataFrame = imageDF.withColumn("text", lit("What's this picture about?")) + * + * val imageAssembler: ImageAssembler = new ImageAssembler() + * .setInputCol("image") + * .setOutputCol("image_assembler") + * + * val visualQAClassifier = Phi3Vision.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * + * val pipeline = new Pipeline().setStages(Array( + * imageAssembler, + * visualQAClassifier + * )) + * + * val result = pipeline.fit(testDF).transform(testDF) + * + * result.select("image_assembler.origin", "answer.result").show(false) + * +--------------------------------------+------+ + * |origin |result| + * +--------------------------------------+------+ + * |[file:///content/images/cat_image.jpg]|[cats]| + * +--------------------------------------+------+ + * }}} + * + * @see + * [[CLIPForZeroShotClassification]] for Zero Shot Image Classifier + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ + +class Phi3Vision(override val uid: String) + extends AnnotatorModel[Phi3Vision] + with HasBatchedAnnotateImage[Phi3Vision] + with HasImageFeatureProperties + with WriteOpenvinoModel + with HasGeneratorProperties + with HasEngine { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("Phi3Vision")) + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(IMAGE) + override val outputAnnotatorType: AnnotatorType = DOCUMENT + + /** @group setParam */ + def setRandomSeed(value: Int): Phi3Vision.this.type = { + if (randomSeed.isEmpty) { + this.randomSeed = Some(value) + } + this + } + + /** A list of token ids which are ignored in the decoder's output (Default: `Array()`) + * + * @group param + */ + var ignoreTokenIds = new IntArrayParam( + this, + "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output") + + /** @group setParam */ + def setIgnoreTokenIds(tokenIds: Array[Int]): Phi3Vision.this.type = { + set(ignoreTokenIds, tokenIds) + } + + /** @group getParam */ + def getIgnoreTokenIds: Array[Int] = $(ignoreTokenIds) + + /** Vocabulary used to encode the words to ids with bpeTokenizer.encode + * + * @group param + */ + val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() + + /** @group setParam */ + def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value) + + /** Holding merges.txt coming from RoBERTa model + * + * @group param + */ + val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected() + + /** @group setParam */ + def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value) + + /** Additional tokens to be added to the vocabulary + * + * @group param + */ + val addedTokens: MapFeature[String, Int] = new MapFeature(this, "addedTokens").setProtected() + + /** @group setParam */ + def setAddedTokens(value: Map[String, Int]): this.type = set(addedTokens, value) + + /** Stop tokens to terminate the generation + * + * @group param + */ + override val stopTokenIds = + new IntArrayParam(this, "stopTokenIds", "Stop tokens to terminate the generation") + + /** @group setParam */ + override def setStopTokenIds(value: Array[Int]): this.type = { + set(stopTokenIds, value) + } + + /** @group getParam */ + override def getStopTokenIds: Array[Int] = $(stopTokenIds) + + private var _model: Option[Broadcast[Phi3V]] = None + val generationConfig: StructFeature[GenerationConfig] = + new StructFeature(this, "generationConfig").setProtected() + + def setGenerationConfig(value: GenerationConfig): this.type = + set(generationConfig, value) + + def getGenerationConfig: GenerationConfig = $$(generationConfig) + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + onnxWrappers: Option[DecoderWrappers], + openvinoWrapper: Option[Phi3VWrappers]): this.type = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new Phi3V( + onnxWrappers, + openvinoWrapper, + $$(merges), + $$(vocabulary), + $$(addedTokens), + generationConfig = getGenerationConfig))) + } + this + } + + /** @group getParam */ + def getModelIfNotSet: Phi3V = _model.get.value + + setDefault( + minOutputLength -> 0, + maxOutputLength -> 20, + doSample -> false, + temperature -> 0.6, + topK -> -1, + topP -> 0.9, + repetitionPenalty -> 1.0, + noRepeatNgramSize -> 3, + ignoreTokenIds -> Array(), + batchSize -> 1, + beamSize -> 1, + maxInputLength -> 4096, + stopTokenIds -> Array(128001)) + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + */ + override def batchAnnotate( + batchedAnnotations: Seq[Array[AnnotationImage]]): Seq[Seq[Annotation]] = { + + batchedAnnotations + .filter { annotationImages => + annotationImages.exists(_.text.nonEmpty) + } + .map { cleanAnnotationImages => + val validImages = cleanAnnotationImages.filter(_.result.nonEmpty) + val questionAnnotations = extractInputAnnotation(validImages) + + getModelIfNotSet.predict( + questionAnnotations, + validImages.toSeq, + batchSize = $(batchSize), + minOutputLength = $(minOutputLength), + maxOutputLength = $(maxOutputLength), + doSample = $(doSample), + temperature = $(temperature), + topK = $(topK), + topP = $(topP), + repetitionPenalty = $(repetitionPenalty), + noRepeatNgramSize = $(noRepeatNgramSize), + randomSeed = this.randomSeed, + ignoreTokenIds = $(ignoreTokenIds), + beamSize = $(beamSize), + maxInputLength = $(maxInputLength)) + } + } + + private def extractInputAnnotation( + annotationImages: Array[AnnotationImage]): Seq[Annotation] = { + val questions = annotationImages.map(annotationImage => Annotation(annotationImage.text)) + + questions + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) +// writeTensorflowModelV2( +// path, +// spark, +// getModelIfNotSet.tensorflowWrapper, +// "_image_qa", +// Phi3Vision.tfFile, +// configProtoBytes = getConfigProtoBytes) + } + +} + +trait ReadablePretrainedPhi3Vision + extends ParamsAndFeaturesReadable[Phi3Vision] + with HasPretrained[Phi3Vision] { + + override val defaultModelName: Some[String] = Some("blip_vqa_base") + + /** Java compliant-overrides */ + override def pretrained(): Phi3Vision = super.pretrained() + + override def pretrained(name: String): Phi3Vision = + super.pretrained(name) + + override def pretrained(name: String, lang: String): Phi3Vision = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): Phi3Vision = + super.pretrained(name, lang, remoteLoc) + +} + +trait ReadPhi3VisionDLModel extends ReadOpenvinoModel { + this: ParamsAndFeaturesReadable[Phi3Vision] => + val suffix: String = "_phi3v" + override val openvinoFile: String = "phi3v_openvino" + def readModel(instance: Phi3Vision, path: String, spark: SparkSession): Unit = { + instance.getEngine match { + case Openvino.name => + val reshapeWrappers = + readOpenvinoModels(path, spark, Seq("reshape_model.xml"), suffix) + val wteWrappers = + readOpenvinoModels(path, spark, Seq("wte_model.xml"), suffix) + + val languageModelWrappers = + readOpenvinoModels(path, spark, Seq("language_model.xml"), suffix) + + val ovWrapper = Phi3VWrappers( + wte = wteWrappers("wte_model.xml"), + languageModel = languageModelWrappers("language_model.xml"), + reshape = reshapeWrappers("reshape_model.xml")) + instance.setModelIfNotSet(spark, None, Some(ovWrapper)) + case _ => { + throw new Exception(notSupportedEngineError) + } + } + } + + addReader(readModel) + + def loadSavedModel( + modelPath: String, + spark: SparkSession, + useOpenvino: Boolean = false): Phi3Vision = { + implicit val formats: DefaultFormats.type = DefaultFormats // for json4 + val (localModelPath, detectedEngine) = + modelSanityCheck( + modelPath, + isDecoder = false, + custom = Some(List("reshape_model", "wte_model", "language_model"))) + val modelConfig: JValue = + parse(loadJsonStringAsset(localModelPath, "config.json")) + + val beginSuppressTokens: Array[Int] = + (modelConfig \ "begin_suppress_tokens").extract[Array[Int]] + + val suppressTokenIds: Array[Int] = + (modelConfig \ "suppress_tokens").extract[Array[Int]] + + val forcedDecoderIds: Array[(Int, Int)] = + (modelConfig \ "forced_decoder_ids").extract[Array[Array[Int]]].map { + case idxWithTokenId: Array[Int] if idxWithTokenId.length == 2 => + (idxWithTokenId(0), idxWithTokenId(1)) + case _ => + throw new Exception( + "Could not extract forced_decoder_ids. Should be a list of tuples with 2 entries.") + } + + def arrayOrNone[T](array: Array[T]): Option[Array[T]] = + if (array.nonEmpty) Some(array) else None + + val bosTokenId = (modelConfig \ "bos_token_id").extract[Int] + val eosTokenId = (modelConfig \ "eos_token_id").extract[Int] + val padTokenId = (modelConfig \ "eos_token_id").extract[Int] + val vocabSize = (modelConfig \ "vocab_size").extract[Int] + + // Check if tokenizer.json exists + val tokenizerPath = s"$localModelPath/assets/tokenizer.json" + val tokenizerExists = new java.io.File(tokenizerPath).exists() + val (vocabs, addedTokens, bytePairs) = if (tokenizerExists) { + val tokenizerConfig: JValue = parse(loadJsonStringAsset(localModelPath, "tokenizer.json")) + // extract vocab from tokenizer.json ( model -> vocab) + var vocabs: Map[String, Int] = + (tokenizerConfig \ "model" \ "vocab").extract[Map[String, Int]] + + // extract merges from tokenizer.json ( model -> merges) + val bytePairs = (tokenizerConfig \ "model" \ "merges") + .extract[List[String]] + .map(_.split(" ")) + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + + // extract added_tokens from tokenizer.json (added_tokens) + // "added_tokens": [ + // { + // "id": 128000, + // "content": "<|begin_of_text|>", + // "single_word": false, + // "lstrip": false, + // "rstrip": false, + // "normalized": false, + // "special": true + // }, ... + // ] + val addedTokens = (tokenizerConfig \ "added_tokens") + .extract[List[Map[String, Any]]] + .map { token => + val id = token("id").asInstanceOf[BigInt].intValue() + val content = token("content").asInstanceOf[String] + (content, id) + } + .toMap + + // update vocab with added tokens + addedTokens.foreach { case (content, id) => + vocabs += (content -> id) + } + (vocabs, addedTokens, bytePairs) + } else { + val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap + val addedTokens = loadTextAsset(localModelPath, "added_tokens.txt").zipWithIndex.toMap + val bytePairs = loadTextAsset(localModelPath, "merges.txt") + .map(_.split(" ")) + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + (vocabs, addedTokens, bytePairs) + } + + val annotatorModel = new Phi3Vision() + .setGenerationConfig( + GenerationConfig( + bosTokenId, + padTokenId, + eosTokenId, + vocabSize, + arrayOrNone(beginSuppressTokens), + arrayOrNone(suppressTokenIds), + arrayOrNone(forcedDecoderIds))) + .setVocabulary(vocabs) + .setMerges(bytePairs) + .setAddedTokens(addedTokens) + + val modelEngine = + if (useOpenvino) + Openvino.name + else + detectedEngine + annotatorModel.set(annotatorModel.engine, modelEngine) + + detectedEngine match { + case Openvino.name => + val reshapeWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "reshape_model") + val wteWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "wte_model") + val languageModelWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "language_model") + val openvinoWrapper = Phi3VWrappers( + wte = wteWrappers, + languageModel = languageModelWrappers, + reshape = reshapeWrappers) + annotatorModel.setModelIfNotSet(spark, None, Some(openvinoWrapper)) + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } +} + +object Phi3Vision extends ReadablePretrainedPhi3Vision with ReadPhi3VisionDLModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala index 8c72a8f99d6685..a0256539b6a6b2 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala @@ -319,7 +319,8 @@ object BpeTokenizer { padWithSequenceTokens: Boolean = false, addPrefixSpaceToSentence: Boolean = false, specialTokens: Option[SpecialTokens] = None, - alwaysAddPrefix: Boolean = true): BpeTokenizer = { + alwaysAddPrefix: Boolean = true, + prependString: String = ""): BpeTokenizer = { def modelSpecialTokens() = specialTokens match { case Some(specialTok) => specialTok @@ -382,6 +383,14 @@ object BpeTokenizer { modelSpecialTokens(), padWithSequenceTokens, addPrefixSpaceToSentence = addPrefixSpaceToSentence) + case "phi3v" => + new Phi3VisionTokenizer( + merges, + vocab, + modelSpecialTokens(), + padWithSequenceTokens, + addPrefixSpaceToSentence = addPrefixSpaceToSentence, + prependString = prependString) case _ => throw new IllegalArgumentException("Model type \"" + modelType + "\" not supported yet.") } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala new file mode 100644 index 00000000000000..dc44585d894da1 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala @@ -0,0 +1,112 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.tokenizer.bpe + +import com.johnsnowlabs.nlp.annotators.common.IndexedToken + +import java.nio.charset.Charset +import scala.collection.mutable.ListBuffer +import scala.util.matching.Regex + +class Phi3VisionTokenizer( + merges: Map[(String, String), Int], + vocab: Map[String, Int], + specialTokens: SpecialTokens, + padWithSequenceTokens: Boolean = true, + prependString: String = "", + addPrefixSpaceToSentence: Boolean = false, + alwaysAddPrefix: Boolean = true, + splitPatternRegex: Regex = + raw"""(?i)(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".r) + extends BpeTokenizer( + merges, + vocab, + specialTokens, + padWithSequenceTokens, + addPrefixSpaceToSentence, + alwaysAddPrefix) { + + /** Mapping for bytes to a different set of unicode characters (especially white spaces). This + * improved model performance for gpt-2 + */ + protected val bytesToUnicodeMapping: Map[Int, String] = { + val bytes: ListBuffer[Int] = + ListBuffer.range('!', '~' + 1) ++ ListBuffer.range('ยก', 'ยฌ' + 1) ++ ListBuffer + .range('ยฎ', 'รฟ' + 1) + val characters: ListBuffer[Int] = bytes.clone + var n = 0 + for (b <- 0 to 256) { + if (!bytes.contains(b)) { + bytes += b + characters += (256 + n) + n += 1 + } + } + (bytes zip characters.map(_.toChar.toString)).toMap + } + + // Differs from Transformers, space is always prepended. + // FIX: Space should not be prepended to all tokens, but to the beginning of the text only. Otherwise token + // such as '.' get space prepended and they should not. + override val prefixForPieceId: Option[String] = + if (prependString.nonEmpty) Some(prependString) else None + + protected val decoderVocab: Map[Int, String] = vocab.map(x => (x._2, x._1)) + + protected val unicodeToByteMapping: Map[String, Int] = + bytesToUnicodeMapping.map(x => (x._2, x._1)) + + override def preProcessTokenForBpe(token: String): String = { + token + .getBytes("UTF-8") + .map { b => if (b < 0) 256 + b else b } + .foldLeft("")(_ + bytesToUnicodeMapping(_)) + } + + val splitPattern: Regex = splitPatternRegex + + override def tokenizeSubText(text: String, indexOffset: Int): Array[IndexedToken] = { + // split pattern based on gpt2's bpe tokenizer + splitPattern + .findAllMatchIn(if (prefixForPieceId.isDefined || text.startsWith(" ")) text + else " " + text) // Prepend space to the beginning of text + .map(tok => IndexedToken(tok.matched, tok.start + indexOffset, tok.end + indexOffset - 1)) + .toArray + } + + def decodeTokens(tokens: Array[Int]): String = { + var text = tokens + .map(token => decoderVocab(token)) + .filter(x => !specialTokens.contains(x)) + .mkString("") + + text = text.replaceAll("โ–", " ").trim() + + text = + try { + val bytes = + text.map(x => unicodeToByteMapping(x.toString)).map(x => x.toByte).toArray + new String(bytes, Charset.forName("UTF-8")) + } catch { + case e: Exception => + {} + // Do nothing, just return the text + text + } + text + } +} diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala new file mode 100644 index 00000000000000..ff2c0de794f703 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala @@ -0,0 +1,184 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, ImageAssembler} +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit +import org.scalatest.flatspec.AnyFlatSpec + +class Phi3VisionTestSpec extends AnyFlatSpec { + + lazy val model = getBLIPForQuestionAnsweringPipelineModel + +// "BLIP" should "answer a question for a given image" taggedAs FastTest in { +// +// val testDF = getTestDF +// val result = model.transform(testDF) +// +// val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") +// +// answerAnnotation.foreach { annotation => +// annotation.foreach(a => assert(a.result.nonEmpty)) +// } +// +// answerAnnotation.foreach { annotation => +// annotation.foreach(a => println(a.result)) +// } +// +// } + + it should "work with light pipeline annotate" taggedAs FastTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/image/egyptian_cat.jpeg" + val resultAnnotate = + lightPipeline.annotate( + imagePath, + "<|user|> \n <|image_1|> \nWhat is unusual on this picture? <|end|>\n <|assistant|>\n") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.contains("cat")) + } +// +// it should "work with light pipeline full annotate" taggedAs SlowTest in { +// val lightPipeline = new LightPipeline(model) +// val imagePath = "src/test/resources/image/bluetick.jpg" +// val resultFullAnnotate = +// lightPipeline.fullAnnotateImage(imagePath, "What's this picture about?") +// +// val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] +// +// println(s"imageName.result: ${answerAnnotation.result}") +// assert(answerAnnotation.result.nonEmpty) +// } + +// it should "fullAnnotate with empty Map when a text is empty" taggedAs SlowTest in { +// val lightPipeline = new LightPipeline(model) +// val imagesPath = Array( +// "src/test/resources/image/bluetick.jpg", +// "src/test/resources/image/chihuahua.jpg", +// "src/test/resources/image/egyptian_cat.jpeg") +// val question = "What's this picture about?" +// val questions = Array(question, "", question) +// +// val resultFullAnnotate = lightPipeline.fullAnnotateImages(imagesPath, questions) +// +// resultFullAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => +// imagePath match { +// case "src/test/resources/image/chihuahua.jpg" => +// // For the chihuahua image, the annotateMap should be empty because the question is empty +// assert( +// annotateMap.isEmpty, +// s"Expected empty map for image: $imagePath, but got: $annotateMap") +// +// case _ => +// assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") +// +// annotateMap.get("answer") match { +// case Some(annotations) => +// annotations.foreach { iAnnotation => +// val annotation = iAnnotation.asInstanceOf[Annotation] +// assert( +// annotation.result.nonEmpty, +// s"Expected non-empty result for image: $imagePath, but got empty result") +// } +// case None => +// fail(s"'answer' key not found in annotateMap for image: $imagePath") +// } +// } +// } +// } + +// it should "annotate with empty Map when a text is empty" taggedAs SlowTest in { +// val lightPipeline = new LightPipeline(model) +// val imagesPath = Array( +// "src/test/resources/image/bluetick.jpg", +// "src/test/resources/image/chihuahua.jpg", +// "src/test/resources/image/egyptian_cat.jpeg") +// val question = "What's this picture about?" +// val questions = Array(question, "", question) +// +// val resultAnnotate = lightPipeline.annotate(imagesPath, questions) +// +// resultAnnotate.foreach { annotate => +// println(s"annotate: $annotate") +// } +// +// resultAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => +// imagePath match { +// case "src/test/resources/image/chihuahua.jpg" => +// // For the chihuahua image, the annotateMap should be empty because the question is empty +// assert( +// annotateMap.isEmpty, +// s"Expected empty map for image: $imagePath, but got: $annotateMap") +// +// case _ => +// assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") +// +// annotateMap.get("answer") match { +// case Some(annotations) => +// annotations.foreach { annotation => +// assert( +// annotation.nonEmpty, +// s"Expected non-empty result for image: $imagePath, but got empty result") +// } +// case None => +// fail(s"'answer' key not found in annotateMap for image: $imagePath") +// } +// } +// } +// +// } + + private def getBLIPForQuestionAnsweringPipelineModel = { + val testDF = getTestDF + + val imageAssembler: ImageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + + val loadModel = Phi3Vision + .loadSavedModel( + "/mnt/research/Projects/ModelZoo/Phi-3.5-vision/model/INT4", + ResourceHelper.spark) + .setInputCols("image_assembler") + .setOutputCol("answer") +// .setSize(384) + + val newPipeline: Pipeline = + new Pipeline().setStages(Array(imageAssembler, loadModel)) + + newPipeline.fit(testDF) + } + + private def getTestDF: DataFrame = { + val imageFolder = "src/test/resources/image/" + val imageDF: DataFrame = ResourceHelper.spark.read + .format("image") + .option("dropInvalid", value = true) + .load(imageFolder) + + val testDF: DataFrame = imageDF.withColumn("text", lit("What's this picture about?")) + + testDF + } + +} From c047188aa7dea6db2ef314786e8faf8ef1793f29 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 29 Oct 2024 01:21:59 +0000 Subject: [PATCH 036/108] Added tests Signed-off-by: Prabod Rathnayaka --- .../nlp/annotators/cv/Phi3Vision.scala | 68 +++-- .../cv/util/transform/Phi3vUtils.scala | 1 - .../annotators/cv/Phi3VisionTestSpec.scala | 233 +++++++++--------- 3 files changed, 161 insertions(+), 141 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala index f57c90662e23de..a5d688eb4c5302 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala @@ -25,35 +25,31 @@ import com.johnsnowlabs.ml.util.LoadExternalModel.{ modelSanityCheck, notSupportedEngineError } -import com.johnsnowlabs.ml.util.{Openvino} +import com.johnsnowlabs.ml.util.Openvino import com.johnsnowlabs.nlp.AnnotatorType.{DOCUMENT, IMAGE} import com.johnsnowlabs.nlp._ -import com.johnsnowlabs.nlp.annotators.RegexTokenizer -import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor -import com.johnsnowlabs.nlp.annotators.sbd.pragmatic.SentenceDetector import org.json4s.{DefaultFormats, JValue} import org.json4s.jackson.JsonMethods.parse -//import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{BertTokenizer, SpecialTokens} import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.Phi3VWrappers import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} import org.apache.spark.broadcast.Broadcast -import org.apache.spark.ml.param.{IntArrayParam, IntParam} +import org.apache.spark.ml.param.IntArrayParam import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.SparkSession -/** Phi3Vision can load BLIP models for visual question answering. The model consists of a vision - * encoder, a text encoder as well as a text decoder. The vision encoder will encode the input - * image, the text encoder will encode the input question together with the encoding of the +/** Phi3Vision can load Phi3 Vision models for visual question answering. The model consists of a + * vision encoder, a text encoder as well as a text decoder. The vision encoder will encode the + * input image, the text encoder will encode the input question together with the encoding of the * image, and the text decoder will output the answer to the question. * * Pretrained models can be loaded with `pretrained` of the companion object: * {{{ - * val visualQAClassifier = Phi3Vision.pretrained() + * val visualQA = Phi3Vision.pretrained() * .setInputCols("image_assembler") * .setOutputCol("answer") * }}} - * The default model is `"blip_vqa_base"`, if no name is provided. + * The default model is `"phi3v"`, if no name is provided. * * For available pretrained models please see the * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. @@ -76,7 +72,7 @@ import org.apache.spark.sql.SparkSession * .option("dropInvalid", value = true) * .load(imageFolder) * - * val testDF: DataFrame = imageDF.withColumn("text", lit("What's this picture about?")) + * val testDF: DataFrame = imageDF.withColumn("text", lit("<|user|> \n <|image_1|> \nWhat is unusual on this picture? <|end|>\n <|assistant|>\n")) * * val imageAssembler: ImageAssembler = new ImageAssembler() * .setInputCol("image") @@ -97,7 +93,7 @@ import org.apache.spark.sql.SparkSession * +--------------------------------------+------+ * |origin |result| * +--------------------------------------+------+ - * |[file:///content/images/cat_image.jpg]|[cats]| + * |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]| * +--------------------------------------+------+ * }}} * @@ -272,9 +268,9 @@ class Phi3Vision(override val uid: String) batchedAnnotations: Seq[Array[AnnotationImage]]): Seq[Seq[Annotation]] = { batchedAnnotations - .filter { annotationImages => - annotationImages.exists(_.text.nonEmpty) - } +// .filter { annotationImages => +// annotationImages.exists(_.text.nonEmpty) +// } .map { cleanAnnotationImages => val validImages = cleanAnnotationImages.filter(_.result.nonEmpty) val questionAnnotations = extractInputAnnotation(validImages) @@ -300,20 +296,42 @@ class Phi3Vision(override val uid: String) private def extractInputAnnotation( annotationImages: Array[AnnotationImage]): Seq[Annotation] = { - val questions = annotationImages.map(annotationImage => Annotation(annotationImage.text)) + val questions = annotationImages.map(annotationImage => { + val imageText = + if (annotationImage.text.nonEmpty) annotationImage.text + else + "<|user|> \n <|image_1|> This is an image\n <|end|>\n <|assistant|>\n" // default question + Annotation(imageText) + }) questions } override def onWrite(path: String, spark: SparkSession): Unit = { super.onWrite(path, spark) -// writeTensorflowModelV2( -// path, -// spark, -// getModelIfNotSet.tensorflowWrapper, -// "_image_qa", -// Phi3Vision.tfFile, -// configProtoBytes = getConfigProtoBytes) + getEngine match { + case Openvino.name => + val wrappers = getModelIfNotSet.openvinoWrapper + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.reshape, "reshape_model.xml")), + Phi3Vision.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.wte, "wte_model.xml")), + Phi3Vision.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.languageModel, "language_model.xml")), + Phi3Vision.suffix) + case _ => + throw new Exception(notSupportedEngineError) + } } } @@ -322,7 +340,7 @@ trait ReadablePretrainedPhi3Vision extends ParamsAndFeaturesReadable[Phi3Vision] with HasPretrained[Phi3Vision] { - override val defaultModelName: Some[String] = Some("blip_vqa_base") + override val defaultModelName: Some[String] = Some("phi3v") /** Java compliant-overrides */ override def pretrained(): Phi3Vision = super.pretrained() diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala index 49c709ba1d09f3..4f1afac53d8119 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Phi3vUtils.scala @@ -189,7 +189,6 @@ private[johnsnowlabs] object Phi3vUtils { def concatenateImages( globalImage: BufferedImage, localImages: List[BufferedImage]): BufferedImage = { - println(localImages.size) val totalWidth = 336 * localImages.size + 336 val totalHeight = 336 val concatenatedImage = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_INT_RGB) diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala index ff2c0de794f703..08eb4e78d920f7 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala @@ -19,7 +19,7 @@ package com.johnsnowlabs.nlp.annotators.cv import com.johnsnowlabs.nlp.base.LightPipeline import com.johnsnowlabs.nlp.util.io.ResourceHelper import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, ImageAssembler} -import com.johnsnowlabs.tags.FastTest +import com.johnsnowlabs.tags.{FastTest, SlowTest} import org.apache.spark.ml.Pipeline import org.apache.spark.sql.DataFrame import org.apache.spark.sql.functions.lit @@ -29,124 +29,128 @@ class Phi3VisionTestSpec extends AnyFlatSpec { lazy val model = getBLIPForQuestionAnsweringPipelineModel -// "BLIP" should "answer a question for a given image" taggedAs FastTest in { -// -// val testDF = getTestDF -// val result = model.transform(testDF) -// -// val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") -// -// answerAnnotation.foreach { annotation => -// annotation.foreach(a => assert(a.result.nonEmpty)) -// } -// -// answerAnnotation.foreach { annotation => -// annotation.foreach(a => println(a.result)) -// } -// -// } - - it should "work with light pipeline annotate" taggedAs FastTest in { + "BLIP" should "answer a question for a given image" taggedAs SlowTest in { + + val testDF = getTestDF + val result = model.transform(testDF) + + val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") + + answerAnnotation.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + + answerAnnotation.foreach { annotation => + annotation.foreach(a => println(a.result)) + } + + } + + it should "work with light pipeline annotate" taggedAs SlowTest in { val lightPipeline = new LightPipeline(model) val imagePath = "src/test/resources/image/egyptian_cat.jpeg" val resultAnnotate = lightPipeline.annotate( imagePath, - "<|user|> \n <|image_1|> \nWhat is unusual on this picture? <|end|>\n <|assistant|>\n") + "<|user|> \n <|image_1|> \n What is unusual on this picture? <|end|>\n <|assistant|>\n") println(s"resultAnnotate: $resultAnnotate") assert(resultAnnotate("answer").head.contains("cat")) } -// -// it should "work with light pipeline full annotate" taggedAs SlowTest in { -// val lightPipeline = new LightPipeline(model) -// val imagePath = "src/test/resources/image/bluetick.jpg" -// val resultFullAnnotate = -// lightPipeline.fullAnnotateImage(imagePath, "What's this picture about?") -// -// val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] -// -// println(s"imageName.result: ${answerAnnotation.result}") -// assert(answerAnnotation.result.nonEmpty) -// } - -// it should "fullAnnotate with empty Map when a text is empty" taggedAs SlowTest in { -// val lightPipeline = new LightPipeline(model) -// val imagesPath = Array( -// "src/test/resources/image/bluetick.jpg", -// "src/test/resources/image/chihuahua.jpg", -// "src/test/resources/image/egyptian_cat.jpeg") -// val question = "What's this picture about?" -// val questions = Array(question, "", question) -// -// val resultFullAnnotate = lightPipeline.fullAnnotateImages(imagesPath, questions) -// -// resultFullAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => -// imagePath match { -// case "src/test/resources/image/chihuahua.jpg" => -// // For the chihuahua image, the annotateMap should be empty because the question is empty -// assert( -// annotateMap.isEmpty, -// s"Expected empty map for image: $imagePath, but got: $annotateMap") -// -// case _ => -// assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") -// -// annotateMap.get("answer") match { -// case Some(annotations) => -// annotations.foreach { iAnnotation => -// val annotation = iAnnotation.asInstanceOf[Annotation] -// assert( -// annotation.result.nonEmpty, -// s"Expected non-empty result for image: $imagePath, but got empty result") -// } -// case None => -// fail(s"'answer' key not found in annotateMap for image: $imagePath") -// } -// } -// } -// } - -// it should "annotate with empty Map when a text is empty" taggedAs SlowTest in { -// val lightPipeline = new LightPipeline(model) -// val imagesPath = Array( -// "src/test/resources/image/bluetick.jpg", -// "src/test/resources/image/chihuahua.jpg", -// "src/test/resources/image/egyptian_cat.jpeg") -// val question = "What's this picture about?" -// val questions = Array(question, "", question) -// -// val resultAnnotate = lightPipeline.annotate(imagesPath, questions) -// -// resultAnnotate.foreach { annotate => -// println(s"annotate: $annotate") -// } -// -// resultAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => -// imagePath match { -// case "src/test/resources/image/chihuahua.jpg" => -// // For the chihuahua image, the annotateMap should be empty because the question is empty -// assert( -// annotateMap.isEmpty, -// s"Expected empty map for image: $imagePath, but got: $annotateMap") -// -// case _ => -// assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") -// -// annotateMap.get("answer") match { -// case Some(annotations) => -// annotations.foreach { annotation => -// assert( -// annotation.nonEmpty, -// s"Expected non-empty result for image: $imagePath, but got empty result") -// } -// case None => -// fail(s"'answer' key not found in annotateMap for image: $imagePath") -// } -// } -// } -// -// } + + it should "work with light pipeline full annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/image/bluetick.jpg" + val resultFullAnnotate = + lightPipeline.fullAnnotateImage( + imagePath, + "<|user|> \n <|image_1|> \n What's this picture about? <|end|>\n <|assistant|>\n") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + println(s"imageName.result: ${answerAnnotation.result}") + assert(answerAnnotation.result.nonEmpty) + } + + it should "fullAnnotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "<|user|> \n <|image_1|> \n What's this picture about? <|end|>\n <|assistant|>\n" + val questions = Array(question, "", question) + + val resultFullAnnotate = lightPipeline.fullAnnotateImages(imagesPath, questions) + + resultFullAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { iAnnotation => + val annotation = iAnnotation.asInstanceOf[Annotation] + assert( + annotation.result.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + } + + it should "annotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "<|user|> \n <|image_1|> \n What's this picture about? <|end|>\n <|assistant|>\n" + val questions = Array(question, "", question) + + val resultAnnotate = lightPipeline.annotate(imagesPath, questions) + + resultAnnotate.foreach { annotate => + println(s"annotate: $annotate") + } + + resultAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { annotation => + assert( + annotation.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + + } private def getBLIPForQuestionAnsweringPipelineModel = { val testDF = getTestDF @@ -156,12 +160,9 @@ class Phi3VisionTestSpec extends AnyFlatSpec { .setOutputCol("image_assembler") val loadModel = Phi3Vision - .loadSavedModel( - "/mnt/research/Projects/ModelZoo/Phi-3.5-vision/model/INT4", - ResourceHelper.spark) + .pretrained() .setInputCols("image_assembler") .setOutputCol("answer") -// .setSize(384) val newPipeline: Pipeline = new Pipeline().setStages(Array(imageAssembler, loadModel)) @@ -176,7 +177,9 @@ class Phi3VisionTestSpec extends AnyFlatSpec { .option("dropInvalid", value = true) .load(imageFolder) - val testDF: DataFrame = imageDF.withColumn("text", lit("What's this picture about?")) + val testDF: DataFrame = imageDF.withColumn( + "text", + lit("<|user|> \n <|image_1|> \n What's this picture about? <|end|>\n <|assistant|>\n")) testDF } From 59e596c6fbad899181cc4ecc4c69b1cd54ec24d6 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 29 Oct 2024 04:02:32 +0000 Subject: [PATCH 037/108] Phi3V python api and tests Signed-off-by: Prabod Rathnayaka --- python/sparknlp/annotator/cv/__init__.py | 2 +- .../cv/phi3_vision_for_multimodal.py | 329 ++++++++++++++++++ python/sparknlp/internal/__init__.py | 9 + .../cv/phi3_vision_for_multimodal_test.py | 80 +++++ 4 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py create mode 100644 python/test/annotator/cv/phi3_vision_for_multimodal_test.py diff --git a/python/sparknlp/annotator/cv/__init__.py b/python/sparknlp/annotator/cv/__init__.py index 37eeaf696bb2a8..34d555f04e799f 100644 --- a/python/sparknlp/annotator/cv/__init__.py +++ b/python/sparknlp/annotator/cv/__init__.py @@ -16,4 +16,4 @@ from sparknlp.annotator.cv.convnext_for_image_classification import * from sparknlp.annotator.cv.vision_encoder_decoder_for_image_captioning import * from sparknlp.annotator.cv.clip_for_zero_shot_classification import * -from sparknlp.annotator.cv.blip_for_question_answering import * \ No newline at end of file +from sparknlp.annotator.cv.phi3_vision_for_multimodal import * diff --git a/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py b/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py new file mode 100644 index 00000000000000..bb57e0a94f3985 --- /dev/null +++ b/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py @@ -0,0 +1,329 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class Phi3Vision(AnnotatorModel, + HasBatchedAnnotateImage, + HasImageFeatureProperties, + HasEngine, + HasCandidateLabelsProperties, + HasRescaleFactor): + """BLIPForQuestionAnswering can load BLIP models for visual question answering. + The model consists of a vision encoder, a text encoder as well as a text decoder. + The vision encoder will encode the input image, the text encoder will encode the input question together + with the encoding of the image, and the text decoder will output the answer to the question. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion + object: + + >>> visualQAClassifier = BLIPForQuestionAnswering.pretrained() \\ + ... .setInputCols(["image_assembler"]) \\ + ... .setOutputCol("answer") + + The default model is ``"blip_vqa_base"``, if no name is + provided. + + For available pretrained models please see the `Models Hub + `__. + + To see which models are compatible and how to import them see + `Import Transformers into Spark NLP ๐Ÿš€ + `_. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``IMAGE`` ``DOCUMENT`` + ====================== ====================== + + Parameters + ---------- + batchSize + Batch size. Large values allows faster processing but requires more + memory, by default 2 + configProtoBytes + ConfigProto from tensorflow, serialized into byte array. + maxSentenceLength + Max sentence length to process, by default 50 + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> image_df = SparkSessionForTest.spark.read.format("image").load(path=images_path) + >>> test_df = image_df.withColumn("text", lit("What's this picture about?")) + >>> imageAssembler = ImageAssembler() \\ + ... .setInputCol("image") \\ + ... .setOutputCol("image_assembler") + >>> visualQAClassifier = BLIPForQuestionAnswering.pretrained() \\ + ... .setInputCols("image_assembler") \\ + ... .setOutputCol("answer") \\ + ... .setSize(384) + >>> pipeline = Pipeline().setStages([ + ... imageAssembler, + ... visualQAClassifier + ... ]) + >>> result = pipeline.fit(test_df).transform(test_df) + >>> result.select("image_assembler.origin", "answer.result").show(false) + +--------------------------------------+------+ + |origin |result| + +--------------------------------------+------+ + |[file:///content/images/cat_image.jpg]|[cats]| + +--------------------------------------+------+ + """ + + name = "Phi3Vision" + + inputAnnotatorTypes = [AnnotatorType.IMAGE] + + outputAnnotatorType = AnnotatorType.DOCUMENT + + configProtoBytes = Param(Params._dummy(), + "configProtoBytes", + "ConfigProto from tensorflow, serialized into byte array. Get with " + "config_proto.SerializeToString()", + TypeConverters.toListInt) + + minOutputLength = Param(Params._dummy(), "minOutputLength", "Minimum length of the sequence to be generated", + typeConverter=TypeConverters.toInt) + + maxOutputLength = Param(Params._dummy(), "maxOutputLength", "Maximum length of output text", + typeConverter=TypeConverters.toInt) + + doSample = Param(Params._dummy(), "doSample", "Whether or not to use sampling; use greedy decoding otherwise", + typeConverter=TypeConverters.toBoolean) + + temperature = Param(Params._dummy(), "temperature", "The value used to module the next token probabilities", + typeConverter=TypeConverters.toFloat) + + topK = Param(Params._dummy(), "topK", + "The number of highest probability vocabulary tokens to keep for top-k-filtering", + typeConverter=TypeConverters.toInt) + + topP = Param(Params._dummy(), "topP", + "If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or higher are kept for generation", + typeConverter=TypeConverters.toFloat) + + repetitionPenalty = Param(Params._dummy(), "repetitionPenalty", + "The parameter for repetition penalty. 1.0 means no penalty. See `this paper `__ for more details", + typeConverter=TypeConverters.toFloat) + + noRepeatNgramSize = Param(Params._dummy(), "noRepeatNgramSize", + "If set to int > 0, all ngrams of that size can only occur once", + typeConverter=TypeConverters.toInt) + + ignoreTokenIds = Param(Params._dummy(), "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output", + typeConverter=TypeConverters.toListInt) + beamSize = Param(Params._dummy(), "beamSize", + "The Number of beams for beam search.", + typeConverter=TypeConverters.toInt) + + def setMaxSentenceSize(self, value): + """Sets Maximum sentence length that the annotator will process, by + default 50. + + Parameters + ---------- + value : int + Maximum sentence length that the annotator will process + """ + return self._set(maxSentenceLength=value) + + def setIgnoreTokenIds(self, value): + """A list of token ids which are ignored in the decoder's output. + + Parameters + ---------- + value : List[int] + The words to be filtered out + """ + return self._set(ignoreTokenIds=value) + + def setConfigProtoBytes(self, b): + """Sets configProto from tensorflow, serialized into byte array. + + Parameters + ---------- + b : List[int] + ConfigProto from tensorflow, serialized into byte array + """ + return self._set(configProtoBytes=b) + + def setMinOutputLength(self, value): + """Sets minimum length of the sequence to be generated. + + Parameters + ---------- + value : int + Minimum length of the sequence to be generated + """ + return self._set(minOutputLength=value) + + def setMaxOutputLength(self, value): + """Sets maximum length of output text. + + Parameters + ---------- + value : int + Maximum length of output text + """ + return self._set(maxOutputLength=value) + + def setDoSample(self, value): + """Sets whether or not to use sampling, use greedy decoding otherwise. + + Parameters + ---------- + value : bool + Whether or not to use sampling; use greedy decoding otherwise + """ + return self._set(doSample=value) + + def setTemperature(self, value): + """Sets the value used to module the next token probabilities. + + Parameters + ---------- + value : float + The value used to module the next token probabilities + """ + return self._set(temperature=value) + + def setTopK(self, value): + """Sets the number of highest probability vocabulary tokens to keep for + top-k-filtering. + + Parameters + ---------- + value : int + Number of highest probability vocabulary tokens to keep + """ + return self._set(topK=value) + + def setTopP(self, value): + """Sets the top cumulative probability for vocabulary tokens. + + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + + Parameters + ---------- + value : float + Cumulative probability for vocabulary tokens + """ + return self._set(topP=value) + + def setRepetitionPenalty(self, value): + """Sets the parameter for repetition penalty. 1.0 means no penalty. + + Parameters + ---------- + value : float + The repetition penalty + + References + ---------- + See `Ctrl: A Conditional Transformer Language Model For Controllable + Generation `__ for more details. + """ + return self._set(repetitionPenalty=value) + + def setNoRepeatNgramSize(self, value): + """Sets size of n-grams that can only occur once. + + If set to int > 0, all ngrams of that size can only occur once. + + Parameters + ---------- + value : int + N-gram size can only occur once + """ + return self._set(noRepeatNgramSize=value) + + def setBeamSize(self, value): + """Sets the number of beam size for beam search, by default `4`. + + Parameters + ---------- + value : int + Number of beam size for beam search + """ + return self._set(beamSize=value) + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cv.Phi3Vision", + java_model=None): + super(Phi3Vision, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=2, + minOutputLength=0, + maxOutputLength=200, + doSample=False, + temperature=1, + topK=50, + topP=1, + repetitionPenalty=1.0, + noRepeatNgramSize=0, + ignoreTokenIds=[], + beamSize=1, + ) + + @staticmethod + def loadSavedModel(folder, spark_session, use_openvino=False): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.internal import _Phi3VisionLoader + jModel = _Phi3VisionLoader(folder, spark_session._jsparkSession, use_openvino)._java_obj + return Phi3Vision(java_model=jModel) + + @staticmethod + def pretrained(name="phi3v", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "blip_vqa_tf" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(Phi3Vision, name, lang, remote_loc) \ No newline at end of file diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..a5a7bbc9ea71f6 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -363,6 +363,15 @@ def __init__(self, path, jspark, use_openvino=False): use_openvino, ) +class _Phi3VisionLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark, use_openvino=False): + super(_Phi3VisionLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.cv.Phi3Vision.loadSavedModel", + path, + jspark, + use_openvino + ) + class _RoBertaLoader(ExtendedJavaWrapper): def __init__(self, path, jspark, use_openvino=False): super(_RoBertaLoader, self).__init__( diff --git a/python/test/annotator/cv/phi3_vision_for_multimodal_test.py b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py new file mode 100644 index 00000000000000..1d2942e3246977 --- /dev/null +++ b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py @@ -0,0 +1,80 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +import pytest +import os + +from sparknlp.annotator import * +from sparknlp.base import * +from pyspark.sql.functions import lit +from test.util import SparkSessionForTest,SparkContextForTest + + +class Phi3VisionTestSetup(unittest.TestCase): + + def setUp(self): + self.images_path = os.getcwd() + "/../src/test/resources/image/" + self.spark = SparkContextForTest.spark + image_df = SparkSessionForTest.spark.read.format("image").load( + path=self.images_path + ) + + self.test_df = image_df.withColumn("text", lit("<|user|> \n <|image_1|> \n What's this picture about? <|end|>\n <|assistant|>\n")) + + image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") + + imageClassifier = Phi3Vision.loadSavedModel("/mnt/research/Projects/ModelZoo/Phi-3.5-vision/model/INT4", self.spark) \ + .setInputCols("image_assembler") \ + .setOutputCol("answer") + + self.pipeline = Pipeline( + stages=[ + image_assembler, + imageClassifier, + ] + ) + + self.model = self.pipeline.fit(self.test_df) + +@pytest.mark.slow +class Phi3VisionTest(Phi3VisionTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + result = self.model.transform(self.test_df).collect() + + for row in result: + self.assertTrue(row["answer"] != "") + + +@pytest.mark.slow +class LightPhi3VisionTest(Phi3VisionTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.model) + image_path = self.images_path + "bluetick.jpg" + print("image_path: " + image_path) + annotations_result = light_pipeline.fullAnnotateImage( + image_path, + "<|user|> \n <|image_1|> \n What's this picture about? <|end|>\n <|assistant|>\n" + ) + print(annotations_result) + for result in annotations_result: + self.assertTrue(len(result["image_assembler"]) > 0) + self.assertTrue(len(result["answer"]) > 0) \ No newline at end of file From d0ad585bd6c048df606f95c98322a5cbe9ba02f4 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 29 Oct 2024 09:36:05 +0000 Subject: [PATCH 038/108] added byte fallback Signed-off-by: Prabod Rathnayaka --- .../tokenizer/bpe/Phi3VisionTokenizer.scala | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala index dc44585d894da1..9a2318dcd88b16 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Phi3VisionTokenizer.scala @@ -21,6 +21,7 @@ import com.johnsnowlabs.nlp.annotators.common.IndexedToken import java.nio.charset.Charset import scala.collection.mutable.ListBuffer import scala.util.matching.Regex +import scala.collection.mutable class Phi3VisionTokenizer( merges: Map[(String, String), Int], @@ -89,24 +90,22 @@ class Phi3VisionTokenizer( } def decodeTokens(tokens: Array[Int]): String = { - var text = tokens - .map(token => decoderVocab(token)) - .filter(x => !specialTokens.contains(x)) - .mkString("") - - text = text.replaceAll("โ–", " ").trim() - - text = - try { - val bytes = - text.map(x => unicodeToByteMapping(x.toString)).map(x => x.toByte).toArray - new String(bytes, Charset.forName("UTF-8")) - } catch { - case e: Exception => - {} - // Do nothing, just return the text - text + val decoded = new mutable.StringBuilder() + tokens.foreach { token => + { + val decodedToken = decoderVocab(token) + if (!specialTokens.contains(decodedToken)) { + if (decodedToken.startsWith("<0x") && decodedToken.endsWith(">")) { + val strippedHex = decodedToken.replaceAll("<0x|>", "") + val byteValue = Integer.parseInt(strippedHex, 16) + decoded.append(byteValue.toChar) + } else { + decoded.append(decodedToken) + } + } } - text + + } + decoded.toString().replaceAll(decoderVocab(29871), " ").trim() } } From c12713d9bfa512871c83d0815645cdb00d96dbe5 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 29 Oct 2024 09:37:31 +0000 Subject: [PATCH 039/108] changed to pretrained Signed-off-by: Prabod Rathnayaka --- python/test/annotator/cv/phi3_vision_for_multimodal_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/test/annotator/cv/phi3_vision_for_multimodal_test.py b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py index 1d2942e3246977..064e94d02716ed 100644 --- a/python/test/annotator/cv/phi3_vision_for_multimodal_test.py +++ b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py @@ -34,7 +34,7 @@ def setUp(self): image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") - imageClassifier = Phi3Vision.loadSavedModel("/mnt/research/Projects/ModelZoo/Phi-3.5-vision/model/INT4", self.spark) \ + imageClassifier = Phi3Vision.pretrained() \ .setInputCols("image_assembler") \ .setOutputCol("answer") From d12ae3deb0c10f9403832593cea57cb9a01f0a9e Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 30 Oct 2024 04:56:56 +0000 Subject: [PATCH 040/108] export notebook Signed-off-by: Prabod Rathnayaka --- ...ace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb | 1562 +++++++++++++++++ .../cv/phi3_vision_for_multimodal.py | 17 +- 2 files changed, 1570 insertions(+), 9 deletions(-) create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb new file mode 100644 index 00000000000000..8b614a45969f21 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb @@ -0,0 +1,1562 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import OpenVINO Phi3Vision models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and importing Phi3Vision models from HuggingFace for use in Spark NLP, with [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html). The focus is on converting the model to the OpenVINO format and applying precision optimizations (INT8 and INT4), to enhance the performance and efficiency on CPU platforms using [Optimum Intel](https://huggingface.co/docs/optimum/main/en/intel/inference).\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance CPU inference for models. So please make sure you have upgraded to the latest Spark NLP release.\n", + "- Model quantization is a computationally expensive process, so it is recommended to use a runtime with more than 32GB memory for exporting the quantized model from HuggingFace.\n", + "- You can import Phi3Vision models via `Phi3Vision`. These models are usually under `Text Generation` category and have `Phi3Vision` in their labels.\n", + "- Reference: [Phi3Vision](https://huggingface.co/docs/transformers/model_doc/llama#transformers.Phi3Vision)\n", + "- Some [example models](https://huggingface.co/models?search=Phi3Vision)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Export and Save the HuggingFace model\n", + "\n", + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future release, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -q --upgrade transformers==4.41.2\n", + "!pip install -q --upgrade openvino==2024.1\n", + "!pip install -q --upgrade optimum-intel\n", + "!pip install -q --upgrade nncf\n", + "!pip install -q --upgrade huggingface_hub\n", + "!pip install -q --upgrade onnx==1.15.0\n", + "!pip install -q --upgrade torch==2.2.1" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# taken from \n", + "\n", + "\n", + "from pathlib import Path\n", + "import types\n", + "from typing import Optional, Tuple, Union, List\n", + "import gc\n", + "import openvino as ov\n", + "from openvino.runtime import opset13\n", + "import nncf\n", + "import numpy as np\n", + "import torch\n", + "from transformers import AutoModelForCausalLM, AutoProcessor, AutoConfig\n", + "from transformers.generation import GenerationConfig, GenerationMixin\n", + "from transformers.modeling_outputs import CausalLMOutputWithPast, BaseModelOutputWithPast\n", + "\n", + "\n", + "def model_has_state(ov_model: ov.Model):\n", + " return len(ov_model.get_sinks()) > 0\n", + "\n", + "\n", + "def model_has_input_output_name(ov_model: ov.Model, name: str):\n", + " \"\"\"\n", + " Helper function for checking that model has specified input or output name\n", + "\n", + " Parameters:\n", + " ov_model (ov.Model):\n", + " name (str):\n", + " name of input or output\n", + "\n", + " Returns:\n", + " True if input or output with requested name exists else False\n", + " \"\"\"\n", + " return name in sum([list(t.get_names()) for t in ov_model.inputs + ov_model.outputs], [])\n", + "\n", + "\n", + "def fuse_cache_reorder(\n", + " ov_model: ov.Model,\n", + " not_kv_inputs: List[str],\n", + " key_value_input_names: List[str],\n", + " gather_dim: int,\n", + "):\n", + " \"\"\"\n", + " Fuses reored_cache during generate cycle into ov.Model. Used with stateful models, because we can not modify model state directly.\n", + "\n", + " Adds a new beam_idx parameter and Gather op per each kv-cache input in a given model.\n", + " Should be run before make_stateful. Implements optimumum's _reorder_cache\n", + " inside the model in the beginning of each iteration.\n", + " Gather works along given gather_dim dimension that may vary from model to model.\n", + " KV-cache inputs are identified based on names in key_value_input_names.\n", + " Append the new beam_idx parameter to not_kv_inputs.\n", + "\n", + " Parameters:\n", + " ov_model (`ov.Model`):\n", + " openvino model for processing\n", + " not_kv_inputs (`List[str]`):\n", + " list of input nodes in model that not related to past key values\n", + " key_value_input_names (`List[str]`):\n", + " list of names for key value input layers\n", + " gather_dim (int):\n", + " dimension for gathering cache during reorder pass\n", + " \"\"\"\n", + "\n", + " if model_has_input_output_name(ov_model, \"beam_idx\"):\n", + " raise ValueError(\"Model already has fused cache\")\n", + " input_batch = ov_model.input(\"inputs_embeds\").get_partial_shape()[0]\n", + " beam_idx = opset13.parameter(name=\"beam_idx\", dtype=ov.Type.i32, shape=ov.PartialShape([input_batch]))\n", + " beam_idx.output(0).get_tensor().add_names({\"beam_idx\"}) # why list is not accepted?\n", + " ov_model.add_parameters([beam_idx])\n", + " not_kv_inputs.append(ov_model.inputs[-1])\n", + " # Go over all cache parameters and fuse _reorder_cache with indices provided by the new parameter beam_idx\n", + " for input_name in key_value_input_names:\n", + " parameter_output_port = ov_model.input(input_name)\n", + " consumers = parameter_output_port.get_target_inputs()\n", + " gather = opset13.gather(parameter_output_port, beam_idx, opset13.constant(gather_dim))\n", + " for consumer in consumers:\n", + " consumer.replace_source_output(gather.output(0))\n", + " ov_model.validate_nodes_and_infer_types()\n", + "\n", + "\n", + "def build_state_initializer(ov_model: ov.Model, batch_dim: int):\n", + " \"\"\"\n", + " Build initialization ShapeOf Expression for all ReadValue ops\n", + "\n", + " Parameters:\n", + " ov_model (ov.Model):\n", + " openvino model\n", + " batch_dim (int):\n", + " index of dimension corresponding to batch size\n", + " \"\"\"\n", + " input_ids = ov_model.input(\"inputs_embeds\")\n", + " batch = opset13.gather(\n", + " opset13.shape_of(input_ids, output_type=\"i64\"),\n", + " opset13.constant([0]),\n", + " opset13.constant(0),\n", + " )\n", + " for op in ov_model.get_ops():\n", + " if op.get_type_name() == \"ReadValue\":\n", + " dims = [dim.min_length for dim in list(op.get_output_partial_shape(0))]\n", + " dims[batch_dim] = batch\n", + " dims = [(opset13.constant(np.array([dim], dtype=np.int64)) if isinstance(dim, int) else dim) for dim in dims]\n", + " shape = opset13.concat(dims, axis=0)\n", + " broadcast = opset13.broadcast(opset13.constant(0.0, dtype=op.get_output_element_type(0)), shape)\n", + " op.set_arguments([broadcast])\n", + " ov_model.validate_nodes_and_infer_types()\n", + "\n", + "\n", + "def make_stateful(\n", + " ov_model: ov.Model,\n", + " not_kv_inputs: List[str],\n", + " key_value_input_names: List[str],\n", + " key_value_output_names: List[str],\n", + " batch_dim: int,\n", + " num_attention_heads: int,\n", + " num_beams_and_batch: int = None,\n", + "):\n", + " \"\"\"\n", + " Hides kv-cache inputs and outputs inside the model as variables.\n", + "\n", + " Parameters:\n", + " ov_model (ov.Model):\n", + " openvino model\n", + " not_kv_inputs (`List[str]`):\n", + " list of input nodes in model that not related to past key values\n", + " key_value_input_names (`List[str]`):\n", + " list of names for key value input layers\n", + " key_value_output_names (`List[str]`):\n", + " list of names for key value input layers\n", + " batch_dim (int):\n", + " index of batch dimension in key value layers\n", + " num_attention_heads (int):\n", + " number of attention heads for batch dimension initialization\n", + " num_beams_an_batch (int):\n", + " precalculated number of beams and batch for shapes initialization\n", + " \"\"\"\n", + " from openvino._offline_transformations import apply_make_stateful_transformation\n", + "\n", + " input_output_map = {}\n", + "\n", + " if num_beams_and_batch is not None:\n", + " # Set batch size for input_ids and attention mask to avoid dynamic dimension got propagated from the end of the model back to ReadValue\n", + " for input in not_kv_inputs:\n", + " shape = input.get_partial_shape()\n", + " if shape.rank.get_length() <= 2: # == 1 for beam_index\n", + " shape[0] = num_beams_and_batch\n", + " input.get_node().set_partial_shape(shape)\n", + " for kv_name_pair in zip(key_value_input_names, key_value_output_names):\n", + " input_output_map[kv_name_pair[0]] = kv_name_pair[1]\n", + " if num_beams_and_batch is not None:\n", + " input = ov_model.input(kv_name_pair[0])\n", + " shape = input.get_partial_shape()\n", + " shape[batch_dim] = num_beams_and_batch * num_attention_heads\n", + " input.get_node().set_partial_shape(shape)\n", + "\n", + " if num_beams_and_batch is not None:\n", + " # Re-validation model if shapes are altered above\n", + " ov_model.validate_nodes_and_infer_types()\n", + "\n", + " apply_make_stateful_transformation(ov_model, input_output_map)\n", + " if num_beams_and_batch is None:\n", + " build_state_initializer(ov_model, batch_dim)\n", + "\n", + "\n", + "def patch_stateful(ov_model):\n", + " key_value_input_names = [key.get_any_name() for key in ov_model.inputs[2:-1]]\n", + " key_value_output_names = [key.get_any_name() for key in ov_model.outputs[1:]]\n", + " not_kv_inputs = [input for input in ov_model.inputs if not any(name in key_value_input_names for name in input.get_names())]\n", + " if not key_value_input_names or not key_value_output_names:\n", + " return\n", + " batch_dim = 0\n", + " num_attention_heads = 1\n", + "\n", + " fuse_cache_reorder(ov_model, not_kv_inputs, key_value_input_names, batch_dim)\n", + " make_stateful(\n", + " ov_model,\n", + " not_kv_inputs,\n", + " key_value_input_names,\n", + " key_value_output_names,\n", + " batch_dim,\n", + " num_attention_heads,\n", + " None,\n", + " )\n", + "\n", + "\n", + "core = ov.Core()\n", + "\n", + "\n", + "def cleanup_torchscript_cache():\n", + " \"\"\"\n", + " Helper for removing cached model representation\n", + " \"\"\"\n", + " torch._C._jit_clear_class_registry()\n", + " torch.jit._recursive.concrete_type_store = torch.jit._recursive.ConcreteTypeStore()\n", + " torch.jit._state._clear_class_state()\n", + "\n", + "\n", + "def convert_phi3_model(model_id, output_dir, quantization_config):\n", + " output_dir = Path(output_dir)\n", + "\n", + " lang_model_path = output_dir / \"language_model.xml\"\n", + " image_embed_path = output_dir / \"image_embed.xml\"\n", + " img_projection_path = output_dir / \"img_projection.xml\"\n", + " embed_token_path = output_dir / \"embed_token.xml\"\n", + " embed_token_path_2 = output_dir / \"wte_model.xml\"\n", + "\n", + " if all(\n", + " [\n", + " lang_model_path.exists(),\n", + " image_embed_path.exists(),\n", + " img_projection_path.exists(),\n", + " embed_token_path.exists(),\n", + " embed_token_path_2.exists(),\n", + " ]\n", + " ):\n", + " print(f\"โœ… Phi-3-vision model already converted. You can find results in {output_dir}\")\n", + " return\n", + " print(\"โŒ› Phi-3-vision conversion started. Be patient, it may takes some time.\")\n", + " print(\"โŒ› Load Original model\")\n", + " model = AutoModelForCausalLM.from_pretrained(model_id, trust_remote_code=True, _attn_implementation=\"eager\")\n", + " processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True)\n", + " model.config.save_pretrained(output_dir)\n", + " processor.save_pretrained(output_dir)\n", + " print(\"โœ… Original model successfully loaded\")\n", + "\n", + " if not embed_token_path_2.exists():\n", + " print(\"โŒ› Convert Input embedding model\")\n", + " ov_model = ov.convert_model(\n", + " model.model.embed_tokens,\n", + " example_input=torch.ones([2, 2], dtype=torch.int64),\n", + " )\n", + " ov.save_model(ov_model, embed_token_path)\n", + " ov.save_model(ov_model, embed_token_path_2)\n", + " del ov_model\n", + " cleanup_torchscript_cache()\n", + " gc.collect()\n", + " print(\"โœ… Input embedding model successfully converted\")\n", + "\n", + " vision_embed_tokens = model.model.vision_embed_tokens\n", + " if not image_embed_path.exists():\n", + " print(\"โŒ› Convert Image embedding model\")\n", + " vision_embed_tokens.forward = vision_embed_tokens.get_img_features\n", + " ov_model = ov.convert_model(vision_embed_tokens, example_input=torch.ones([17, 3, 336, 336]))\n", + " ov.save_model(ov_model, image_embed_path)\n", + " del ov_model\n", + " cleanup_torchscript_cache()\n", + " gc.collect()\n", + " print(\"โœ… Image embedding model successfully converted\")\n", + "\n", + " if not img_projection_path.exists():\n", + " print(\"โŒ› Convert Image projection model\")\n", + " ov_model = ov.convert_model(\n", + " vision_embed_tokens.img_projection,\n", + " example_input=torch.ones([1, 1921, 4096]),\n", + " )\n", + " ov.save_model(ov_model, img_projection_path)\n", + " del ov_model\n", + " cleanup_torchscript_cache()\n", + " gc.collect()\n", + " print(\"โœ… Image projection model successfully converted\")\n", + "\n", + " if not lang_model_path.exists():\n", + " print(\"โŒ› Convert Language model\")\n", + "\n", + " def forward_wrap(\n", + " self,\n", + " attention_mask,\n", + " position_ids=None,\n", + " past_key_values=None,\n", + " inputs_embeds=None,\n", + " ):\n", + " result = self._orig_forward(\n", + " input_ids=None,\n", + " attention_mask=attention_mask,\n", + " position_ids=position_ids,\n", + " past_key_values=past_key_values,\n", + " inputs_embeds=inputs_embeds,\n", + " )\n", + " return tuple(result.values())\n", + "\n", + " model._orig_forward = model.forward\n", + " model.forward = types.MethodType(forward_wrap, model)\n", + " llm_input = torch.zeros([2, 2, 3072])\n", + " pkv = model(\n", + " inputs_embeds=llm_input,\n", + " attention_mask=torch.ones((2, 2), dtype=torch.int64),\n", + " )[1]\n", + " model_inputs = [\"attention_mask\", \"position_ids\"]\n", + " model_outputs = [\"logits\"]\n", + " for idx in range(len(pkv)):\n", + " model_inputs.extend([f\"past_key_values.{idx}.key\", f\"past_key_values.{idx}.value\"])\n", + " model_outputs.extend([f\"present.{idx}.key\", f\"present.{idx}.value\"])\n", + " model_inputs.append(\"inputs_embeds\")\n", + " position_ids = torch.tensor([[2, 3], [2, 3]])\n", + " ov_model = ov.convert_model(\n", + " model,\n", + " example_input={\n", + " \"inputs_embeds\": llm_input,\n", + " \"attention_mask\": torch.ones([2, 4], dtype=torch.int64),\n", + " \"past_key_values\": pkv,\n", + " \"position_ids\": position_ids,\n", + " },\n", + " )\n", + "\n", + " for input, input_name in zip(ov_model.inputs, model_inputs):\n", + " input.get_tensor().set_names({input_name})\n", + "\n", + " for output, output_name in zip(ov_model.outputs, model_outputs):\n", + " output.get_tensor().set_names({output_name})\n", + " patch_stateful(ov_model)\n", + " print(\"โœ… Language model successfully converted\")\n", + "\n", + " if quantization_config is not None:\n", + " print(f\"โŒ› Weights compression with {quantization_config['mode']} mode started\")\n", + " ov_model = nncf.compress_weights(ov_model, **quantization_config)\n", + " print(\"โœ… Weights compression finished\")\n", + "\n", + " ov.save_model(ov_model, lang_model_path)\n", + " del ov_model\n", + " cleanup_torchscript_cache()\n", + " del model\n", + " gc.collect()\n", + " print(f\"โœ… Phi-3-vision model conversion finished. You can find results in {output_dir}\")\n", + "\n", + "\n", + "class OvPhi3Vision(GenerationMixin):\n", + " def __init__(self, model_dir, device):\n", + " model_dir = Path(model_dir)\n", + " self.model = core.read_model(model_dir / \"language_model.xml\")\n", + " self.image_embed = core.compile_model(model_dir / \"image_embed.xml\", device)\n", + " self.img_projection = core.compile_model(model_dir / \"img_projection.xml\", device)\n", + " self.embed_tokem = core.compile_model(model_dir / \"embed_token.xml\", device)\n", + " self.input_names = {key.get_any_name(): idx for idx, key in enumerate(self.model.inputs)}\n", + " self.output_names = {key.get_any_name(): idx for idx, key in enumerate(self.model.outputs)}\n", + " compiled_model = core.compile_model(self.model, device)\n", + " self.request = compiled_model.create_infer_request()\n", + " self.config = AutoConfig.from_pretrained(model_dir, trust_remote_code=True)\n", + " self.generation_config = GenerationConfig.from_model_config(self.config)\n", + " self.main_input_name = \"input_ids\"\n", + " self.device = torch.device(\"cpu\")\n", + " self.num_pkv = 2\n", + " self._supports_cache_class = False\n", + " self.next_beam_idx = None\n", + " self._past_length = None\n", + " self.hd_transform_order = \"glb_sub\"\n", + " self.num_img_tokens = self.config.img_processor[\"num_img_tokens\"]\n", + " self.image_dim_out = self.config.img_processor[\"image_dim_out\"]\n", + " self.glb_GN = torch.zeros([1, 1, self.image_dim_out * 4])\n", + " self.sub_GN = torch.zeros([1, 1, 1, self.image_dim_out * 4])\n", + "\n", + " def can_generate(self):\n", + " \"\"\"Returns True to validate the check that the model using `GenerationMixin.generate()` can indeed generate.\"\"\"\n", + " return True\n", + "\n", + " def __call__(\n", + " self,\n", + " input_ids: torch.LongTensor,\n", + " pixel_values: torch.Tensor,\n", + " attention_mask: Optional[torch.LongTensor] = None,\n", + " past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None,\n", + " position_ids: Optional[torch.LongTensor] = None,\n", + " image_sizes=None,\n", + " **kwargs,\n", + " ) -> CausalLMOutputWithPast:\n", + " return self.forward(\n", + " input_ids=input_ids,\n", + " pixel_values=pixel_values,\n", + " attention_mask=attention_mask,\n", + " past_key_values=past_key_values,\n", + " position_ids=position_ids,\n", + " image_sizes=image_sizes,\n", + " **kwargs,\n", + " )\n", + "\n", + " def forward(\n", + " self,\n", + " input_ids: torch.LongTensor = None,\n", + " attention_mask: Optional[torch.Tensor] = None,\n", + " position_ids: Optional[torch.LongTensor] = None,\n", + " past_key_values: Optional[List[torch.FloatTensor]] = None,\n", + " inputs_embeds: Optional[torch.FloatTensor] = None,\n", + " pixel_values: Optional[torch.FloatTensor] = None,\n", + " image_sizes: Optional[torch.LongTensor] = None,\n", + " **kwargs,\n", + " ) -> Union[Tuple, BaseModelOutputWithPast]:\n", + " if inputs_embeds is None:\n", + " if pixel_values is not None and image_sizes is not None:\n", + " inputs_embeds = self.vision_embed_tokens(input_ids, pixel_values=pixel_values, image_sizes=image_sizes)\n", + " else:\n", + " inputs_embeds = self.embed_token(input_ids)[0]\n", + " if past_key_values is None:\n", + " self.request.reset_state()\n", + " self.next_beam_idx = np.arange(inputs_embeds.shape[0], dtype=int)\n", + " self._past_length = 0\n", + " inputs = {}\n", + " inputs[\"inputs_embeds\"] = inputs_embeds\n", + " inputs[\"attention_mask\"] = attention_mask\n", + " inputs[\"position_ids\"] = position_ids\n", + " if \"beam_idx\" in self.input_names:\n", + " inputs[\"beam_idx\"] = self.next_beam_idx if self.next_beam_idx is not None else np.arange(inputs_embeds.shape[0], dtype=int)\n", + " self.request.start_async(inputs, share_inputs=True)\n", + " self.request.wait()\n", + " logits = self.request.get_tensor(\"logits\").data\n", + " logits = torch.from_numpy(logits).to(self.device)\n", + " past_key_values = ((),)\n", + " self._past_length += inputs[\"inputs_embeds\"].shape[1]\n", + "\n", + " return CausalLMOutputWithPast(logits=logits, past_key_values=past_key_values)\n", + "\n", + " def _reorder_cache(self, past_key_values: Tuple[Tuple[torch.Tensor]], beam_idx: torch.Tensor) -> Tuple[Tuple[torch.Tensor]]:\n", + " \"\"\"\n", + " This function is used to re-order the `past_key_values` cache if [`~PreTrainedModel.beam_search`] or\n", + " [`~PreTrainedModel.beam_sample`] is called.\n", + " This is required to match `past_key_values` with the correct beam_idx at every generation step.\n", + " \"\"\"\n", + " self.next_beam_idx = np.array(beam_idx) # save beam_idx to be used as an input in the next iteration\n", + " return past_key_values\n", + "\n", + " def _get_past_length(self, past_key_values=None):\n", + " if past_key_values is None:\n", + " return 0\n", + " return self._past_length\n", + "\n", + " def prepare_inputs_for_generation(\n", + " self,\n", + " input_ids,\n", + " past_key_values=None,\n", + " attention_mask=None,\n", + " inputs_embeds=None,\n", + " pixel_values=None,\n", + " image_sizes=None,\n", + " **kwargs,\n", + " ):\n", + " if past_key_values is not None:\n", + " past_length = self._get_past_length(past_key_values)\n", + "\n", + " # Keep only the unprocessed tokens:\n", + " # 1 - If the length of the attention_mask exceeds the length of input_ids, then we are in a setting where\n", + " # some of the inputs are exclusively passed as part of the cache (e.g. when passing input_embeds as\n", + " # input)\n", + " if attention_mask is not None and attention_mask.shape[1] > input_ids.shape[1]:\n", + " input_ids = input_ids[:, -(attention_mask.shape[1] - past_length) :]\n", + " # 2 - If the past_length is smaller than input_ids', then input_ids holds all input tokens. We can discard\n", + " # input_ids based on the past_length.\n", + " elif past_length < input_ids.shape[1]:\n", + " input_ids = input_ids[:, past_length:]\n", + "\n", + " position_ids = kwargs.get(\"position_ids\", None)\n", + " if attention_mask is not None and position_ids is None:\n", + " # create position_ids on the fly for batch generation\n", + " position_ids = attention_mask.long().cumsum(-1) - 1\n", + " position_ids.masked_fill_(attention_mask == 0, 1)\n", + " if past_key_values:\n", + " position_ids = position_ids[:, -input_ids.shape[1] :]\n", + "\n", + " # if `inputs_embeds` are passed, we only want to use them in the 1st generation step\n", + " if inputs_embeds is not None and past_key_values is None:\n", + " model_inputs = {\"inputs_embeds\": inputs_embeds}\n", + " else:\n", + " model_inputs = {\"input_ids\": input_ids}\n", + "\n", + " model_inputs.update(\n", + " {\n", + " \"position_ids\": position_ids,\n", + " \"past_key_values\": past_key_values,\n", + " \"use_cache\": kwargs.get(\"use_cache\"),\n", + " \"attention_mask\": attention_mask,\n", + " \"pixel_values\": pixel_values,\n", + " \"image_sizes\": image_sizes,\n", + " }\n", + " )\n", + " return model_inputs\n", + "\n", + " def vision_embed_tokens(\n", + " self,\n", + " input_ids: torch.LongTensor,\n", + " pixel_values: torch.FloatTensor,\n", + " image_sizes=None,\n", + " ) -> torch.FloatTensor:\n", + " MAX_INPUT_ID = int(1e9)\n", + " img_embeds = pixel_values\n", + " img_sizes = image_sizes\n", + "\n", + " input_shape = input_ids.size()\n", + " input_ids = input_ids.view(-1, input_shape[-1])\n", + "\n", + " with torch.no_grad():\n", + " positions = torch.nonzero((input_ids < 0) & (input_ids > -MAX_INPUT_ID), as_tuple=False)\n", + "\n", + " select = False\n", + " if len(positions.tolist()) > 0:\n", + " g_values = abs(input_ids[positions[:, 0], positions[:, 1]])\n", + "\n", + " if img_sizes is not None and len(img_sizes):\n", + " hd_transform = True\n", + " bs = img_embeds.shape[0]\n", + " # Nx(HW)xC\n", + " img_features = torch.from_numpy(self.image_embed(img_embeds.flatten(0, 1))[0])\n", + " base_feat_height = base_feat_width = int(img_features.shape[1] ** 0.5)\n", + "\n", + " # bs x max_num_crops x (24x24) x C\n", + " img_features = img_features.view(bs, -1, base_feat_height * base_feat_width, self.image_dim_out)\n", + " C = self.image_dim_out\n", + " H = base_feat_height\n", + "\n", + " output_imgs = []\n", + " output_len = []\n", + " # training is tensor, inference is list\n", + " if isinstance(img_sizes, torch.Tensor):\n", + " img_sizes = img_sizes.view(-1, 2)\n", + " for _bs in range(bs):\n", + " h, w = img_sizes[_bs]\n", + " h = h // 336\n", + " w = w // 336\n", + " B_ = h * w\n", + "\n", + " # 1 x (24x24) x 1024\n", + " global_img_feature = img_features[_bs, :1]\n", + "\n", + " # 1 x 12 x 12 x 4096\n", + " glb_img = (\n", + " global_img_feature.reshape(1, H, H, C)\n", + " .reshape(1, H // 2, 2, H // 2, 2, C)\n", + " .contiguous()\n", + " .permute(0, 1, 3, 2, 4, 5)\n", + " .reshape(1, H // 2, H // 2, 4 * C)\n", + " .contiguous()\n", + " )\n", + " temp_glb_GN = self.sub_GN.repeat(1, H // 2, 1, 1)\n", + "\n", + " # 1 x 156 x 4096\n", + " glb_img = torch.cat([glb_img, temp_glb_GN], dim=2).reshape(1, -1, 4 * C)\n", + "\n", + " # (max_num_crops-1) x (12x12) x C\n", + " sub_img = img_features[_bs, 1:]\n", + " # 16x574x1024\n", + " # get rid of padding sub_img\n", + " sub_img = sub_img[:B_]\n", + "\n", + " # (num_crops, 12, 2, 12, 2, 1024) -> (num_crops, 12, 12, 2, 2, 1024) -> (num_crops, 12*12, 4*1024)\n", + " sub_img = (\n", + " sub_img.reshape(B_, H, H, C)\n", + " .reshape(B_, H // 2, 2, H // 2, 2, C)\n", + " .contiguous()\n", + " .permute(0, 1, 3, 2, 4, 5)\n", + " .reshape(B_, -1, 4 * C)\n", + " .contiguous()\n", + " )\n", + " sub_img = sub_img.reshape(1, h, w, 12, 12, -1).permute(0, 1, 3, 2, 4, 5).reshape(1, h * 12, w * 12, 4 * C)\n", + " temp_sub_GN = self.sub_GN.repeat(1, h * 12, 1, 1)\n", + " sub_img = torch.cat([sub_img, temp_sub_GN], dim=2).reshape(1, -1, 4 * C)\n", + " # (1, num_img_tokens, 1024*4)\n", + "\n", + " # glb + sub\n", + " if self.hd_transform_order == \"glb_sub\":\n", + " output_imgs.append(torch.cat([glb_img, self.glb_GN, sub_img], dim=1))\n", + " elif self.hd_transform_order == \"sub_glb\":\n", + " output_imgs.append(torch.cat([sub_img, self.glb_GN, glb_img], dim=1))\n", + " else:\n", + " raise NotImplementedError(f\"hd_transform_order = {self.hd_transform_order}, not implemented\")\n", + "\n", + " temp_len = int((h * w + 1) * 144 + 1 + (h + 1) * 12)\n", + " output_len.append(temp_len)\n", + "\n", + " num_img_tokens = output_len\n", + " img_set_tensor = []\n", + " for _output_img in output_imgs:\n", + " img_feature_proj = torch.from_numpy(self.img_projection(_output_img)[0])\n", + " img_set_tensor.append(img_feature_proj)\n", + " elif img_embeds.ndim == 4:\n", + " selected_g_values = g_values[:: self.num_img_tokens]\n", + " tt = self.image_embed(img_embeds).reshape(-1, self.image_dim_out)[0]\n", + " img_set_tensor = torch.from_numpy(self.img_projection(tt)[0]) # adapted visual features.\n", + " elif img_embeds.ndim == 3:\n", + " selected_g_values = g_values[:: self.num_img_tokens]\n", + " tt = img_embeds.view(-1, self.image_dim_out)\n", + " img_set_tensor = torch.from_numpy(self.img_projection(tt)[0]) # adapted visual features.\n", + " else:\n", + " raise NotImplementedError\n", + " select = True\n", + " input_ids.clamp_min_(0).clamp_max_(self.config.vocab_size)\n", + "\n", + " hidden_states = torch.from_numpy(self.embed_tokem(input_ids)[0])\n", + " if select:\n", + " if hd_transform:\n", + " idx = 0\n", + " for i, cnt in enumerate(num_img_tokens):\n", + " hidden_states[positions[idx, 0], positions[idx, 1] : positions[idx, 1] + cnt] = img_set_tensor[i]\n", + " idx += cnt\n", + " else:\n", + " idx = 0\n", + " for i, g in enumerate(selected_g_values):\n", + " cnt = self.num_img_tokens\n", + " hidden_states[positions[idx, 0], positions[idx, 1] : positions[idx, 1] + cnt] = img_set_tensor[i * cnt : (i + 1) * cnt]\n", + " idx += cnt\n", + " return hidden_states\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Convert the model to OpenVino" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โŒ› Phi-3-vision conversion started. Be patient, it may takes some time.\n", + "โŒ› Load Original model\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "69672e2c20374b6b844015915d8fde8e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading checkpoint shards: 0%| | 0/2 [00:00 1 or self.sliding_window is not None) and self.is_causal:\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/modeling_attn_mask_utils.py:162: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if past_key_values_length > 0:\n", + "/mnt/research/.cache/modules/transformers_modules/microsoft/Phi-3-vision-128k-instruct/c45209e90a4c4f7d16b2e9d48503c7f3e83623ed/modeling_phi3_v.py:143: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if seq_len > self.original_max_position_embeddings:\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/nncf/torch/dynamic_graph/wrappers.py:86: TracerWarning: torch.tensor results are registered as constants in the trace. You can safely ignore this warning if you use this function to create tensors out of constant variables that would be the same every time you call this function. In any other case, this might cause the trace to be incorrect.\n", + " op1 = operator(*args, **kwargs)\n", + "/mnt/research/.cache/modules/transformers_modules/microsoft/Phi-3-vision-128k-instruct/c45209e90a4c4f7d16b2e9d48503c7f3e83623ed/modeling_phi3_v.py:381: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if attn_weights.size() != (bsz, self.num_heads, q_len, kv_seq_len):\n", + "/mnt/research/.cache/modules/transformers_modules/microsoft/Phi-3-vision-128k-instruct/c45209e90a4c4f7d16b2e9d48503c7f3e83623ed/modeling_phi3_v.py:388: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):\n", + "/mnt/research/.cache/modules/transformers_modules/microsoft/Phi-3-vision-128k-instruct/c45209e90a4c4f7d16b2e9d48503c7f3e83623ed/modeling_phi3_v.py:400: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if attn_output.size() != (bsz, self.num_heads, q_len, self.head_dim):\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/torch/jit/_trace.py:165: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations. (Triggered internally at aten/src/ATen/core/TensorBody.h:489.)\n", + " if a.grad is not None:\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Language model successfully converted\n", + "โŒ› Weights compression with int4_sym mode started\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "788cfb6b9b424976bc4faa0c8e25cc6a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "

\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "INFO:nncf:Statistics of the bitwidth distribution:\n",
+      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n",
+      "โ”‚   Num bits (N) โ”‚ % all parameters (layers)   โ”‚ % ratio-defining parameters (layers)   โ”‚\n",
+      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n",
+      "โ”‚              8 โ”‚ 42% (54 / 129)              โ”‚ 40% (53 / 128)                         โ”‚\n",
+      "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n",
+      "โ”‚              4 โ”‚ 58% (75 / 129)              โ”‚ 60% (75 / 128)                         โ”‚\n",
+      "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n"
+     ]
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "3fffe8d9828c416cb00938dc959b04a3",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "โœ… Weights compression finished\n",
+      "โœ… Phi-3-vision model conversion finished. You can find results in model/openvino/INT4\n"
+     ]
+    }
+   ],
+   "source": [
+    "from pathlib import Path\n",
+    "import nncf\n",
+    "\n",
+    "\n",
+    "model_id = \"microsoft/Phi-3-vision-128k-instruct\"\n",
+    "out_dir = Path(\"model/openvino/INT4\")\n",
+    "compression_configuration = {\n",
+    "    \"mode\": nncf.CompressWeightsMode.INT4_SYM,\n",
+    "    \"group_size\": 64,\n",
+    "    \"ratio\": 0.6,\n",
+    "}\n",
+    "convert_phi3_model(model_id, out_dir, compression_configuration)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from typing import List\n",
+    "import torch\n",
+    "\n",
+    "def reshape_hd_patches_2x2merge(self, image_features, h_crop, w_crop):\n",
+    "        \"\"\"\n",
+    "        image_features: (num_images*num_crops, 24*24, 1024)\n",
+    "        output: (num_images, h_crop*12, w_crop*12, 4096), h_crop*w_crop == num_crops\n",
+    "        \"\"\"\n",
+    "        N, L, C = image_features.shape\n",
+    "        # assert L == 24 * 24 and C == 1024\n",
+    "        \n",
+    "        # Calculate the number of images dynamically\n",
+    "        num_images = N // (h_crop * w_crop)\n",
+    "        \n",
+    "        # Compute the height dynamically using tensor operations\n",
+    "        H = 24  # Hardcoded value\n",
+    "        \n",
+    "        # Ensure h_crop and w_crop are tensors for traced operations\n",
+    "        # h_crop = torch.tensor(h_crop, dtype=torch.int32)\n",
+    "        # w_crop = torch.tensor(w_crop, dtype=torch.int32)\n",
+    "        \n",
+    "        image_features_hd = (\n",
+    "            image_features.reshape(N, H, H, C)  # N, 24, 24, 1024\n",
+    "            .reshape(N, H // 2, 2, H // 2, 2, C)  # N, 12, 2, 12, 2, 1024\n",
+    "            .permute(0, 1, 3, 2, 4, 5)  # N, 12, 12, 2, 2, 1024\n",
+    "            .reshape(N, -1, 4 * C)  # N, 144, 4096\n",
+    "            .reshape(\n",
+    "                num_images, h_crop, w_crop, H // 2, H // 2, -1\n",
+    "            )  # n_img, h_crop, w_crop, 12, 12, 4096\n",
+    "            .permute(0, 1, 3, 2, 4, 5)  # n_img, h_crop, 12, w_crop, 12, 4096\n",
+    "            .reshape(\n",
+    "                num_images, h_crop * (H // 2), w_crop * (H // 2), 4 * C\n",
+    "            )  # n_img, h_crop*12, w_crop*12, 4096\n",
+    "        )\n",
+    "\n",
+    "        return image_features_hd\n",
+    "\n",
+    "\n",
+    "# @torch.jit.script  # To make this function TorchScript compatible\n",
+    "def hd_feature_transform(self, image_features: torch.Tensor, image_sizes: torch.Tensor) -> torch.Tensor:\n",
+    "    \"\"\"\n",
+    "    image_features: (num_images, num_crops+1, 24*24, 1024)\n",
+    "    image_sizes: list of tuples (h, w) for each image\n",
+    "    \"\"\"\n",
+    "    # Assuming img_projection is either Sequential or Linear\n",
+    "\n",
+    "    global_image_features = image_features[:, 0]  # (num_images, 24*24, 1024)\n",
+    "\n",
+    "    # Assuming these methods are also TorchScript compatible\n",
+    "    global_image_features_hd = self.reshape_hd_patches_2x2merge(global_image_features, 1, 1)\n",
+    "    global_image_features_hd_newline = self.add_image_newline(global_image_features_hd)\n",
+    "\n",
+    "    all_image_embeddings = torch.jit.annotate(List[torch.Tensor], [])\n",
+    "\n",
+    "    # Iterate through each image and handle based on its size\n",
+    "    for i in range(image_features.size(0)):\n",
+    "        img_size = image_sizes[i]\n",
+    "        h, w = img_size[0], img_size[1]\n",
+    "        h_crop = h // 336\n",
+    "        w_crop = w // 336\n",
+    "        num_crops = h_crop * w_crop\n",
+    "\n",
+    "        # Process sub image features\n",
+    "        sub_image_features = image_features[i, 1:1 + num_crops]  # (num_crops, 24*24, 1024)\n",
+    "        sub_image_features_hd = self.reshape_hd_patches_2x2merge(sub_image_features, h_crop, w_crop)\n",
+    "        sub_image_features_hd_newline = self.add_image_newline(sub_image_features_hd)\n",
+    "\n",
+    "        # Append results to the list\n",
+    "        all_image_embeddings.append(sub_image_features_hd_newline.squeeze(0))  # (h_crop*12*(w_crop*12+1), 4096)\n",
+    "        all_image_embeddings.append(self.glb_GN.squeeze(0))\n",
+    "        all_image_embeddings.append(global_image_features_hd_newline[i])\n",
+    "\n",
+    "    # Concatenate all embeddings and apply the projection\n",
+    "    all_image_embeddings_cat = torch.cat(all_image_embeddings, dim=0)\n",
+    "    image_features_proj = self.img_projection(all_image_embeddings_cat)\n",
+    "\n",
+    "    return image_features_proj"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "3ab290c5ac3042c184bf8b0941748f9e",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Loading checkpoint shards:   0%|          | 0/2 [00:00"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "import requests\n",
+    "from PIL import Image\n",
+    "\n",
+    "url = \"http://images.cocodataset.org/val2017/000000039769.jpg\"\n",
+    "image = Image.open(requests.get(url, stream=True).raw)\n",
+    "\n",
+    "print(\"Question:\\n What is unusual on this picture?\")\n",
+    "image"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from transformers import AutoProcessor, TextStreamer\n",
+    "\n",
+    "messages = [\n",
+    "    {\"role\": \"user\", \"content\": \"<|image_1|>\\nWhat is unusual on this picture?\"},\n",
+    "]\n",
+    "\n",
+    "processor = AutoProcessor.from_pretrained(out_dir, trust_remote_code=True)\n",
+    "\n",
+    "prompt = processor.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)\n",
+    "\n",
+    "inputs_new = processor(prompt, [image], return_tensors=\"pt\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "input_ids = inputs_new[\"input_ids\"]\n",
+    "pixel_values = inputs_new[\"pixel_values\"]\n",
+    "image_sizes = inputs_new[\"image_sizes\"]\n",
+    "MAX_INPUT_ID = int(1e9)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/modeling_utils.py:4713: FutureWarning: `_is_quantized_training_enabled` is going to be deprecated in transformers 4.39.0. Please use `model.hf_quantizer.is_trainable` instead\n",
+      "  warnings.warn(\n"
+     ]
+    }
+   ],
+   "source": [
+    "import torch\n",
+    "import types\n",
+    "\n",
+    "vision_embed_tokens = model.model.vision_embed_tokens\n",
+    "\n",
+    "def forward_wrap(\n",
+    "        self,\n",
+    "        pixel_values,\n",
+    "        image_sizes,\n",
+    "        input_ids\n",
+    "):\n",
+    "    num_images, num_crops, c, h, w = pixel_values.shape\n",
+    "    MAX_INPUT_ID = int(1e9)\n",
+    "    # positions for image tokens\n",
+    "    positions = torch.nonzero((input_ids < 0) & (input_ids > -MAX_INPUT_ID), as_tuple=True)\n",
+    "    # input_shape = input_ids.size()\n",
+    "    # input_ids = input_ids.view(-1, input_shape[-1])\n",
+    "    input_ids = input_ids.clamp_min(0).clamp_max(self.vocab_size).detach()\n",
+    "    hidden_states = self.wte(input_ids)\n",
+    "    \n",
+    "    # torch jit condition check for the position shape\n",
+    "\n",
+    "    if len(positions) > 0:\n",
+    "\n",
+    "        img_features = self.get_img_features(pixel_values.flatten(0, 1)).reshape(\n",
+    "                    num_images, num_crops, -1, self.image_dim_out\n",
+    "                )\n",
+    "        image_features_proj = self.hd_feature_transform(img_features, image_sizes)\n",
+    "        hidden_states[positions] = image_features_proj\n",
+    "            \n",
+    "    return hidden_states\n",
+    "\n",
+    "vision_embed_tokens.reshape_hd_patches_2x2merge = types.MethodType(reshape_hd_patches_2x2merge, vision_embed_tokens)\n",
+    "vision_embed_tokens.hd_feature_transform = types.MethodType(hd_feature_transform, vision_embed_tokens)\n",
+    "vision_embed_tokens.forward = types.MethodType(forward_wrap, vision_embed_tokens)\n",
+    "ov_model = ov.convert_model(vision_embed_tokens, example_input={\n",
+    "    \"pixel_values\": pixel_values,\n",
+    "    \"image_sizes\": image_sizes,\n",
+    "    \"input_ids\": input_ids,\n",
+    "})\n",
+    "ov.save_model(ov_model, out_dir/\"reshape_model.xml\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "โŒ› Check if all models are converted\n",
+      "โœ… All models are converted. You can find results in {out_dir}\n"
+     ]
+    }
+   ],
+   "source": [
+    "# check if all the models are converted\n",
+    "\n",
+    "print(\"โŒ› Check if all models are converted\")\n",
+    "lang_model_path = out_dir / \"language_model.xml\"\n",
+    "image_embed_path = out_dir / \"image_embed.xml\"\n",
+    "img_projection_path = out_dir / \"img_projection.xml\"\n",
+    "embed_token_path = out_dir / \"embed_token.xml\"\n",
+    "embed_token_path_2 = out_dir / \"wte_model.xml\"\n",
+    "reshape_model_path = out_dir / \"reshape_model.xml\"\n",
+    "\n",
+    "if all(\n",
+    "    [\n",
+    "        lang_model_path.exists(),\n",
+    "        image_embed_path.exists(),\n",
+    "        img_projection_path.exists(),\n",
+    "        embed_token_path.exists(),\n",
+    "        embed_token_path_2.exists(),\n",
+    "        reshape_model_path.exists(),\n",
+    "    ]\n",
+    "):\n",
+    "    print(\"โœ… All models are converted. You can find results in {out_dir}\")\n",
+    "else:\n",
+    "    print(\"โŒ Not all models are converted. Please check the conversion process\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2 Copy assets to the assets folder"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assets_dir = out_dir / \"assets\"\n",
+    "assets_dir.mkdir(exist_ok=True)\n",
+    "\n",
+    "# copy all the assets to the assets directory (json files, vocab files, etc.)\n",
+    "\n",
+    "import shutil\n",
+    "\n",
+    "# copy all json files\n",
+    "\n",
+    "for file in out_dir.glob(\"*.json\"):\n",
+    "    shutil.copy(file, assets_dir)\n",
+    "\n",
+    "    \n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "total 4.3G\n",
+      "drwxrwxr-x 2 prabod prabod 4.0K Oct 30 01:47 assets\n",
+      "-rw-rw-r-- 1 prabod prabod 3.7K Oct 30 01:33 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 188M Oct 30 01:33 embed_token.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9K Oct 30 01:33 embed_token.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 555M Oct 30 01:34 image_embed.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 937K Oct 30 01:34 image_embed.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  43M Oct 30 01:34 img_projection.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 6.9K Oct 30 01:34 img_projection.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 2.6G Oct 30 01:39 language_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.7M Oct 30 01:39 language_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  525 Oct 30 01:33 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 785M Oct 30 01:41 reshape_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 1.1M Oct 30 01:41 reshape_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  670 Oct 30 01:33 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 9.3K Oct 30 01:33 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.8M Oct 30 01:33 tokenizer.json\n",
+      "-rw-rw-r-- 1 prabod prabod 188M Oct 30 01:33 wte_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9K Oct 30 01:33 wte_model.xml\n"
+     ]
+    }
+   ],
+   "source": [
+    "!ls -lh {out_dir}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "total 1.8M\n",
+      "-rw-rw-r-- 1 prabod prabod 3.7K Oct 30 01:47 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  525 Oct 30 01:47 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  670 Oct 30 01:47 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 9.3K Oct 30 01:47 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.8M Oct 30 01:47 tokenizer.json\n"
+     ]
+    }
+   ],
+   "source": [
+    "!ls -lh {assets_dir}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.3 Test the openvino model"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import openvino as ov\n",
+    "import torch\n",
+    "\n",
+    "core = ov.Core()\n",
+    "device = \"CPU\"\n",
+    "model_dir = out_dir"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "language_model = core.read_model(model_dir / \"language_model.xml\")\n",
+    "compiled_language_model = core.compile_model(language_model, \"AUTO\")\n",
+    "\n",
+    "image_embed = core.compile_model(model_dir / \"image_embed.xml\", device)\n",
+    "img_projection = core.compile_model(model_dir / \"img_projection.xml\", device)\n",
+    "embed_tokem = core.compile_model(model_dir / \"wte_model.xml\", device)\n",
+    "reshape_model = core.compile_model(model_dir / \"reshape_model.xml\", device)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\n",
+      "Question:\n",
+      " What is unusual on this picture?\n",
+      "Answer:\n",
+      "The unusual aspect of this picture is the presence of two cats lying on a pink couch, with one cat sleeping and the other cat playing with a remote control. It is not common to see cats actively engaging with objects\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Initialize the generation loop\n",
+    "generated_tokens = []\n",
+    "\n",
+    "from transformers import AutoProcessor, TextStreamer\n",
+    "\n",
+    "messages = [\n",
+    "    {\"role\": \"user\", \"content\": \"<|image_1|>\\nWhat is unusual on this picture?\"},\n",
+    "]\n",
+    "\n",
+    "processor = AutoProcessor.from_pretrained(model_dir, trust_remote_code=True)\n",
+    "\n",
+    "prompt = processor.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)\n",
+    "\n",
+    "inputs_new = processor(prompt, [image], return_tensors=\"pt\")\n",
+    "\n",
+    "generation_args = {\"max_new_tokens\": 50, \"do_sample\": False, \"streamer\": TextStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)}\n",
+    "\n",
+    "\n",
+    "request = compiled_language_model.create_infer_request()\n",
+    "input_names = {key.get_any_name(): idx for idx, key in enumerate(language_model.inputs)}\n",
+    "inputs = {}\n",
+    "# Set the initial input_ids\n",
+    "current_input_ids = input_ids\n",
+    "attention_mask = inputs_new[\"attention_mask\"]\n",
+    "position_ids = attention_mask.long().cumsum(-1) - 1\n",
+    "position_ids.masked_fill_(attention_mask == 0, 1)\n",
+    "# Loop for generating tokens\n",
+    "for i in range(generation_args[\"max_new_tokens\"]):\n",
+    "    # Generate input embeds each time\n",
+    "    if current_input_ids.shape[-1] > 1:\n",
+    "        input_embeds = torch.from_numpy(reshape_model({\n",
+    "            \"pixel_values\": pixel_values,\n",
+    "            \"image_sizes\": image_sizes,\n",
+    "            \"input_ids\": current_input_ids\n",
+    "        })[0])\n",
+    "    else:\n",
+    "        input_embeds = torch.from_numpy(embed_tokem(current_input_ids)[0])\n",
+    "    \n",
+    "    if i>0:\n",
+    "        inputs = {}\n",
+    "    # Prepare inputs for the model\n",
+    "    inputs[\"inputs_embeds\"] = input_embeds\n",
+    "    inputs[\"attention_mask\"] = attention_mask\n",
+    "    inputs[\"position_ids\"] = position_ids\n",
+    "    if \"beam_idx\" in input_names:\n",
+    "        inputs[\"beam_idx\"] = np.arange(input_embeds.shape[0], dtype=int)\n",
+    "    \n",
+    "    # Start inference\n",
+    "    request.start_async(inputs, share_inputs=True)\n",
+    "    request.wait()\n",
+    "    \n",
+    "    # Get the logits and find the next token\n",
+    "    logits = torch.from_numpy(request.get_tensor(\"logits\").data)\n",
+    "    next_token = logits.argmax(-1)[0][-1]\n",
+    "    \n",
+    "    # Append the generated token\n",
+    "    generated_tokens.append(next_token)\n",
+    "    \n",
+    "    # Update input_ids with the new token\n",
+    "    current_input_ids = torch.cat([next_token.unsqueeze(0).unsqueeze(0)], dim=-1)\n",
+    "    \n",
+    "    # update the attention mask\n",
+    "    attention_mask = torch.cat([attention_mask, torch.ones_like(attention_mask[:, :1])], dim=-1)\n",
+    "\n",
+    "    # Update inputs for the next iteration\n",
+    "    position_ids = attention_mask.long().cumsum(-1) - 1\n",
+    "    position_ids.masked_fill_(attention_mask == 0, 1)\n",
+    "    position_ids = position_ids[:, -current_input_ids.shape[1] :]\n",
+    "    inputs[\"position_ids\"] = position_ids\n",
+    "\n",
+    "# Convert generated tokens to text\n",
+    "generated_text = processor.tokenizer.decode(generated_tokens, skip_special_tokens=True, eos_token_id=processor.tokenizer.eos_token_id)\n",
+    "image\n",
+    "print(\"Question:\\n What is unusual on this picture?\")\n",
+    "print(\"Answer:\")\n",
+    "print(generated_text)\n"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 2. Import and Save Phi3Vision in Spark NLP\n",
+    "\n",
+    "- Let's install and setup Spark NLP in Google Colab\n",
+    "- This part is pretty easy via our simple script"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's start Spark with Spark NLP included via our simple `start()` function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "24/10/30 04:35:00 WARN Utils: Your hostname, minotaur resolves to a loopback address: 127.0.1.1; using 192.168.1.4 instead (on interface eno1)\n",
+      "24/10/30 04:35:00 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n",
+      "24/10/30 04:35:01 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "Setting default log level to \"WARN\".\n",
+      "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n"
+     ]
+    }
+   ],
+   "source": [
+    "import sparknlp\n",
+    "\n",
+    "# let's start Spark with Spark NLP\n",
+    "spark = sparknlp.start()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "24/10/30 04:27:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native6854177710944855827/libtbb.so.2\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "WARNING: An illegal reflective access operation has occurred\n",
+      "WARNING: Illegal reflective access by org.apache.spark.util.SizeEstimator$ (file:/home/prabod/spark/jars/spark-core_2.12-3.3.2.jar) to field java.util.regex.Pattern.pattern\n",
+      "WARNING: Please consider reporting this to the maintainers of org.apache.spark.util.SizeEstimator$\n",
+      "WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n",
+      "WARNING: All illegal access operations will be denied in a future release\n"
+     ]
+    }
+   ],
+   "source": [
+    "imageClassifier = Phi3Vision.pretrained() \\\n",
+    "            .setInputCols(\"image_assembler\") \\\n",
+    "            .setOutputCol(\"answer\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "imageClassifier.write().overwrite().save(\"phi3vision_spark_nlp\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sparknlp\n",
+    "from sparknlp.base import *\n",
+    "from sparknlp.annotator import *\n",
+    "from pyspark.sql.functions import lit\n",
+    "from pyspark.ml import Pipeline\n",
+    "from pathlib import Path\n",
+    "import os\n",
+    "\n",
+    "# download two images to test into ./images folder\n",
+    "\n",
+    "url1 = \"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\"\n",
+    "url2 = \"http://images.cocodataset.org/val2017/000000039769.jpg\"\n",
+    "\n",
+    "Path(\"images\").mkdir(exist_ok=True)\n",
+    "\n",
+    "!wget -q -O images/image1.jpg {url1}\n",
+    "!wget -q -O images/image2.jpg {url2}\n",
+    "\n",
+    "\n",
+    "\n",
+    "images_path = \"file://\" + os.getcwd() + \"/images/\"\n",
+    "image_df = spark.read.format(\"image\").load(\n",
+    "    path=images_path\n",
+    ")\n",
+    "\n",
+    "test_df = image_df.withColumn(\"text\", lit(\"<|user|> \\n <|image_1|> \\n What's this picture about? <|end|>\\n <|assistant|>\\n\"))\n",
+    "\n",
+    "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n",
+    "\n",
+    "imageClassifier = Phi3Vision.load(\"phi3vision_spark_nlp\")\\\n",
+    "            .setMaxOutputLength(50) \\\n",
+    "            .setInputCols(\"image_assembler\") \\\n",
+    "            .setOutputCol(\"answer\")\n",
+    "\n",
+    "pipeline = Pipeline(\n",
+    "            stages=[\n",
+    "                image_assembler,\n",
+    "                imageClassifier,\n",
+    "            ]\n",
+    "        )\n",
+    "\n",
+    "model = pipeline.fit(test_df)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "image_path: /mnt/research/Projects/ModelZoo/Phi-3.5-vision/images/image1.jpg\n",
+      "[Annotation(document, 0, 714, The image shows a cat lying inside a cardboard box. The cat appears to be relaxed and comfortable, with its paws up in the air. The box is placed on a carpeted floor, and there is a white sofa visible in the background. The cat's fur is grey and white, and it has a curious expression on its face. The overall mood of the image is calm and peaceful. The image shows a cat lying inside a cardboard box. The cat appears to be relaxed and comfortable, with its paws up in the air. The box is placed on a carpeted floor, and there is a white sofa visible in the background. The cat's fur is grey and white, and it has a curious expression on its face. The overall mood of the image is calm and peaceful. The image shows, Map(), [])]\n"
+     ]
+    }
+   ],
+   "source": [
+    "light_pipeline = LightPipeline(model)\n",
+    "image_path = os.getcwd() + \"/images/\" + \"image1.jpg\"\n",
+    "print(\"image_path: \" + image_path)\n",
+    "annotations_result = light_pipeline.fullAnnotateImage(\n",
+    "    image_path,\n",
+    "    \"<|user|> \\n <|image_1|> \\n What's this picture about? <|end|>\\n <|assistant|>\\n\"\n",
+    ")\n",
+    "\n",
+    "for result in annotations_result:\n",
+    "    print(result[\"answer\"])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "tempspark",
+   "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.8.16"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py b/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py
index bb57e0a94f3985..f8cf221e0546ed 100644
--- a/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py
+++ b/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py
@@ -20,7 +20,7 @@ class Phi3Vision(AnnotatorModel,
                                HasEngine,
                                HasCandidateLabelsProperties,
                                HasRescaleFactor):
-    """BLIPForQuestionAnswering can load BLIP models  for visual question answering.
+    """Phi3Vision can load Phi3Vision models  for visual question answering.
     The model consists of a vision encoder, a text encoder as well as a text decoder.
     The vision encoder will encode the input image, the text encoder will encode the input question together
     with the encoding of the image, and the text decoder will output the answer to the question.
@@ -28,11 +28,11 @@ class Phi3Vision(AnnotatorModel,
     Pretrained models can be loaded with :meth:`.pretrained` of the companion
     object:
 
-    >>> visualQAClassifier = BLIPForQuestionAnswering.pretrained() \\
+    >>> visualQAClassifier = Phi3Vision.pretrained() \\
     ...     .setInputCols(["image_assembler"]) \\
     ...     .setOutputCol("answer")
 
-    The default model is ``"blip_vqa_base"``, if no name is
+    The default model is ``"phi3v"``, if no name is
     provided.
 
     For available pretrained models please see the `Models Hub
@@ -65,14 +65,13 @@ class Phi3Vision(AnnotatorModel,
     >>> from sparknlp.annotator import *
     >>> from pyspark.ml import Pipeline
     >>> image_df = SparkSessionForTest.spark.read.format("image").load(path=images_path)
-    >>> test_df = image_df.withColumn("text", lit("What's this picture about?"))
+    >>> test_df = image_df.withColumn("text", lit("<|user|> \n <|image_1|> \nWhat is unusual on this picture? <|end|>\n <|assistant|>\n"))
     >>> imageAssembler = ImageAssembler() \\
     ...     .setInputCol("image") \\
     ...     .setOutputCol("image_assembler")
-    >>> visualQAClassifier = BLIPForQuestionAnswering.pretrained() \\
+    >>> visualQAClassifier = Phi3Vision.pretrained() \\
     ...     .setInputCols("image_assembler") \\
-    ...     .setOutputCol("answer") \\
-    ...     .setSize(384)
+    ...     .setOutputCol("answer")
     >>> pipeline = Pipeline().setStages([
     ...     imageAssembler,
     ...     visualQAClassifier
@@ -82,7 +81,7 @@ class Phi3Vision(AnnotatorModel,
     +--------------------------------------+------+
     |origin                                |result|
     +--------------------------------------+------+
-    |[file:///content/images/cat_image.jpg]|[cats]|
+    |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]|
     +--------------------------------------+------+
     """
 
@@ -313,7 +312,7 @@ def pretrained(name="phi3v", lang="en", remote_loc=None):
         ----------
         name : str, optional
             Name of the pretrained model, by default
-            "blip_vqa_tf"
+            "phi3v"
         lang : str, optional
             Language of the pretrained model, by default "en"
         remote_loc : str, optional

From 504046808c194f7665fb5cca346e934818f12ee2 Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Wed, 30 Oct 2024 04:58:26 +0000
Subject: [PATCH 041/108] updated testes

Signed-off-by: Prabod Rathnayaka 
---
 .../nlp/annotators/cv/Phi3VisionTestSpec.scala             | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala
index 08eb4e78d920f7..6f3e9b56b5f427 100644
--- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala
+++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTestSpec.scala
@@ -27,9 +27,9 @@ import org.scalatest.flatspec.AnyFlatSpec
 
 class Phi3VisionTestSpec extends AnyFlatSpec {
 
-  lazy val model = getBLIPForQuestionAnsweringPipelineModel
+  lazy val model = getPhi3VisionPipelineModel
 
-  "BLIP" should "answer a question for a given image" taggedAs SlowTest in {
+  "Phi3Vision" should "answer a question for a given image" taggedAs SlowTest in {
 
     val testDF = getTestDF
     val result = model.transform(testDF)
@@ -152,7 +152,7 @@ class Phi3VisionTestSpec extends AnyFlatSpec {
 
   }
 
-  private def getBLIPForQuestionAnsweringPipelineModel = {
+  private def getPhi3VisionPipelineModel = {
     val testDF = getTestDF
 
     val imageAssembler: ImageAssembler = new ImageAssembler()
@@ -163,6 +163,7 @@ class Phi3VisionTestSpec extends AnyFlatSpec {
       .pretrained()
       .setInputCols("image_assembler")
       .setOutputCol("answer")
+      .setMaxOutputLength(50)
 
     val newPipeline: Pipeline =
       new Pipeline().setStages(Array(imageAssembler, loadModel))

From 27140d7b9cb7a8d910a131a467e641e0b505854a Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Thu, 13 Feb 2025 03:39:37 +0000
Subject: [PATCH 042/108] update default name and documentation

Signed-off-by: Prabod Rathnayaka 
---
 ...ace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb | 529 +++++++++++++-----
 .../cv/phi3_vision_for_multimodal.py          |   6 +-
 .../cv/phi3_vision_for_multimodal_test.py     |   4 +-
 .../nlp/annotators/cv/Phi3Vision.scala        |   6 +-
 4 files changed, 387 insertions(+), 158 deletions(-)

diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb
index 8b614a45969f21..1fcd43a3d495df 100644
--- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb
+++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Phi3Vision.ipynb
@@ -38,26 +38,41 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 4,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n"
+     ]
+    }
+   ],
    "source": [
-    "!pip install -q --upgrade transformers==4.41.2\n",
-    "!pip install -q --upgrade openvino==2024.1\n",
-    "!pip install -q --upgrade optimum-intel\n",
-    "!pip install -q --upgrade nncf\n",
-    "!pip install -q --upgrade huggingface_hub\n",
-    "!pip install -q --upgrade onnx==1.15.0\n",
-    "!pip install -q --upgrade torch==2.2.1"
+    "%pip install -q \"torch>=2.1\" \"torchvision\" \"transformers==4.41\" \"protobuf>=3.20\" \"gradio>=4.26\" \"Pillow\" \"accelerate\" \"tqdm\"  --extra-index-url https://download.pytorch.org/whl/cpu\n",
+    "%pip install  -q \"openvino>=2024.2.0\" \"nncf>=2.11.0\""
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 1,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/importlib/util.py:245: DeprecationWarning: The `openvino.runtime` module is deprecated and will be removed in the 2026.0 release. Please replace `openvino.runtime` with `openvino`.\n",
+      "  self.__spec__.loader.exec_module(self)\n",
+      "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
+      "  from .autonotebook import tqdm as notebook_tqdm\n"
+     ]
+    }
+   ],
    "source": [
-    "# taken from \n",
+    "# taken from https://github.com/openvinotoolkit/openvino_notebooks/blob/e14498f99864a10e37223ec74bf7f7827c07633d/notebooks/phi-3-vision/phi-3-vision.ipynb\n",
     "\n",
     "\n",
     "from pathlib import Path\n",
@@ -663,7 +678,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [
     {
@@ -674,26 +689,12 @@
       "โŒ› Load Original model\n"
      ]
     },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "69672e2c20374b6b844015915d8fde8e",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Loading checkpoint shards:   0%|          | 0/2 [00:00 1 or self.sliding_window is not None) and self.is_causal:\n",
-      "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/modeling_attn_mask_utils.py:162: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
+      "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/transformers/modeling_attn_mask_utils.py:162: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
       "  if past_key_values_length > 0:\n",
       "/mnt/research/.cache/modules/transformers_modules/microsoft/Phi-3-vision-128k-instruct/c45209e90a4c4f7d16b2e9d48503c7f3e83623ed/modeling_phi3_v.py:143: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
       "  if seq_len > self.original_max_position_embeddings:\n",
-      "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/nncf/torch/dynamic_graph/wrappers.py:86: TracerWarning: torch.tensor results are registered as constants in the trace. You can safely ignore this warning if you use this function to create tensors out of constant variables that would be the same every time you call this function. In any other case, this might cause the trace to be incorrect.\n",
+      "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/nncf/torch/dynamic_graph/wrappers.py:85: TracerWarning: torch.tensor results are registered as constants in the trace. You can safely ignore this warning if you use this function to create tensors out of constant variables that would be the same every time you call this function. In any other case, this might cause the trace to be incorrect.\n",
       "  op1 = operator(*args, **kwargs)\n",
       "/mnt/research/.cache/modules/transformers_modules/microsoft/Phi-3-vision-128k-instruct/c45209e90a4c4f7d16b2e9d48503c7f3e83623ed/modeling_phi3_v.py:381: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
       "  if attn_weights.size() != (bsz, self.num_heads, q_len, kv_seq_len):\n",
@@ -743,7 +749,7 @@
       "  if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):\n",
       "/mnt/research/.cache/modules/transformers_modules/microsoft/Phi-3-vision-128k-instruct/c45209e90a4c4f7d16b2e9d48503c7f3e83623ed/modeling_phi3_v.py:400: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
       "  if attn_output.size() != (bsz, self.num_heads, q_len, self.head_dim):\n",
-      "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/torch/jit/_trace.py:165: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations. (Triggered internally at aten/src/ATen/core/TensorBody.h:489.)\n",
+      "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/torch/jit/_trace.py:165: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations. (Triggered internally at /pytorch/build/aten/src/ATen/core/TensorBody.h:489.)\n",
       "  if a.grad is not None:\n"
      ]
     },
@@ -757,13 +763,16 @@
     },
     {
      "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "788cfb6b9b424976bc4faa0c8e25cc6a",
-       "version_major": 2,
-       "version_minor": 0
-      },
+      "text/html": [
+       "
/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/rich/live.py:231: UserWarning: install \"ipywidgets\" \n",
+       "for Jupyter support\n",
+       "  warnings.warn('install \"ipywidgets\" for Jupyter support')\n",
+       "
\n" + ], "text/plain": [ - "Output()" + "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/rich/live.py:231: UserWarning: install \"ipywidgets\" \n", + "for Jupyter support\n", + " warnings.warn('install \"ipywidgets\" for Jupyter support')\n" ] }, "metadata": {}, @@ -784,29 +793,15 @@ "output_type": "stream", "text": [ "INFO:nncf:Statistics of the bitwidth distribution:\n", - "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n", - "โ”‚ Num bits (N) โ”‚ % all parameters (layers) โ”‚ % ratio-defining parameters (layers) โ”‚\n", - "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n", - "โ”‚ 8 โ”‚ 42% (54 / 129) โ”‚ 40% (53 / 128) โ”‚\n", - "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n", - "โ”‚ 4 โ”‚ 58% (75 / 129) โ”‚ 60% (75 / 128) โ”‚\n", - "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n" + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n", + "โ”‚ Weight compression mode โ”‚ % all parameters (layers) โ”‚ % ratio-defining parameters (layers) โ”‚\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n", + "โ”‚ int8_asym โ”‚ 42% (54 / 129) โ”‚ 40% (53 / 128) โ”‚\n", + "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n", + "โ”‚ int4_sym โ”‚ 58% (75 / 129) โ”‚ 60% (75 / 128) โ”‚\n", + "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n" ] }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "3fffe8d9828c416cb00938dc959b04a3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, { "data": { "text/html": [ @@ -843,7 +838,33 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: transformers\n", + "Version: 4.41.0\n", + "Summary: State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow\n", + "Home-page: https://github.com/huggingface/transformers\n", + "Author: The Hugging Face team (past and future) with the help of all our contributors (https://github.com/huggingface/transformers/graphs/contributors)\n", + "Author-email: transformers@huggingface.co\n", + "License: Apache 2.0 License\n", + "Location: /home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages\n", + "Requires: filelock, huggingface-hub, numpy, packaging, pyyaml, regex, requests, safetensors, tokenizers, tqdm\n", + "Required-by: \n" + ] + } + ], + "source": [ + "!pip show transformers" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -928,22 +949,16 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "3ab290c5ac3042c184bf8b0941748f9e", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Loading checkpoint shards: 0%| | 0/2 [00:00" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -994,9 +1009,18 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "You are using the default legacy behaviour of the . This is expected, and simply means that the `legacy` (previous) behavior will be used so nothing changes for you. If you want to use the new behaviour, set `legacy=False`. This should only be set if you understand what it means, and thoroughly read the reason why this was added as explained in https://github.com/huggingface/transformers/pull/24565\n", + "Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n" + ] + } + ], "source": [ "from transformers import AutoProcessor, TextStreamer\n", "\n", @@ -1013,7 +1037,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -1025,15 +1049,211 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/modeling_utils.py:4713: FutureWarning: `_is_quantized_training_enabled` is going to be deprecated in transformers 4.39.0. Please use `model.hf_quantizer.is_trainable` instead\n", - " warnings.warn(\n" + "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/transformers/modeling_utils.py:4481: FutureWarning: `_is_quantized_training_enabled` is going to be deprecated in transformers 4.39.0. Please use `model.hf_quantizer.is_trainable` instead\n", + " warnings.warn(\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/transformers/models/clip/modeling_clip.py:276: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if attn_weights.size() != (bsz * self.num_heads, tgt_len, src_len):\n", + "/home/prabod/anaconda3/envs/phi3v2/lib/python3.9/site-packages/transformers/models/clip/modeling_clip.py:316: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if attn_output.size() != (bsz * self.num_heads, tgt_len, self.head_dim):\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n" ] } ], @@ -1083,7 +1303,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -1130,7 +1350,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -1151,7 +1371,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -1169,24 +1389,24 @@ "output_type": "stream", "text": [ "total 4.3G\n", - "drwxrwxr-x 2 prabod prabod 4.0K Oct 30 01:47 assets\n", - "-rw-rw-r-- 1 prabod prabod 3.7K Oct 30 01:33 config.json\n", - "-rw-rw-r-- 1 prabod prabod 188M Oct 30 01:33 embed_token.bin\n", - "-rw-rw-r-- 1 prabod prabod 2.9K Oct 30 01:33 embed_token.xml\n", - "-rw-rw-r-- 1 prabod prabod 555M Oct 30 01:34 image_embed.bin\n", - "-rw-rw-r-- 1 prabod prabod 937K Oct 30 01:34 image_embed.xml\n", - "-rw-rw-r-- 1 prabod prabod 43M Oct 30 01:34 img_projection.bin\n", - "-rw-rw-r-- 1 prabod prabod 6.9K Oct 30 01:34 img_projection.xml\n", - "-rw-rw-r-- 1 prabod prabod 2.6G Oct 30 01:39 language_model.bin\n", - "-rw-rw-r-- 1 prabod prabod 2.7M Oct 30 01:39 language_model.xml\n", - "-rw-rw-r-- 1 prabod prabod 525 Oct 30 01:33 preprocessor_config.json\n", - "-rw-rw-r-- 1 prabod prabod 785M Oct 30 01:41 reshape_model.bin\n", - "-rw-rw-r-- 1 prabod prabod 1.1M Oct 30 01:41 reshape_model.xml\n", - "-rw-rw-r-- 1 prabod prabod 670 Oct 30 01:33 special_tokens_map.json\n", - "-rw-rw-r-- 1 prabod prabod 9.3K Oct 30 01:33 tokenizer_config.json\n", - "-rw-rw-r-- 1 prabod prabod 1.8M Oct 30 01:33 tokenizer.json\n", - "-rw-rw-r-- 1 prabod prabod 188M Oct 30 01:33 wte_model.bin\n", - "-rw-rw-r-- 1 prabod prabod 2.9K Oct 30 01:33 wte_model.xml\n" + "drwxrwxr-x 2 prabod prabod 4.0K Feb 13 01:09 assets\n", + "-rw-rw-r-- 1 prabod prabod 3.7K Feb 13 01:03 config.json\n", + "-rw-rw-r-- 1 prabod prabod 188M Feb 13 01:03 embed_token.bin\n", + "-rw-rw-r-- 1 prabod prabod 2.9K Feb 13 01:03 embed_token.xml\n", + "-rw-rw-r-- 1 prabod prabod 555M Feb 13 01:04 image_embed.bin\n", + "-rw-rw-r-- 1 prabod prabod 982K Feb 13 01:04 image_embed.xml\n", + "-rw-rw-r-- 1 prabod prabod 43M Feb 13 01:04 img_projection.bin\n", + "-rw-rw-r-- 1 prabod prabod 6.9K Feb 13 01:04 img_projection.xml\n", + "-rw-rw-r-- 1 prabod prabod 2.6G Feb 13 01:06 language_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 2.3M Feb 13 01:06 language_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 525 Feb 13 01:03 preprocessor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 785M Feb 13 01:08 reshape_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 1.1M Feb 13 01:08 reshape_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 670 Feb 13 01:03 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 9.3K Feb 13 01:03 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 1.8M Feb 13 01:03 tokenizer.json\n", + "-rw-rw-r-- 1 prabod prabod 188M Feb 13 01:03 wte_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 2.9K Feb 13 01:03 wte_model.xml\n" ] } ], @@ -1196,7 +1416,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -1214,11 +1434,11 @@ "output_type": "stream", "text": [ "total 1.8M\n", - "-rw-rw-r-- 1 prabod prabod 3.7K Oct 30 01:47 config.json\n", - "-rw-rw-r-- 1 prabod prabod 525 Oct 30 01:47 preprocessor_config.json\n", - "-rw-rw-r-- 1 prabod prabod 670 Oct 30 01:47 special_tokens_map.json\n", - "-rw-rw-r-- 1 prabod prabod 9.3K Oct 30 01:47 tokenizer_config.json\n", - "-rw-rw-r-- 1 prabod prabod 1.8M Oct 30 01:47 tokenizer.json\n" + "-rw-rw-r-- 1 prabod prabod 3.7K Feb 13 01:09 config.json\n", + "-rw-rw-r-- 1 prabod prabod 525 Feb 13 01:09 preprocessor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 670 Feb 13 01:09 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 9.3K Feb 13 01:09 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 1.8M Feb 13 01:09 tokenizer.json\n" ] } ], @@ -1235,7 +1455,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -1249,7 +1469,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -1267,15 +1487,21 @@ "execution_count": 16, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "\n", "Question:\n", " What is unusual on this picture?\n", "Answer:\n", - "The unusual aspect of this picture is the presence of two cats lying on a pink couch, with one cat sleeping and the other cat playing with a remote control. It is not common to see cats actively engaging with objects\n" + "The unusual aspect of this picture is that there are two cats lying on a pink couch, and they are both holding remotes in their paws. This is an uncommon sight, as it is not typical for cats to interact\n" ] } ], @@ -1416,14 +1642,42 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "imageClassifier = Phi3Vision.loadSavedModel(out_dir, spark) \\\n", + " .setInputCols(\"image_assembler\") \\\n", + " .setOutputCol(\"answer\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "imageClassifier.write().overwrite().save(\"/tmp/phi3vision_spark_nlp\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "24/10/30 04:27:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native6854177710944855827/libtbb.so.2\n" + "25/02/13 03:00:12 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native2045993875761212240/libtbb.so.2\n" ] }, { @@ -1438,26 +1692,6 @@ ] } ], - "source": [ - "imageClassifier = Phi3Vision.pretrained() \\\n", - " .setInputCols(\"image_assembler\") \\\n", - " .setOutputCol(\"answer\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "imageClassifier.write().overwrite().save(\"phi3vision_spark_nlp\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], "source": [ "import sparknlp\n", "from sparknlp.base import *\n", @@ -1488,7 +1722,7 @@ "\n", "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n", "\n", - "imageClassifier = Phi3Vision.load(\"phi3vision_spark_nlp\")\\\n", + "imageClassifier = Phi3Vision.load(\"file:///tmp/phi3vision_spark_nlp\")\\\n", " .setMaxOutputLength(50) \\\n", " .setInputCols(\"image_assembler\") \\\n", " .setOutputCol(\"answer\")\n", @@ -1500,7 +1734,9 @@ " ]\n", " )\n", "\n", - "model = pipeline.fit(test_df)" + "model = pipeline.fit(test_df)\n", + "\n", + "results.select(\"generation.result\").show(truncate=False)" ] }, { @@ -1512,8 +1748,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "image_path: /mnt/research/Projects/ModelZoo/Phi-3.5-vision/images/image1.jpg\n", - "[Annotation(document, 0, 714, The image shows a cat lying inside a cardboard box. The cat appears to be relaxed and comfortable, with its paws up in the air. The box is placed on a carpeted floor, and there is a white sofa visible in the background. The cat's fur is grey and white, and it has a curious expression on its face. The overall mood of the image is calm and peaceful. The image shows a cat lying inside a cardboard box. The cat appears to be relaxed and comfortable, with its paws up in the air. The box is placed on a carpeted floor, and there is a white sofa visible in the background. The cat's fur is grey and white, and it has a curious expression on its face. The overall mood of the image is calm and peaceful. The image shows, Map(), [])]\n" + "image_path: /home/prabod/Projects/spark-nlp/examples/python/transformers/openvino/images/image1.jpg\n", + "[Annotation(document, 0, 200, The image shows a cat lying inside a cardboard box. The cat appears to be relaxed and comfortable, with its paws up in the air and its head resting on the side of the box. The box is placed on a carpet, Map(), [])]\n" ] } ], @@ -1529,18 +1765,11 @@ "for result in annotations_result:\n", " print(result[\"answer\"])" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "tempspark", + "display_name": "phi3v2", "language": "python", "name": "python3" }, @@ -1554,7 +1783,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.16" + "version": "3.9.21" } }, "nbformat": 4, diff --git a/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py b/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py index f8cf221e0546ed..d86261cbc5f894 100644 --- a/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py +++ b/python/sparknlp/annotator/cv/phi3_vision_for_multimodal.py @@ -32,7 +32,7 @@ class Phi3Vision(AnnotatorModel, ... .setInputCols(["image_assembler"]) \\ ... .setOutputCol("answer") - The default model is ``"phi3v"``, if no name is + The default model is ``"phi_3_vision_128k_instruct"``, if no name is provided. For available pretrained models please see the `Models Hub @@ -69,7 +69,7 @@ class Phi3Vision(AnnotatorModel, >>> imageAssembler = ImageAssembler() \\ ... .setInputCol("image") \\ ... .setOutputCol("image_assembler") - >>> visualQAClassifier = Phi3Vision.pretrained() \\ + >>> visualQAClassifier = Phi3Vision.pretrained("phi_3_vision_128k_instruct","en") \\ ... .setInputCols("image_assembler") \\ ... .setOutputCol("answer") >>> pipeline = Pipeline().setStages([ @@ -305,7 +305,7 @@ def loadSavedModel(folder, spark_session, use_openvino=False): return Phi3Vision(java_model=jModel) @staticmethod - def pretrained(name="phi3v", lang="en", remote_loc=None): + def pretrained(name="phi_3_vision_128k_instruct", lang="en", remote_loc=None): """Downloads and loads a pretrained model. Parameters diff --git a/python/test/annotator/cv/phi3_vision_for_multimodal_test.py b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py index 064e94d02716ed..bef92d3a306f80 100644 --- a/python/test/annotator/cv/phi3_vision_for_multimodal_test.py +++ b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py @@ -24,7 +24,7 @@ class Phi3VisionTestSetup(unittest.TestCase): def setUp(self): - self.images_path = os.getcwd() + "/../src/test/resources/image/" + self.images_path = "file://" + os.getcwd() + "/../src/test/resources/image/" self.spark = SparkContextForTest.spark image_df = SparkSessionForTest.spark.read.format("image").load( path=self.images_path @@ -34,7 +34,7 @@ def setUp(self): image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") - imageClassifier = Phi3Vision.pretrained() \ + imageClassifier = Phi3Vision.loadSavedModel("/home/prabod/Projects/spark-nlp/examples/python/transformers/openvino/model/openvino/INT4", self.spark) \ .setInputCols("image_assembler") \ .setOutputCol("answer") diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala index a5d688eb4c5302..eeb7cc0b0fb8c0 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala @@ -49,7 +49,7 @@ import org.apache.spark.sql.SparkSession * .setInputCols("image_assembler") * .setOutputCol("answer") * }}} - * The default model is `"phi3v"`, if no name is provided. + * The default model is `"phi_3_vision_128k_instruct"`, if no name is provided. * * For available pretrained models please see the * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. @@ -78,7 +78,7 @@ import org.apache.spark.sql.SparkSession * .setInputCol("image") * .setOutputCol("image_assembler") * - * val visualQAClassifier = Phi3Vision.pretrained() + * val visualQAClassifier = Phi3Vision.pretrained("phi_3_vision_128k_instruct","en") * .setInputCols("image_assembler") * .setOutputCol("answer") * @@ -340,7 +340,7 @@ trait ReadablePretrainedPhi3Vision extends ParamsAndFeaturesReadable[Phi3Vision] with HasPretrained[Phi3Vision] { - override val defaultModelName: Some[String] = Some("phi3v") + override val defaultModelName: Some[String] = Some("phi_3_vision_128k_instruct") /** Java compliant-overrides */ override def pretrained(): Phi3Vision = super.pretrained() From 9752516a87a32fd28129e6fcc2b16703cb7488c8 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 13 Feb 2025 03:57:23 +0000 Subject: [PATCH 043/108] update documentation and resource downloader entry Signed-off-by: Prabod Rathnayaka --- docs/en/transformer_entries/Phi3Vision.md | 127 ++++++++++++++++++ .../nlp/pretrained/ResourceDownloader.scala | 3 +- 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 docs/en/transformer_entries/Phi3Vision.md diff --git a/docs/en/transformer_entries/Phi3Vision.md b/docs/en/transformer_entries/Phi3Vision.md new file mode 100644 index 00000000000000..f1c332373f5a7f --- /dev/null +++ b/docs/en/transformer_entries/Phi3Vision.md @@ -0,0 +1,127 @@ +{%- capture title -%} +Phi3Vision +{%- endcapture -%} + +{%- capture description -%} +Visual Question Answering using Phi3Vision. + +Phi3Vision can load Phi3Vision models for visual question answering. +The model consists of a vision encoder, a text encoder as well as a text decoder. +The vision encoder will encode the input image, the text encoder will encode the input question together +with the encoding of the image, and the text decoder will output the answer to the question. + +Pretrained models can be loaded with `pretrained` of the companion object: + +```scala +val visualQA = Phi3Vision.pretrained() + .setInputCols("image_assembler") + .setOutputCol("answer") +``` + +The default model is `"phi_3_vision_128k_instruct"`, if no name is provided. + +For available pretrained models please see the +[Models Hub](https://sparknlp.org/models?task=Question+Answering). + +Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To +see which models are compatible and how to import them see +[Import Transformers into Spark NLP ๐Ÿš€](https://github.com/JohnSnowLabs/spark-nlp/discussions/5669). + +For extended examples of usage, see +[Phi3VisionTestSpec](https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3VisionTest.scala). + +{%- endcapture -%} + +{%- capture input_anno -%} +IMAGE +{%- endcapture -%} + +{%- capture output_anno -%} +DOCUMENT +{%- endcapture -%} + +{%- capture python_example -%} +import sparknlp +from sparknlp.base import * +from sparknlp.annotator import * +from pyspark.ml import Pipeline +from pyspark.sql.functions import lit + +image_df = spark.read.format("image").load(path=images_path) # Replace with your image path +test_df = image_df.withColumn("text", lit("<|user|> \n <|image_1|> \nWhat is unusual on this picture? <|end|>\n <|assistant|>\n")) + +imageAssembler = ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + +visualQAClassifier = Phi3Vision.pretrained("phi_3_vision_128k_instruct","en") + .setInputCols("image_assembler") + .setOutputCol("answer") + +pipeline = Pipeline().setStages([ + imageAssembler, + visualQAClassifier +]) + +result = pipeline.fit(test_df).transform(test_df) +result.select("image_assembler.origin", "answer.result").show(False) +{%- endcapture -%} + +{%- capture scala_example -%} +import spark.implicits._ +import com.johnsnowlabs.nlp.base._ +import com.johnsnowlabs.nlp.annotator._ +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit + +val imageFolder = "path/to/your/images" // Replace with your image path + +val imageDF: DataFrame = spark.read + .format("image") + .option("dropInvalid", value = true) + .load(imageFolder) + +val testDF: DataFrame = imageDF.withColumn("text", lit("<|user|> \n <|image_1|> \nWhat is unusual on this picture? <|end|>\n <|assistant|>\n")) + +val imageAssembler: ImageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + +val visualQAClassifier = Phi3Vision.pretrained("phi_3_vision_128k_instruct","en") + .setInputCols("image_assembler") + .setOutputCol("answer") + +val pipeline = new Pipeline().setStages(Array( + imageAssembler, + visualQAClassifier +)) + +val result = pipeline.fit(testDF).transform(testDF) + +result.select("image_assembler.origin", "answer.result").show(false) +{%- endcapture -%} + +{%- capture api_link -%} +[Phi3Vision](https://www.google.com/url?sa=E&source=gmail&q=/api/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision) +{%- endcapture -%} + +{%- capture python_api_link -%} +[Phi3Vision](https://www.google.com/url?sa=E&source=gmail&q=/api/python/reference/autosummary/sparknlp/annotator/cv/phi3_vision/index.html#sparknlp.annotator.cv.phi3_vision.Phi3Vision) +{%- endcapture -%} + +{%- capture source_link -%} +[Phi3Vision](https://www.google.com/url?sa=E&source=gmail&q=https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Phi3Vision.scala) +{%- endcapture -%} + +{% include templates/anno_template.md +title=title +description=description +input_anno=input_anno +output_anno=output_anno +python_example=python_example +scala_example=scala_example +api_link=api_link +python_api_link=python_api_link +source_link=source_link +%} \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala index 0e457d4d6e20df..a08cad1fa95b38 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala @@ -697,7 +697,8 @@ object PythonResourceDownloader { "NLLBTransformer" -> NLLBTransformer, "Phi3Transformer" -> Phi3Transformer, "QwenTransformer" -> QwenTransformer, - "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings) + "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings, + "Phi3Vision" -> Phi3Vision) // List pairs of types such as the one with key type can load a pretrained model from the value type val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering") From 1af39be746ceee82f2581279fcdf936c28a79ff8 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Fri, 1 Nov 2024 07:13:30 +0000 Subject: [PATCH 044/108] LLAVA Scala API and Tests Signed-off-by: Prabod Rathnayaka --- .../scala/com/johnsnowlabs/ml/ai/LLaVA.scala | 511 +++++++++++++++ .../ml/openvino/OpenvinoWrapper.scala | 5 + .../annotators/cv/LLAVAForMultiModal.scala | 607 ++++++++++++++++++ .../tokenizer/bpe/BpeTokenizer.scala | 11 +- .../tokenizer/bpe/LLAVATokenizer.scala | 111 ++++ .../cv/LLAVAForMultiModalTestSpec.scala | 190 ++++++ 6 files changed, 1434 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/com/johnsnowlabs/ml/ai/LLaVA.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/LLAVATokenizer.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/LLaVA.scala b/src/main/scala/com/johnsnowlabs/ml/ai/LLaVA.scala new file mode 100644 index 00000000000000..067d44a3418693 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/ml/ai/LLaVA.scala @@ -0,0 +1,511 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.ml.ai + +import breeze.optimize.BatchSize +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.LLAVAWrappers +import com.johnsnowlabs.nlp.annotators.common.Sentence +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.annotators.common.SentenceSplit +import com.johnsnowlabs.nlp.annotators.cv.util.transform.ImageResizeUtils + +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils +import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{BpeTokenizer, LLAVATokenizer, SpecialTokens} +import org.intel.openvino.InferRequest + +import scala.collection.JavaConverters._ + +private[johnsnowlabs] class LLaVA( + val onnxWrappers: Option[DecoderWrappers], + val openvinoWrapper: Option[LLAVAWrappers], + merges: Map[(String, String), Int], + vocabulary: Map[String, Int], + addedTokens: Map[String, Int], + preprocessor: Preprocessor, + generationConfig: GenerationConfig, + imageTokenLength: Int, + imageToken: Int) + extends Serializable { + + val detectedEngine: String = + if (onnxWrappers.isDefined) ONNX.name + else if (openvinoWrapper.isDefined) Openvino.name + else Openvino.name + + private val GenerationConfig( + bosTokenId: Int, + paddingTokenId: Int, + eosTokenId: Int, + vocabSize: Int, + beginSuppressTokens, + suppressTokenIds, + forcedDecoderIds) = + generationConfig + val reversedVocabulary: Map[Int, String] = vocabulary.map(_.swap) + + val specialTokens: SpecialTokens = SpecialTokens( + vocabulary, + startTokenString = reversedVocabulary(bosTokenId), + endTokenString = reversedVocabulary(eosTokenId), + unkTokenString = reversedVocabulary(eosTokenId), + maskTokenString = reversedVocabulary(eosTokenId), + padTokenString = reversedVocabulary(paddingTokenId), + additionalStrings = addedTokens.keys.toArray) + + val bpeTokenizer: LLAVATokenizer = BpeTokenizer + .forModel( + "llava", + merges = merges, + vocab = vocabulary, + specialTokens = Some(specialTokens), + addPrefixSpaceToSentence = false, + alwaysAddPrefix = false, + prependString = "") + .asInstanceOf[LLAVATokenizer] + + /** Decode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of decoded sentences + */ + def decode(sentences: Array[Array[Int]]): Seq[String] = { + sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt))) + } + + /** Encode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of encoded sentences + */ + def encodeText(sentences: Seq[Annotation], imgTokenLen: List[Int]): Seq[Array[Int]] = { + + val pattern = raw"<\|image\|>".r + + // raise an error if the pattern is not found in the text + if (pattern.findFirstIn(sentences.head.result).isEmpty) { + throw new IllegalArgumentException("The pattern <\\|image\\|> is not found in the text") + } + + // split the sentences into chunks based on the pattern and tokenize them + // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)] + val promptChunks = sentences + .map(s => { + val sentWithTask = s.result + var offsetLength = 0 + pattern + .split(sentWithTask) + .zipWithIndex + .map(s => { + val sentenceWithTask = Sentence( + content = s._1, + start = offsetLength, + end = offsetLength + s._1.length, + index = s._2) + offsetLength += s._1.length + bpeTokenizer + .tokenize(sentenceWithTask) + .map(bpeTokenizer.encode) + .flatMap(_.map(_.pieceId)) + }) + }) + + // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]] + val tokens = promptChunks + .zip(imgTokenLen) + .map(s => { + val (promptChunk, imgTokenLen) = s + val imgPaddingTokens = Array.fill(imgTokenLen)(imageToken) + val combinedChunks = promptChunk + .map(_.toArray) + .reduce(_ ++ imgPaddingTokens ++ _) + Array(bosTokenId) ++ combinedChunks + }) + + // val tokens = SentenceSplit + // .unpack(sentences) + // .map(s => { + // val sentWithTask = s + // bpeTokenizer + // .tokenize(sentWithTask) + // .map(bpeTokenizer.encode) + // .flatMap(_.map(_.pieceId)) + // }) + tokens + } + + def encode( + imageAnnotations: Seq[AnnotationImage], + sentences: Seq[Annotation], + preprocessor: Preprocessor, + imageTokenLength: Int = imageTokenLength) + : (Seq[Array[Int]], Array[Array[Array[Array[Float]]]]) = { + val preprocessedImages = encodeImage(imageAnnotations.toArray, preprocessor) + val encodedText = encodeText(sentences, List(imageTokenLength)).toArray + + (encodedText, preprocessedImages) + } + + def tag( + batch: Seq[Array[Int]], + images: Array[Array[Array[Array[Float]]]], + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long], + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int, + stopTokenIds: Array[Int]): Array[Array[Int]] = { + + val pixelValues = images + val ignoreTokenIdsInt = ignoreTokenIds + val expandedDecoderInputsVals = batch + val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray + val maxSentenceLength = sequencesLength.max // - curLen + // val pixelValues = images._1 + // val imageSizes = images._2 + val numReturn_sequences = 1 + // from config + + var effectiveBatch_size = 1 + var effectiveBatch_mult = 1 + + if (doSample) { + effectiveBatch_size = expandedDecoderInputsVals.length * numReturn_sequences + effectiveBatch_mult = numReturn_sequences + } else { + effectiveBatch_size = expandedDecoderInputsVals.length + effectiveBatch_mult = 1 + } + + val inferRequestLanguageModel = + openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request() + val inferRequestVisionEmbeddingsModel = + openvinoWrapper.get.visionEmbeddingsModel.getCompiledModel().create_infer_request() + val inferRequestTextEmbeddingsModel = + openvinoWrapper.get.textEmbeddingsModel.getCompiledModel().create_infer_request() + val inferRequestMergeModel = + openvinoWrapper.get.mergeModel.getCompiledModel().create_infer_request() + + val generatedIds = generateGreedy( + batch.toArray, + batch.toArray, + pixelValues, + maxOutputLength, + inferRequestLanguageModel, + inferRequestVisionEmbeddingsModel, + inferRequestTextEmbeddingsModel, + inferRequestMergeModel) + generatedIds + } + + def generateGreedy( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Float]]]], + maxOutputLength: Int, + inferRequestLanguageModel: InferRequest, + inferRequestVisionEmbeddingsModel: InferRequest, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestMergeModel: InferRequest): Array[Array[Int]] = { + + var generatedIds: Array[Array[Int]] = Array() + var decoderInputIdsCopied = decoderInputIds + while (!greedyGenerationFinished(generatedIds, eosTokenId, maxOutputLength)) { + val decoderOutputs = getModelOutputs( + encoderInputIds, + decoderInputIdsCopied, + pixelValues, + inferRequestLanguageModel, + inferRequestVisionEmbeddingsModel, + inferRequestTextEmbeddingsModel, + inferRequestMergeModel) + + val nextTokenIds = decoderOutputs.map { scores => + argmax(scores) + } + + if (generatedIds.isEmpty) { + generatedIds = nextTokenIds.map(Array(_)) + } else { + generatedIds = + generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => + currentIds ++ Array(nextId) + } + } + + // extend decoder input ids + decoderInputIdsCopied = + decoderInputIdsCopied.zip(nextTokenIds).map { case (currentIds, nextId) => + currentIds ++ Array(nextId) + } + } + generatedIds + } + + def predict( + sentences: Seq[Annotation], + imageAnnotations: Seq[AnnotationImage], + batchSize: Int, + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long] = None, + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int): Seq[Annotation] = { + + val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences, preprocessor) + val tagged = tag( + encodedText, + preprocessedImages, + minOutputLength, + maxOutputLength, + doSample, + temperature, + topK, + topP, + repetitionPenalty, + noRepeatNgramSize, + randomSeed, + ignoreTokenIds, + beamSize, + maxInputLength, + Array(eosTokenId)) + val decoded = decode(tagged) + + var sentBegin, nextSentEnd = 0 + val annotations = decoded.map { content => + nextSentEnd += content.length - 1 + val annots = new Annotation( + annotatorType = DOCUMENT, + begin = sentBegin, + end = nextSentEnd, + result = content, + metadata = Map()) + sentBegin += nextSentEnd + 1 + annots + } + annotations + } + + def getModelOutputs( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Float]]]], + inferRequestLanguageModel: InferRequest, + inferRequestVisionEmbeddingsModel: InferRequest, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestMergeModel: InferRequest): Array[Array[Float]] = { + + val inputEmbeds = getMultimodalEmbeddings( + encoderInputIds, + decoderInputIds, + pixelValues, + inferRequestVisionEmbeddingsModel, + inferRequestTextEmbeddingsModel, + inferRequestMergeModel) + + val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + val posIdsLong = decoderInputIds.flatMap { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + } + } + (inpIdsLong, posIdsLong) + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + val posIdsLong = decoderInputIds.map { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + }.last + } + (inpIdsLong, posIdsLong) + } + val attentionMask: Array[Long] = + decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) } + + val batchSize: Int = decoderInputIds.length + val beamIdx: Array[Int] = new Array[Int](batchSize) + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + + val decoderAttentionMask: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask) + val decoderPositionIDs: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputPositionIDsLong) + val beamIdxTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize), beamIdx) + + inferRequestLanguageModel.set_tensor("inputs_embeds", inputEmbeds) + inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask) + inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs) + inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor) + + inferRequestLanguageModel.infer() + + val result = inferRequestLanguageModel.get_tensor("logits") + val logitsRaw = result.data() + + val sequenceLength = inputIdsLong.length / batchSize + val decoderOutputs = (0 until batchSize).map(i => { + logitsRaw + .slice( + i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize, + i * sequenceLength * vocabSize + sequenceLength * vocabSize) + }) + decoderOutputs.toArray + } + + private def argmax(scores: Array[Float]): Int = + scores.zipWithIndex.maxBy { case (score, _) => + score + }._2 + + private def greedyGenerationFinished( + decoderIds: Seq[Array[Int]], + eosTokenId: Int, + maxOutputLength: Int): Boolean = { + if (decoderIds.isEmpty) { + false + } else { + decoderIds.forall { ids => + ids.length >= maxOutputLength || ids.last == eosTokenId + } + } + } + + private def encodeImage( + annotations: Array[AnnotationImage], + preprocessor: Preprocessor): Array[Array[Array[Array[Float]]]] = { + + val batchProcessedImages = annotations.map { annot => + val bufferedImage = ImageIOUtils.byteToBufferedImage( + bytes = annot.result, + w = annot.width, + h = annot.height, + nChannels = annot.nChannels) + + val resizedImage = if (preprocessor.do_resize) { + ImageResizeUtils.resizeBufferedImage( + width = preprocessor.size, + height = preprocessor.size, + preprocessor.resample)(bufferedImage) + } else bufferedImage + + val normalizedImage = + ImageResizeUtils.normalizeAndConvertBufferedImage( + img = resizedImage, + mean = preprocessor.image_mean, + std = preprocessor.image_std, + doNormalize = preprocessor.do_normalize, + doRescale = preprocessor.do_rescale, + rescaleFactor = preprocessor.rescale_factor) + + normalizedImage + } + + batchProcessedImages + + } + + def getMultimodalEmbeddings( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: Array[Array[Array[Array[Float]]]], + inferRequestVisionEmbeddingsModel: InferRequest, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestMergeModel: InferRequest): org.intel.openvino.Tensor = { + val inputIdsLong: Array[Long] = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + + inpIdsLong + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + inpIdsLong + } + val batchSize: Int = decoderInputIds.length + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + val inputIdsLongTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputIdsLong) + + val imageEmbeddings: org.intel.openvino.Tensor = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + val pixelValuesTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor( + Array(batchSize, 3, 336, 336), + pixelValues.flatten.flatten.flatten.map(_.toFloat)) + + // Get image embeddings + inferRequestVisionEmbeddingsModel.set_input_tensor(pixelValuesTensor) + + inferRequestVisionEmbeddingsModel.infer() + + val imageEmbeddings = inferRequestVisionEmbeddingsModel.get_output_tensor() + + // Get text embeddings + inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor) + + inferRequestTextEmbeddingsModel.infer() + + val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor() + + // Merge image and text embeddings + inferRequestMergeModel.set_tensor("vision_embeds", imageEmbeddings) + inferRequestMergeModel.set_tensor("inputs_embeds", textEmbeddings) + inferRequestMergeModel.set_tensor("input_ids", inputIdsLongTensor) + + inferRequestMergeModel.infer() + + inferRequestMergeModel.get_tensor("final_embedding") + } else { + // Get text embeddings + inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor) + + inferRequestTextEmbeddingsModel.infer() + + val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor() + + textEmbeddings + } + imageEmbeddings + } + +} diff --git a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala index 0c2f65d4315e4e..19710c36fe252d 100644 --- a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala +++ b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala @@ -218,4 +218,9 @@ object OpenvinoWrapper { decoderWithPast: OpenvinoWrapper) case class DecoderWrappers(decoder: OpenvinoWrapper) case class EncoderDecoderWithoutPastWrappers(encoder: OpenvinoWrapper, decoder: OpenvinoWrapper) + case class LLAVAWrappers( + languageModel: OpenvinoWrapper, + visionEmbeddingsModel: OpenvinoWrapper, + textEmbeddingsModel: OpenvinoWrapper, + mergeModel: OpenvinoWrapper) } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala new file mode 100644 index 00000000000000..dc823cb5d1f011 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala @@ -0,0 +1,607 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.ai.LLaVA +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadJsonStringAsset, + loadTextAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.ml.util.Openvino +import com.johnsnowlabs.nlp.AnnotatorType.{DOCUMENT, IMAGE} +import com.johnsnowlabs.nlp._ +import org.json4s.{DefaultFormats, JValue} +import org.json4s.jackson.JsonMethods.parse +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.LLAVAWrappers +import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntArrayParam, IntParam} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +/** LLAVAForMultiModal can load LLAVA Vision models for visual question answering. The model + * consists of a vision encoder, a text encoder as well as a text decoder. The vision encoder + * will encode the input image, the text encoder will encode the input question together with the + * encoding of the image, and the text decoder will output the answer to the question. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val visualQA = LLAVAForMultiModal.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * }}} + * The default model is `"llava"`, if no name is provided. + * + * For available pretrained models please see the + * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTest.scala]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val imageDF: DataFrame = ResourceHelper.spark.read + * .format("image") + * .option("dropInvalid", value = true) + * .load(imageFolder) + * + * val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n <|image|> \nWhat is unusual on this picture? \n ASSISTANT:\n")) + * + * val imageAssembler: ImageAssembler = new ImageAssembler() + * .setInputCol("image") + * .setOutputCol("image_assembler") + * + * val visualQAClassifier = LLAVAForMultiModal.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * + * val pipeline = new Pipeline().setStages(Array( + * imageAssembler, + * visualQAClassifier + * )) + * + * val result = pipeline.fit(testDF).transform(testDF) + * + * result.select("image_assembler.origin", "answer.result").show(false) + * +--------------------------------------+------+ + * |origin |result| + * +--------------------------------------+------+ + * |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]| + * +--------------------------------------+------+ + * }}} + * + * @see + * [[CLIPForZeroShotClassification]] for Zero Shot Image Classifier + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ + +class LLAVAForMultiModal(override val uid: String) + extends AnnotatorModel[LLAVAForMultiModal] + with HasBatchedAnnotateImage[LLAVAForMultiModal] + with HasImageFeatureProperties + with WriteOpenvinoModel + with HasGeneratorProperties + with HasEngine { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("LLAVAForMultiModal")) + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(IMAGE) + override val outputAnnotatorType: AnnotatorType = DOCUMENT + + /** @group setParam */ + def setRandomSeed(value: Int): LLAVAForMultiModal.this.type = { + if (randomSeed.isEmpty) { + this.randomSeed = Some(value) + } + this + } + + /** A list of token ids which are ignored in the decoder's output (Default: `Array()`) + * + * @group param + */ + var ignoreTokenIds = new IntArrayParam( + this, + "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output") + + /** @group setParam */ + def setIgnoreTokenIds(tokenIds: Array[Int]): LLAVAForMultiModal.this.type = { + set(ignoreTokenIds, tokenIds) + } + + /** @group getParam */ + def getIgnoreTokenIds: Array[Int] = $(ignoreTokenIds) + + /** Vocabulary used to encode the words to ids with bpeTokenizer.encode + * + * @group param + */ + val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() + + /** @group setParam */ + def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value) + + /** Holding merges.txt coming from RoBERTa model + * + * @group param + */ + val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected() + + /** @group setParam */ + def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value) + + /** Additional tokens to be added to the vocabulary + * + * @group param + */ + val addedTokens: MapFeature[String, Int] = new MapFeature(this, "addedTokens").setProtected() + + /** @group setParam */ + def setAddedTokens(value: Map[String, Int]): this.type = set(addedTokens, value) + + /** Stop tokens to terminate the generation + * + * @group param + */ + override val stopTokenIds = + new IntArrayParam(this, "stopTokenIds", "Stop tokens to terminate the generation") + + /** @group setParam */ + override def setStopTokenIds(value: Array[Int]): this.type = { + set(stopTokenIds, value) + } + + /** @group getParam */ + override def getStopTokenIds: Array[Int] = $(stopTokenIds) + + private var _model: Option[Broadcast[LLaVA]] = None + val generationConfig: StructFeature[GenerationConfig] = + new StructFeature(this, "generationConfig").setProtected() + + def setGenerationConfig(value: GenerationConfig): this.type = + set(generationConfig, value) + + def getGenerationConfig: GenerationConfig = $$(generationConfig) + + val imageToken = + new IntParam(this, "imageToken", "Token id for image embeddings") + + /** @group setParam */ + def setImageToken(value: Int): this.type = set(imageToken, value) + + /** @group getParam */ + def getImageToken: Int = $(imageToken) + + val imageTokenLength = + new IntParam(this, "imageTokenLength", "Token length for image embeddings") + + /** @group setParam */ + def setImageTokenLength(value: Int): this.type = set(imageTokenLength, value) + + /** @group getParam */ + def getImageTokenLength: Int = $(imageTokenLength) + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + preprocessor: Preprocessor, + onnxWrappers: Option[DecoderWrappers], + openvinoWrapper: Option[LLAVAWrappers]): this.type = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new LLaVA( + onnxWrappers, + openvinoWrapper, + $$(merges), + $$(vocabulary), + $$(addedTokens), + preprocessor, + generationConfig = getGenerationConfig, + imageToken = getImageToken, + imageTokenLength = getImageTokenLength))) + } + this + } + + /** @group getParam */ + def getModelIfNotSet: LLaVA = _model.get.value + + setDefault( + minOutputLength -> 0, + maxOutputLength -> 20, + doSample -> false, + temperature -> 0.6, + topK -> -1, + topP -> 0.9, + repetitionPenalty -> 1.0, + noRepeatNgramSize -> 3, + ignoreTokenIds -> Array(), + batchSize -> 1, + beamSize -> 1, + maxInputLength -> 4096, + stopTokenIds -> Array(2), + imageToken -> 32000, + imageTokenLength -> 576) + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + */ + override def batchAnnotate( + batchedAnnotations: Seq[Array[AnnotationImage]]): Seq[Seq[Annotation]] = { + + batchedAnnotations + // .filter { annotationImages => + // annotationImages.exists(_.text.nonEmpty) + // } + .map { cleanAnnotationImages => + val validImages = cleanAnnotationImages.filter(_.result.nonEmpty) + val questionAnnotations = extractInputAnnotation(validImages) + + getModelIfNotSet.predict( + questionAnnotations, + validImages.toSeq, + batchSize = $(batchSize), + minOutputLength = $(minOutputLength), + maxOutputLength = $(maxOutputLength), + doSample = $(doSample), + temperature = $(temperature), + topK = $(topK), + topP = $(topP), + repetitionPenalty = $(repetitionPenalty), + noRepeatNgramSize = $(noRepeatNgramSize), + randomSeed = this.randomSeed, + ignoreTokenIds = $(ignoreTokenIds), + beamSize = $(beamSize), + maxInputLength = $(maxInputLength)) + } + } + + private def extractInputAnnotation( + annotationImages: Array[AnnotationImage]): Seq[Annotation] = { + val questions = annotationImages.map(annotationImage => { + val imageText = + if (annotationImage.text.nonEmpty) annotationImage.text + else + "<|user|> \n <|image|> This is an image\n <|end|>\n <|assistant|>\n" // default question + Annotation(imageText) + }) + + questions + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + getEngine match { + case Openvino.name => + val wrappers = getModelIfNotSet.openvinoWrapper + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.languageModel, "openvino_language_model.xml")), + LLAVAForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.visionEmbeddingsModel, "openvino_vision_embeddings_model.xml")), + LLAVAForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.textEmbeddingsModel, "openvino_text_embeddings_model.xml")), + LLAVAForMultiModal.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.mergeModel, "openvino_merge_model.xml")), + LLAVAForMultiModal.suffix) + case _ => + throw new Exception(notSupportedEngineError) + } + } + +} + +trait ReadablePretrainedLLAVAForMultiModal + extends ParamsAndFeaturesReadable[LLAVAForMultiModal] + with HasPretrained[LLAVAForMultiModal] { + + override val defaultModelName: Some[String] = Some("llava") + + /** Java compliant-overrides */ + override def pretrained(): LLAVAForMultiModal = super.pretrained() + + override def pretrained(name: String): LLAVAForMultiModal = + super.pretrained(name) + + override def pretrained(name: String, lang: String): LLAVAForMultiModal = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): LLAVAForMultiModal = + super.pretrained(name, lang, remoteLoc) + +} + +trait ReadLLAVAForMultiModalDLModel extends ReadOpenvinoModel { + this: ParamsAndFeaturesReadable[LLAVAForMultiModal] => + val suffix: String = "_llava" + override val openvinoFile: String = "llava_openvino" + def readModel(instance: LLAVAForMultiModal, path: String, spark: SparkSession): Unit = { + instance.getEngine match { + case Openvino.name => + val languageModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_language_model.xml"), suffix) + + val visionEmbeddingsModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_vision_embeddings_model.xml"), suffix) + + val textEmbeddingsModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_text_embeddings_model.xml"), suffix) + + val mergeModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_merge_model.xml"), suffix) + + val ovWrapper = LLAVAWrappers( + languageModel = languageModelWrappers("openvino_language_model.xml"), + visionEmbeddingsModel = + visionEmbeddingsModelWrappers("openvino_vision_embeddings_model.xml"), + textEmbeddingsModel = textEmbeddingsModelWrappers("openvino_text_embeddings_model.xml"), + mergeModel = mergeModelWrappers("openvino_merge_model.xml")) + val preprocessor = Preprocessor( + do_normalize = true, + do_resize = true, + "LLAVAFeatureExtractor", + instance.getImageMean, + instance.getImageStd, + instance.getResample, + instance.getSize) + instance.setModelIfNotSet(spark, preprocessor, None, Some(ovWrapper)) + case _ => { + throw new Exception(notSupportedEngineError) + } + } + } + + addReader(readModel) + + def loadSavedModel( + modelPath: String, + spark: SparkSession, + useOpenvino: Boolean = false): LLAVAForMultiModal = { + implicit val formats: DefaultFormats.type = DefaultFormats // for json4 + val (localModelPath, detectedEngine) = + modelSanityCheck( + modelPath, + isDecoder = false, + custom = Some( + List( + "openvino_language_model", + "openvino_vision_embeddings_model", + "openvino_text_embeddings_model", + "openvino_merge_model"))) + val modelConfig: JValue = + parse(loadJsonStringAsset(localModelPath, "config.json")) + val preprocessorConfigJsonContent = + loadJsonStringAsset(localModelPath, "preprocessor_config.json") + val preprocessorConfig = Preprocessor.loadPreprocessorConfig(preprocessorConfigJsonContent) + val beginSuppressTokens: Array[Int] = + (modelConfig \ "begin_suppress_tokens").extract[Array[Int]] + + val suppressTokenIds: Array[Int] = + (modelConfig \ "suppress_tokens").extract[Array[Int]] + + val forcedDecoderIds: Array[(Int, Int)] = + (modelConfig \ "forced_decoder_ids").extract[Array[Array[Int]]].map { + case idxWithTokenId: Array[Int] if idxWithTokenId.length == 2 => + (idxWithTokenId(0), idxWithTokenId(1)) + case _ => + throw new Exception( + "Could not extract forced_decoder_ids. Should be a list of tuples with 2 entries.") + } + + def arrayOrNone[T](array: Array[T]): Option[Array[T]] = + if (array.nonEmpty) Some(array) else None + + val bosTokenId = (modelConfig \ "text_config" \ "bos_token_id").extract[Int] + val eosTokenId = (modelConfig \ "text_config" \ "eos_token_id").extract[Int] + val padTokenId = (modelConfig \ "text_config" \ "eos_token_id").extract[Int] + val vocabSize = (modelConfig \ "text_config" \ "vocab_size").extract[Int] + + val imageToken = (modelConfig \ "image_token_index").extract[Int] + val imageTokenLength = (modelConfig \ "image_seq_length").extract[Int] + + // Check if tokenizer.json exists + val tokenizerPath = s"$localModelPath/assets/tokenizer.json" + val tokenizerExists = new java.io.File(tokenizerPath).exists() + val (vocabs, addedTokens, bytePairs) = if (tokenizerExists) { + val tokenizerConfig: JValue = parse(loadJsonStringAsset(localModelPath, "tokenizer.json")) + // extract vocab from tokenizer.json ( model -> vocab) + var vocabs: Map[String, Int] = + (tokenizerConfig \ "model" \ "vocab").extract[Map[String, Int]] + + // extract merges from tokenizer.json ( model -> merges) + val bytePairs = (tokenizerConfig \ "model" \ "merges") + .extract[List[Array[String]]] + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + + // extract added_tokens from tokenizer.json (added_tokens) + // "added_tokens": [ + // { + // "id": 128000, + // "content": "<|begin_of_text|>", + // "single_word": false, + // "lstrip": false, + // "rstrip": false, + // "normalized": false, + // "special": true + // }, ... + // ] + val addedTokens = (tokenizerConfig \ "added_tokens") + .extract[List[Map[String, Any]]] + .map { token => + val id = token("id").asInstanceOf[BigInt].intValue() + val content = token("content").asInstanceOf[String] + (content, id) + } + .toMap + + // update vocab with added tokens + addedTokens.foreach { case (content, id) => + vocabs += (content -> id) + } + (vocabs, addedTokens, bytePairs) + } else { + val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap + val addedTokens = loadTextAsset(localModelPath, "added_tokens.txt").zipWithIndex.toMap + val bytePairs = loadTextAsset(localModelPath, "merges.txt") + .map(_.split(" ")) + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + (vocabs, addedTokens, bytePairs) + } + + val annotatorModel = new LLAVAForMultiModal() + .setGenerationConfig( + GenerationConfig( + bosTokenId, + padTokenId, + eosTokenId, + vocabSize, + arrayOrNone(beginSuppressTokens), + arrayOrNone(suppressTokenIds), + arrayOrNone(forcedDecoderIds))) + .setVocabulary(vocabs) + .setMerges(bytePairs) + .setAddedTokens(addedTokens) + .setImageToken(imageToken) + .setImageTokenLength(imageTokenLength) + + val modelEngine = + if (useOpenvino) + Openvino.name + else + detectedEngine + annotatorModel.set(annotatorModel.engine, modelEngine) + + detectedEngine match { + case Openvino.name => + val visionWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_vision_embeddings_model") + val textWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_text_embeddings_model") + val mergeWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_merge_model") + val languageModelWrapper = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_language_model") + + val openvinoWrapper = LLAVAWrappers( + languageModel = languageModelWrapper, + visionEmbeddingsModel = visionWrapper, + textEmbeddingsModel = textWrapper, + mergeModel = mergeWrapper) + annotatorModel.setModelIfNotSet(spark, preprocessorConfig, None, Some(openvinoWrapper)) + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } +} + +object LLAVAForMultiModal + extends ReadablePretrainedLLAVAForMultiModal + with ReadLLAVAForMultiModalDLModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala index 8c72a8f99d6685..175e7b7f20f138 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala @@ -319,7 +319,8 @@ object BpeTokenizer { padWithSequenceTokens: Boolean = false, addPrefixSpaceToSentence: Boolean = false, specialTokens: Option[SpecialTokens] = None, - alwaysAddPrefix: Boolean = true): BpeTokenizer = { + alwaysAddPrefix: Boolean = true, + prependString: String = ""): BpeTokenizer = { def modelSpecialTokens() = specialTokens match { case Some(specialTok) => specialTok @@ -382,6 +383,14 @@ object BpeTokenizer { modelSpecialTokens(), padWithSequenceTokens, addPrefixSpaceToSentence = addPrefixSpaceToSentence) + case "llava" => + new LLAVATokenizer( + merges, + vocab, + modelSpecialTokens(), + padWithSequenceTokens, + addPrefixSpaceToSentence = addPrefixSpaceToSentence, + prependString = prependString) case _ => throw new IllegalArgumentException("Model type \"" + modelType + "\" not supported yet.") } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/LLAVATokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/LLAVATokenizer.scala new file mode 100644 index 00000000000000..4b2388b5524820 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/LLAVATokenizer.scala @@ -0,0 +1,111 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.tokenizer.bpe + +import com.johnsnowlabs.nlp.annotators.common.IndexedToken + +import java.nio.charset.Charset +import scala.collection.mutable.ListBuffer +import scala.util.matching.Regex +import scala.collection.mutable + +class LLAVATokenizer( + merges: Map[(String, String), Int], + vocab: Map[String, Int], + specialTokens: SpecialTokens, + padWithSequenceTokens: Boolean = true, + prependString: String = "", + addPrefixSpaceToSentence: Boolean = false, + alwaysAddPrefix: Boolean = true, + splitPatternRegex: Regex = + raw"""(?i)(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".r) + extends BpeTokenizer( + merges, + vocab, + specialTokens, + padWithSequenceTokens, + addPrefixSpaceToSentence, + alwaysAddPrefix) { + + /** Mapping for bytes to a different set of unicode characters (especially white spaces). This + * improved model performance for gpt-2 + */ + protected val bytesToUnicodeMapping: Map[Int, String] = { + val bytes: ListBuffer[Int] = + ListBuffer.range('!', '~' + 1) ++ ListBuffer.range('ยก', 'ยฌ' + 1) ++ ListBuffer + .range('ยฎ', 'รฟ' + 1) + val characters: ListBuffer[Int] = bytes.clone + var n = 0 + for (b <- 0 to 256) { + if (!bytes.contains(b)) { + bytes += b + characters += (256 + n) + n += 1 + } + } + (bytes zip characters.map(_.toChar.toString)).toMap + } + + // Differs from Transformers, space is always prepended. + // FIX: Space should not be prepended to all tokens, but to the beginning of the text only. Otherwise token + // such as '.' get space prepended and they should not. + override val prefixForPieceId: Option[String] = + if (prependString.nonEmpty) Some(prependString) else None + + protected val decoderVocab: Map[Int, String] = vocab.map(x => (x._2, x._1)) + + protected val unicodeToByteMapping: Map[String, Int] = + bytesToUnicodeMapping.map(x => (x._2, x._1)) + + override def preProcessTokenForBpe(token: String): String = { + token + .getBytes("UTF-8") + .map { b => if (b < 0) 256 + b else b } + .foldLeft("")(_ + bytesToUnicodeMapping(_)) + } + + val splitPattern: Regex = splitPatternRegex + + override def tokenizeSubText(text: String, indexOffset: Int): Array[IndexedToken] = { + // split pattern based on gpt2's bpe tokenizer + splitPattern + .findAllMatchIn(if (prefixForPieceId.isDefined || text.startsWith(" ")) text + else " " + text) // Prepend space to the beginning of text + .map(tok => IndexedToken(tok.matched, tok.start + indexOffset, tok.end + indexOffset - 1)) + .toArray + } + + def decodeTokens(tokens: Array[Int]): String = { + val decoded = new mutable.StringBuilder() + tokens.foreach { token => + { + val decodedToken = decoderVocab(token) + if (!specialTokens.contains(decodedToken)) { + if (decodedToken.startsWith("<0x") && decodedToken.endsWith(">")) { + val strippedHex = decodedToken.replaceAll("<0x|>", "") + val byteValue = Integer.parseInt(strippedHex, 16) + decoded.append(byteValue.toChar) + } else { + decoded.append(decodedToken) + } + } + } + + } + decoded.toString().replaceAll(decoderVocab(29871), " ").trim() + } +} diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala new file mode 100644 index 00000000000000..48c765020bd4dd --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala @@ -0,0 +1,190 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, ImageAssembler} +import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit +import org.scalatest.flatspec.AnyFlatSpec + +class LLAVAForMultiModalTestSpec extends AnyFlatSpec { + + lazy val model = getLLAVAForMultiModalPipelineModel + + "LLAVAForMultiModal" should "answer a question for a given image" taggedAs FastTest in { + + val testDF = getTestDF + val result = model.transform(testDF) + + val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") + + answerAnnotation.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + + answerAnnotation.foreach { annotation => + annotation.foreach(a => println(a.result)) + } + + } + + it should "work with light pipeline annotate" taggedAs FastTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/image/egyptian_cat.jpeg" + val resultAnnotate = + lightPipeline.annotate( + imagePath, + "USER: \n <|image|> \n What is unusual on this picture? \n ASSISTANT:\n") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.contains("cat")) + } + + it should "work with light pipeline full annotate" taggedAs FastTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/image/bluetick.jpg" + val resultFullAnnotate = + lightPipeline.fullAnnotateImage( + imagePath, + "USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + println(s"imageName.result: ${answerAnnotation.result}") + assert(answerAnnotation.result.nonEmpty) + } + + it should "fullAnnotate with empty Map when a text is empty" taggedAs FastTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n" + val questions = Array(question, "", question) + + val resultFullAnnotate = lightPipeline.fullAnnotateImages(imagesPath, questions) + + resultFullAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { iAnnotation => + val annotation = iAnnotation.asInstanceOf[Annotation] + assert( + annotation.result.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + } + + it should "annotate with empty Map when a text is empty" taggedAs FastTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n" + val questions = Array(question, "", question) + + val resultAnnotate = lightPipeline.annotate(imagesPath, questions) + + resultAnnotate.foreach { annotate => + println(s"annotate: $annotate") + } + + resultAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { annotation => + assert( + annotation.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + + } + + private def getLLAVAForMultiModalPipelineModel = { + val testDF = getTestDF + + val imageAssembler: ImageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + + val loadModel = LLAVAForMultiModal + .loadSavedModel( + "/mnt/research/Projects/ModelZoo/LLAVA/llava-1.5-7b-hf/INT4", + ResourceHelper.spark) + .setInputCols("image_assembler") + .setOutputCol("answer") + .setMaxOutputLength(50) + + val newPipeline: Pipeline = + new Pipeline().setStages(Array(imageAssembler, loadModel)) + + newPipeline.fit(testDF) + } + + private def getTestDF: DataFrame = { + val imageFolder = "src/test/resources/image/" + val imageDF: DataFrame = ResourceHelper.spark.read + .format("image") + .option("dropInvalid", value = true) + .load(imageFolder) + + val testDF: DataFrame = imageDF.withColumn( + "text", + lit("USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n")) + + testDF + } + +} From b5872e71007b9ad67ea5402508aa7ddf29be5bc8 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Fri, 1 Nov 2024 07:14:13 +0000 Subject: [PATCH 045/108] LLAVA Test Signed-off-by: Prabod Rathnayaka --- .../nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala index 48c765020bd4dd..2d7501fee7cace 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala @@ -29,7 +29,7 @@ class LLAVAForMultiModalTestSpec extends AnyFlatSpec { lazy val model = getLLAVAForMultiModalPipelineModel - "LLAVAForMultiModal" should "answer a question for a given image" taggedAs FastTest in { + "LLAVAForMultiModal" should "answer a question for a given image" taggedAs SlowTest in { val testDF = getTestDF val result = model.transform(testDF) @@ -46,7 +46,7 @@ class LLAVAForMultiModalTestSpec extends AnyFlatSpec { } - it should "work with light pipeline annotate" taggedAs FastTest in { + it should "work with light pipeline annotate" taggedAs SlowTest in { val lightPipeline = new LightPipeline(model) val imagePath = "src/test/resources/image/egyptian_cat.jpeg" val resultAnnotate = @@ -58,7 +58,7 @@ class LLAVAForMultiModalTestSpec extends AnyFlatSpec { assert(resultAnnotate("answer").head.contains("cat")) } - it should "work with light pipeline full annotate" taggedAs FastTest in { + it should "work with light pipeline full annotate" taggedAs SlowTest in { val lightPipeline = new LightPipeline(model) val imagePath = "src/test/resources/image/bluetick.jpg" val resultFullAnnotate = @@ -72,7 +72,7 @@ class LLAVAForMultiModalTestSpec extends AnyFlatSpec { assert(answerAnnotation.result.nonEmpty) } - it should "fullAnnotate with empty Map when a text is empty" taggedAs FastTest in { + it should "fullAnnotate with empty Map when a text is empty" taggedAs SlowTest in { val lightPipeline = new LightPipeline(model) val imagesPath = Array( "src/test/resources/image/bluetick.jpg", @@ -110,7 +110,7 @@ class LLAVAForMultiModalTestSpec extends AnyFlatSpec { } } - it should "annotate with empty Map when a text is empty" taggedAs FastTest in { + it should "annotate with empty Map when a text is empty" taggedAs SlowTest in { val lightPipeline = new LightPipeline(model) val imagesPath = Array( "src/test/resources/image/bluetick.jpg", From 6f7c4d69a93e5b53c5b766e803b83a24ee335c63 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 6 Nov 2024 04:29:08 +0000 Subject: [PATCH 046/108] LLAVA python api Signed-off-by: Prabod Rathnayaka --- python/sparknlp/annotator/cv/__init__.py | 2 +- .../annotator/cv/llava_for_multimodal.py | 328 ++++++++++++++++++ .../annotator/cv/llava_for_multimodal_test.py | 81 +++++ .../cv/LLAVAForMultiModalTestSpec.scala | 4 +- 4 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 python/sparknlp/annotator/cv/llava_for_multimodal.py create mode 100644 python/test/annotator/cv/llava_for_multimodal_test.py diff --git a/python/sparknlp/annotator/cv/__init__.py b/python/sparknlp/annotator/cv/__init__.py index 37eeaf696bb2a8..6c5680f4df26e3 100644 --- a/python/sparknlp/annotator/cv/__init__.py +++ b/python/sparknlp/annotator/cv/__init__.py @@ -16,4 +16,4 @@ from sparknlp.annotator.cv.convnext_for_image_classification import * from sparknlp.annotator.cv.vision_encoder_decoder_for_image_captioning import * from sparknlp.annotator.cv.clip_for_zero_shot_classification import * -from sparknlp.annotator.cv.blip_for_question_answering import * \ No newline at end of file +from sparknlp.annotator.cv.llava_for_multimodal import * diff --git a/python/sparknlp/annotator/cv/llava_for_multimodal.py b/python/sparknlp/annotator/cv/llava_for_multimodal.py new file mode 100644 index 00000000000000..9e06e9e599e5b4 --- /dev/null +++ b/python/sparknlp/annotator/cv/llava_for_multimodal.py @@ -0,0 +1,328 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class LLAVAForMultiModal(AnnotatorModel, + HasBatchedAnnotateImage, + HasImageFeatureProperties, + HasEngine, + HasCandidateLabelsProperties, + HasRescaleFactor): + """LLAVAForMultiModal can load LLAVA models for visual question answering. + The model consists of a vision encoder, a text encoder as well as a text decoder. + The vision encoder will encode the input image, the text encoder will encode the input question together + with the encoding of the image, and the text decoder will output the answer to the question. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion + object: + + >>> visualQAClassifier = LLAVAForMultiModal.pretrained() \\ + ... .setInputCols(["image_assembler"]) \\ + ... .setOutputCol("answer") + + The default model is ``"llava"``, if no name is + provided. + + For available pretrained models please see the `Models Hub + `__. + + To see which models are compatible and how to import them see + `Import Transformers into Spark NLP ๐Ÿš€ + `_. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``IMAGE`` ``DOCUMENT`` + ====================== ====================== + + Parameters + ---------- + batchSize + Batch size. Large values allows faster processing but requires more + memory, by default 2 + configProtoBytes + ConfigProto from tensorflow, serialized into byte array. + maxSentenceLength + Max sentence length to process, by default 50 + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> image_df = SparkSessionForTest.spark.read.format("image").load(path=images_path) + >>> test_df = image_df.withColumn("text", lit("USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n")) + >>> imageAssembler = ImageAssembler() \\ + ... .setInputCol("image") \\ + ... .setOutputCol("image_assembler") + >>> visualQAClassifier = LLAVAForMultiModal.pretrained() \\ + ... .setInputCols("image_assembler") \\ + ... .setOutputCol("answer") + >>> pipeline = Pipeline().setStages([ + ... imageAssembler, + ... visualQAClassifier + ... ]) + >>> result = pipeline.fit(test_df).transform(test_df) + >>> result.select("image_assembler.origin", "answer.result").show(false) + +--------------------------------------+------+ + |origin |result| + +--------------------------------------+------+ + |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]| + +--------------------------------------+------+ + """ + + name = "LLAVAForMultiModal" + + inputAnnotatorTypes = [AnnotatorType.IMAGE] + + outputAnnotatorType = AnnotatorType.DOCUMENT + + configProtoBytes = Param(Params._dummy(), + "configProtoBytes", + "ConfigProto from tensorflow, serialized into byte array. Get with " + "config_proto.SerializeToString()", + TypeConverters.toListInt) + + minOutputLength = Param(Params._dummy(), "minOutputLength", "Minimum length of the sequence to be generated", + typeConverter=TypeConverters.toInt) + + maxOutputLength = Param(Params._dummy(), "maxOutputLength", "Maximum length of output text", + typeConverter=TypeConverters.toInt) + + doSample = Param(Params._dummy(), "doSample", "Whether or not to use sampling; use greedy decoding otherwise", + typeConverter=TypeConverters.toBoolean) + + temperature = Param(Params._dummy(), "temperature", "The value used to module the next token probabilities", + typeConverter=TypeConverters.toFloat) + + topK = Param(Params._dummy(), "topK", + "The number of highest probability vocabulary tokens to keep for top-k-filtering", + typeConverter=TypeConverters.toInt) + + topP = Param(Params._dummy(), "topP", + "If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or higher are kept for generation", + typeConverter=TypeConverters.toFloat) + + repetitionPenalty = Param(Params._dummy(), "repetitionPenalty", + "The parameter for repetition penalty. 1.0 means no penalty. See `this paper `__ for more details", + typeConverter=TypeConverters.toFloat) + + noRepeatNgramSize = Param(Params._dummy(), "noRepeatNgramSize", + "If set to int > 0, all ngrams of that size can only occur once", + typeConverter=TypeConverters.toInt) + + ignoreTokenIds = Param(Params._dummy(), "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output", + typeConverter=TypeConverters.toListInt) + beamSize = Param(Params._dummy(), "beamSize", + "The Number of beams for beam search.", + typeConverter=TypeConverters.toInt) + + def setMaxSentenceSize(self, value): + """Sets Maximum sentence length that the annotator will process, by + default 50. + + Parameters + ---------- + value : int + Maximum sentence length that the annotator will process + """ + return self._set(maxSentenceLength=value) + + def setIgnoreTokenIds(self, value): + """A list of token ids which are ignored in the decoder's output. + + Parameters + ---------- + value : List[int] + The words to be filtered out + """ + return self._set(ignoreTokenIds=value) + + def setConfigProtoBytes(self, b): + """Sets configProto from tensorflow, serialized into byte array. + + Parameters + ---------- + b : List[int] + ConfigProto from tensorflow, serialized into byte array + """ + return self._set(configProtoBytes=b) + + def setMinOutputLength(self, value): + """Sets minimum length of the sequence to be generated. + + Parameters + ---------- + value : int + Minimum length of the sequence to be generated + """ + return self._set(minOutputLength=value) + + def setMaxOutputLength(self, value): + """Sets maximum length of output text. + + Parameters + ---------- + value : int + Maximum length of output text + """ + return self._set(maxOutputLength=value) + + def setDoSample(self, value): + """Sets whether or not to use sampling, use greedy decoding otherwise. + + Parameters + ---------- + value : bool + Whether or not to use sampling; use greedy decoding otherwise + """ + return self._set(doSample=value) + + def setTemperature(self, value): + """Sets the value used to module the next token probabilities. + + Parameters + ---------- + value : float + The value used to module the next token probabilities + """ + return self._set(temperature=value) + + def setTopK(self, value): + """Sets the number of highest probability vocabulary tokens to keep for + top-k-filtering. + + Parameters + ---------- + value : int + Number of highest probability vocabulary tokens to keep + """ + return self._set(topK=value) + + def setTopP(self, value): + """Sets the top cumulative probability for vocabulary tokens. + + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + + Parameters + ---------- + value : float + Cumulative probability for vocabulary tokens + """ + return self._set(topP=value) + + def setRepetitionPenalty(self, value): + """Sets the parameter for repetition penalty. 1.0 means no penalty. + + Parameters + ---------- + value : float + The repetition penalty + + References + ---------- + See `Ctrl: A Conditional Transformer Language Model For Controllable + Generation `__ for more details. + """ + return self._set(repetitionPenalty=value) + + def setNoRepeatNgramSize(self, value): + """Sets size of n-grams that can only occur once. + + If set to int > 0, all ngrams of that size can only occur once. + + Parameters + ---------- + value : int + N-gram size can only occur once + """ + return self._set(noRepeatNgramSize=value) + + def setBeamSize(self, value): + """Sets the number of beam size for beam search, by default `4`. + + Parameters + ---------- + value : int + Number of beam size for beam search + """ + return self._set(beamSize=value) + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cv.LLAVAForMultiModal", + java_model=None): + super(LLAVAForMultiModal, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=2, + minOutputLength=0, + maxOutputLength=200, + doSample=False, + temperature=1, + topK=50, + topP=1, + repetitionPenalty=1.0, + noRepeatNgramSize=0, + ignoreTokenIds=[], + beamSize=1, + ) + + @staticmethod + def loadSavedModel(folder, spark_session, use_openvino=False): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.internal import _LLAVAForMultiModalLoader + jModel = _LLAVAForMultiModalLoader(folder, spark_session._jsparkSession, use_openvino)._java_obj + return LLAVAForMultiModal(java_model=jModel) + + @staticmethod + def pretrained(name="phi3v", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "phi3v" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(LLAVAForMultiModal, name, lang, remote_loc) \ No newline at end of file diff --git a/python/test/annotator/cv/llava_for_multimodal_test.py b/python/test/annotator/cv/llava_for_multimodal_test.py new file mode 100644 index 00000000000000..c927ef76c21dca --- /dev/null +++ b/python/test/annotator/cv/llava_for_multimodal_test.py @@ -0,0 +1,81 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +import pytest +import os + +from sparknlp.annotator import * +from sparknlp.base import * +from pyspark.sql.functions import lit +from test.util import SparkSessionForTest,SparkContextForTest + + +class LLAVAForMultiModalTestSetup(unittest.TestCase): + + def setUp(self): + self.images_path = os.getcwd() + "/../src/test/resources/image/" + self.spark = SparkContextForTest.spark + image_df = SparkSessionForTest.spark.read.format("image").load( + path=self.images_path + ) + + self.test_df = image_df.withColumn("text", lit("USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n")) + + image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") + + imageClassifier = LLAVAForMultiModal.pretrained()\ + .setInputCols("image_assembler") \ + .setOutputCol("answer") + + self.pipeline = Pipeline( + stages=[ + image_assembler, + imageClassifier, + ] + ) + + self.model = self.pipeline.fit(self.test_df) + +@pytest.mark.slow +class LLAVAForMultiModalTest(LLAVAForMultiModalTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + result = self.model.transform(self.test_df).collect() + + for row in result: + self.assertTrue(row["answer"] != "") + + +@pytest.mark.slow +class LightLLAVAForMultiModalTest(LLAVAForMultiModalTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.model) + image_path = self.images_path + "bluetick.jpg" + + print("image_path: " + image_path) + annotations_result = light_pipeline.fullAnnotateImage( + image_path, + "USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n" + ) + print(annotations_result) + for result in annotations_result: + self.assertTrue(len(result["image_assembler"]) > 0) + self.assertTrue(len(result["answer"]) > 0) \ No newline at end of file diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala index 2d7501fee7cace..29a37ca9aee7a6 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala @@ -160,9 +160,7 @@ class LLAVAForMultiModalTestSpec extends AnyFlatSpec { .setOutputCol("image_assembler") val loadModel = LLAVAForMultiModal - .loadSavedModel( - "/mnt/research/Projects/ModelZoo/LLAVA/llava-1.5-7b-hf/INT4", - ResourceHelper.spark) + .pretrained() .setInputCols("image_assembler") .setOutputCol("answer") .setMaxOutputLength(50) From 6f2f3a995b80f202b67443a1f408f91d87c1c6b0 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 7 Nov 2024 10:02:37 +0000 Subject: [PATCH 047/108] LLAVA notebook Signed-off-by: Prabod Rathnayaka --- ...gingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb | 879 ++++++++++++++++++ 1 file changed, 879 insertions(+) create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb new file mode 100644 index 00000000000000..a12f939f35e6f4 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb @@ -0,0 +1,879 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import OpenVINO LLAVA models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and importing LLAVA models from HuggingFace for use in Spark NLP, with [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html). The focus is on converting the model to the OpenVINO format and applying precision optimizations (INT8 and INT4), to enhance the performance and efficiency on CPU platforms using [Optimum Intel](https://huggingface.co/docs/optimum/main/en/intel/inference).\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance CPU inference for models. So please make sure you have upgraded to the latest Spark NLP release.\n", + "- Model quantization is a computationally expensive process, so it is recommended to use a runtime with more than 32GB memory for exporting the quantized model from HuggingFace.\n", + "- You can import LLAVA models via `LLAVA`. These models are usually under `Text Generation` category and have `LLAVA` in their labels.\n", + "- Reference: [LLAVA](https://huggingface.co/docs/transformers/model_doc/llama#transformers.LLAVA)\n", + "- Some [example models](https://huggingface.co/models?search=LLAVA)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Export and Save the HuggingFace model\n", + "\n", + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future release, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import requests" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "%pip install -q --upgrade transformers==4.41.2\n", + "%pip install -q --upgrade openvino==2024.1\n", + "%pip install -q \"git+https://github.com/eaidova/optimum-intel.git@ea/minicpmv\"\n", + "%pip install -q \"nncf>=2.13.0\" \"sentencepiece\" \"tokenizers>=0.12.1\" \"transformers>=4.45.0\" \"gradio>=4.36\"\n", + "%pip install -q -U --pre --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly openvino-tokenizers openvino openvino-genai\n", + "%pip install -q --upgrade huggingface_hub\n", + "%pip install -q --upgrade onnx==1.15.0\n", + "%pip install -q --upgrade torch==2.2.1\n", + "\n", + "\n", + "utility_files = [\"notebook_utils.py\", \"cmd_helper.py\"]\n", + "\n", + "for utility in utility_files:\n", + " local_path = Path(utility)\n", + " if not local_path.exists():\n", + " r = requests.get(\n", + " url=f\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/{local_path.name}\",\n", + " )\n", + " with local_path.open(\"w\") as f:\n", + " f.write(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Convert the model to OpenVino" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from cmd_helper import optimum_cli\n", + "\n", + "model_id = \"llava-hf/llava-1.5-7b-hf\"\n", + "model_path = Path(model_id.split(\"/\")[-1]) / \"FP16\"\n", + "\n", + "if not model_path.exists():\n", + " optimum_cli(model_id, model_path, additional_args={\"weight-format\": \"fp16\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:nncf:Statistics of the bitwidth distribution:\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n", + "โ”‚ Num bits (N) โ”‚ % all parameters (layers) โ”‚ % ratio-defining parameters (layers) โ”‚\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n", + "โ”‚ 4 โ”‚ 100% (225 / 225) โ”‚ 100% (225 / 225) โ”‚\n", + "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e8f9bad3e593468db17c882e77311335", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "import shutil\n",
+    "import nncf\n",
+    "import openvino as ov\n",
+    "import gc\n",
+    "\n",
+    "\n",
+    "compression_mode = \"INT4\"\n",
+    "\n",
+    "core = ov.Core()\n",
+    "\n",
+    "\n",
+    "def compress_model_weights(precision):\n",
+    "    int4_compression_config = {\"mode\": nncf.CompressWeightsMode.INT4_ASYM, \"group_size\": 128, \"ratio\": 1, \"all_layers\": True}\n",
+    "    int8_compression_config = {\"mode\": nncf.CompressWeightsMode.INT8_ASYM}\n",
+    "\n",
+    "    compressed_model_path = model_path.parent / precision\n",
+    "\n",
+    "    if not compressed_model_path.exists():\n",
+    "        ov_model = core.read_model(model_path / \"openvino_language_model.xml\")\n",
+    "        compression_config = int4_compression_config if precision == \"INT4\" else int8_compression_config\n",
+    "        compressed_ov_model = nncf.compress_weights(ov_model, **compression_config)\n",
+    "        ov.save_model(compressed_ov_model, compressed_model_path / \"openvino_language_model.xml\")\n",
+    "        del compressed_ov_model\n",
+    "        del ov_model\n",
+    "        gc.collect()\n",
+    "        for file_name in model_path.glob(\"*\"):\n",
+    "            if file_name.name in [\"openvino_language_model.xml\", \"openvino_language_model.bin\"]:\n",
+    "                continue\n",
+    "            shutil.copy(file_name, compressed_model_path)\n",
+    "\n",
+    "\n",
+    "compress_model_weights(compression_mode)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2 Load openvino models"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "model_dir = model_path.parent / compression_mode\n",
+    "language_model = core.read_model(model_dir / \"openvino_language_model.xml\")\n",
+    "vision_embedding = core.compile_model(model_dir / \"openvino_vision_embeddings_model.xml\", \"AUTO\")\n",
+    "text_embedding = core.compile_model(model_dir / \"openvino_text_embeddings_model.xml\", \"AUTO\")\n",
+    "compiled_language_model = core.compile_model(language_model, \"AUTO\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/torch/cuda/__init__.py:619: UserWarning: Can't initialize NVML\n",
+      "  warnings.warn(\"Can't initialize NVML\")\n"
+     ]
+    }
+   ],
+   "source": [
+    "import requests\n",
+    "from PIL import Image\n",
+    "from io import BytesIO\n",
+    "from transformers import AutoProcessor, AutoConfig\n",
+    "\n",
+    "config = AutoConfig.from_pretrained(model_path)\n",
+    "\n",
+    "processor = AutoProcessor.from_pretrained(\n",
+    "    model_path, patch_size=config.vision_config.patch_size, vision_feature_select_strategy=config.vision_feature_select_strategy\n",
+    ")\n",
+    "\n",
+    "\n",
+    "def load_image(image_file):\n",
+    "    if image_file.startswith(\"http\") or image_file.startswith(\"https\"):\n",
+    "        response = requests.get(image_file)\n",
+    "        image = Image.open(BytesIO(response.content)).convert(\"RGB\")\n",
+    "    else:\n",
+    "        image = Image.open(image_file).convert(\"RGB\")\n",
+    "    return image\n",
+    "\n",
+    "\n",
+    "image_file = \"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\"\n",
+    "text_message = \"What is unusual on this image?\"\n",
+    "\n",
+    "image = load_image(image_file)\n",
+    "\n",
+    "conversation = [\n",
+    "    {\n",
+    "        \"role\": \"user\",\n",
+    "        \"content\": [\n",
+    "            {\"type\": \"text\", \"text\": text_message},\n",
+    "            {\"type\": \"image\"},\n",
+    "        ],\n",
+    "    },\n",
+    "]\n",
+    "\n",
+    "prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)\n",
+    "\n",
+    "inputs_new = processor(images=image, text=prompt, return_tensors=\"pt\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\n",
+    "request = compiled_language_model.create_infer_request()\n",
+    "input_names = {key.get_any_name(): idx for idx, key in enumerate(language_model.inputs)}\n",
+    "inputs = {}\n",
+    "# Set the initial input_ids\n",
+    "current_input_ids = inputs_new[\"input_ids\"]\n",
+    "attention_mask = inputs_new[\"attention_mask\"]\n",
+    "position_ids = attention_mask.long().cumsum(-1) - 1\n",
+    "position_ids.masked_fill_(attention_mask == 0, 1)\n",
+    "pixel_values = inputs_new[\"pixel_values\"]\n",
+    "\n",
+    "# Set the initial input_ids\n",
+    "text_out = text_embedding(inputs_new[\"input_ids\"])[0]\n",
+    "vision_out = vision_embedding(pixel_values)[0]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "import torch\n",
+    "\n",
+    "class MergeMultiModalInputs(torch.nn.Module):\n",
+    "    def __init__(self,image_seq_length=576,image_token_index=32000):\n",
+    "        super().__init__()\n",
+    "        self.image_seq_length = image_seq_length\n",
+    "        self.image_token_index = image_token_index\n",
+    "\n",
+    "    def forward(\n",
+    "        self,\n",
+    "        vision_embeds,\n",
+    "        inputs_embeds,\n",
+    "        input_ids,\n",
+    "    ):\n",
+    "        image_features = vision_embeds\n",
+    "        inputs_embeds = inputs_embeds\n",
+    "        special_image_mask = (input_ids == self.image_token_index).unsqueeze(-1).expand_as(inputs_embeds)\n",
+    "        # image_features = image_features.to(inputs_embeds.dtype)\n",
+    "        final_embedding = inputs_embeds.masked_scatter(special_image_mask, image_features)\n",
+    "\n",
+    "        return {\n",
+    "            \"final_embedding\": final_embedding\n",
+    "        }"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "torch_model_merge = MergeMultiModalInputs(\n",
+    "    image_seq_length=config.image_seq_length,\n",
+    "    image_token_index=config.image_token_index\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# test the model\n",
+    "inputs_embeds = torch.from_numpy(text_out)\n",
+    "input_ids = inputs_new[\"input_ids\"]\n",
+    "vision_embeds = torch.from_numpy(vision_out)\n",
+    "\n",
+    "final_embedding = torch_model_merge(vision_embeds, inputs_embeds, input_ids)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "WARNING:nncf:NNCF provides best results with torch==2.4.*, while current torch version is 2.3.1+cu121. If you encounter issues, consider switching to torch==2.4.*\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
+     ]
+    }
+   ],
+   "source": [
+    "import openvino as ov\n",
+    "\n",
+    "# convert MergeMultiModalInputs to OpenVINO IR\n",
+    "ov_model_merge = ov.convert_model(\n",
+    "    torch_model_merge,\n",
+    "    example_input={\n",
+    "        \"vision_embeds\": torch.from_numpy(vision_out),\n",
+    "        \"inputs_embeds\": torch.from_numpy(text_out),\n",
+    "        \"input_ids\": inputs_new[\"input_ids\"],\n",
+    "    }\n",
+    ")\n",
+    "ov.save_model(ov_model_merge, model_dir/\"openvino_merge_model.xml\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "โŒ› Check if all models are converted\n",
+      "โœ… All models are converted. You can find results in llava-1.5-7b-hf/INT4\n"
+     ]
+    }
+   ],
+   "source": [
+    "# check if all the models are converted\n",
+    "\n",
+    "print(\"โŒ› Check if all models are converted\")\n",
+    "lang_model_path = model_dir / \"openvino_language_model.xml\"\n",
+    "image_embed_path = model_dir / \"openvino_vision_embeddings_model.xml\"\n",
+    "img_projection_path = model_dir / \"openvino_text_embeddings_model.xml\"\n",
+    "merge_model_path = model_dir / \"openvino_merge_model.xml\"\n",
+    "\n",
+    "\n",
+    "\n",
+    "if all(\n",
+    "    [\n",
+    "        lang_model_path.exists(),\n",
+    "        image_embed_path.exists(),\n",
+    "        img_projection_path.exists(),\n",
+    "        merge_model_path.exists(),\n",
+    "    ]\n",
+    "):\n",
+    "    print(f\"โœ… All models are converted. You can find results in {model_dir}\")\n",
+    "else:\n",
+    "    print(\"โŒ Not all models are converted. Please check the conversion process\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2 Copy assets to the assets folder"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assets_dir = model_dir / \"assets\"\n",
+    "assets_dir.mkdir(exist_ok=True)\n",
+    "\n",
+    "# copy all the assets to the assets directory (json files, vocab files, etc.)\n",
+    "\n",
+    "import shutil\n",
+    "\n",
+    "# copy all json files\n",
+    "\n",
+    "for file in model_dir.glob(\"*.json\"):\n",
+    "    shutil.copy(file, assets_dir)\n",
+    "\n",
+    "    \n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "total 4.1G\n",
+      "-rw-rw-r-- 1 prabod prabod   41 Nov  7 04:33 added_tokens.json\n",
+      "drwxrwxr-x 2 prabod prabod 4.0K Nov  7 04:37 assets\n",
+      "-rw-rw-r-- 1 prabod prabod  701 Nov  7 04:33 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 4.7K Nov  7 04:33 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  136 Nov  7 04:33 generation_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 332K Nov  7 04:33 openvino_detokenizer.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 8.8K Nov  7 04:33 openvino_detokenizer.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 3.2G Nov  7 04:33 openvino_language_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9M Nov  7 04:33 openvino_language_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod   40 Nov  7 04:36 openvino_merge_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 9.8K Nov  7 04:36 openvino_merge_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 251M Nov  7 04:33 openvino_text_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 3.1K Nov  7 04:33 openvino_text_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 1.2M Nov  7 04:33 openvino_tokenizer.bin\n",
+      "-rw-rw-r-- 1 prabod prabod  25K Nov  7 04:33 openvino_tokenizer.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 595M Nov  7 04:33 openvino_vision_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 929K Nov  7 04:33 openvino_vision_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  505 Nov  7 04:33 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  134 Nov  7 04:33 processor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  552 Nov  7 04:33 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.4K Nov  7 04:33 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 3.5M Nov  7 04:33 tokenizer.json\n",
+      "-rw-rw-r-- 1 prabod prabod 489K Nov  7 04:33 tokenizer.model\n"
+     ]
+    }
+   ],
+   "source": [
+    "!ls -lh {model_dir}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "total 3.5M\n",
+      "-rw-rw-r-- 1 prabod prabod   41 Nov  7 04:37 added_tokens.json\n",
+      "-rw-rw-r-- 1 prabod prabod  701 Nov  7 04:37 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 4.7K Nov  7 04:37 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  136 Nov  7 04:37 generation_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  505 Nov  7 04:37 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  134 Nov  7 04:37 processor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  552 Nov  7 04:37 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.4K Nov  7 04:37 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 3.5M Nov  7 04:37 tokenizer.json\n"
+     ]
+    }
+   ],
+   "source": [
+    "!ls -lh {assets_dir}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.3 Test the openvino model"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import openvino as ov\n",
+    "import torch\n",
+    "\n",
+    "core = ov.Core()\n",
+    "device = \"CPU\"\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "language_model = core.read_model(model_dir / \"openvino_language_model.xml\")\n",
+    "language_model = core.read_model(model_dir / \"openvino_language_model.xml\")\n",
+    "vision_embedding = core.compile_model(model_dir / \"openvino_vision_embeddings_model.xml\", \"AUTO\")\n",
+    "text_embedding = core.compile_model(model_dir / \"openvino_text_embeddings_model.xml\", \"AUTO\")\n",
+    "compiled_language_model = core.compile_model(language_model, \"AUTO\")\n",
+    "merge_multi_modal = core.compile_model(model_dir / \"openvino_merge_model.xml\", \"AUTO\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "generated_tokens = []\n",
+    "\n",
+    "from transformers import AutoProcessor, TextStreamer\n",
+    "\n",
+    "conversation = [\n",
+    "    {\n",
+    "        \"role\": \"user\",\n",
+    "        \"content\": [\n",
+    "            {\"type\": \"text\", \"text\": \"What is unusual on this image?\"},\n",
+    "            {\"type\": \"image\"},\n",
+    "        ],\n",
+    "    },\n",
+    "]\n",
+    "\n",
+    "prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)\n",
+    "\n",
+    "inputs_new = processor(images=image, text=prompt, return_tensors=\"pt\")\n",
+    "\n",
+    "# inputs_new = processor(prompt, [image], return_tensors=\"pt\")\n",
+    "\n",
+    "generation_args = {\"max_new_tokens\": 50, \"do_sample\": False, \"streamer\": TextStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)}\n",
+    "\n",
+    "\n",
+    "request = compiled_language_model.create_infer_request()\n",
+    "merge_model_request = merge_multi_modal.create_infer_request()\n",
+    "input_names = {key.get_any_name(): idx for idx, key in enumerate(language_model.inputs)}\n",
+    "inputs = {}\n",
+    "# Set the initial input_ids\n",
+    "current_input_ids = inputs_new[\"input_ids\"]\n",
+    "attention_mask = inputs_new[\"attention_mask\"]\n",
+    "position_ids = attention_mask.long().cumsum(-1) - 1\n",
+    "position_ids.masked_fill_(attention_mask == 0, 1)\n",
+    "pixel_values = inputs_new[\"pixel_values\"]\n",
+    "\n",
+    "for i in range(generation_args[\"max_new_tokens\"]):\n",
+    "    # Generate input embeds each time\n",
+    "    if current_input_ids.shape[-1] > 1:\n",
+    "        vision_embeds = torch.from_numpy(vision_embedding({\n",
+    "            \"pixel_values\": pixel_values,\n",
+    "        })[0])\n",
+    "    \n",
+    "    text_embeds = torch.from_numpy(text_embedding(current_input_ids)[0])\n",
+    "\n",
+    "    if i == 0:\n",
+    "        merge_model_request.start_async({\n",
+    "            \"vision_embeds\": vision_embeds,\n",
+    "            \"inputs_embeds\": text_embeds,\n",
+    "            \"input_ids\": current_input_ids,\n",
+    "        }, share_inputs=True)\n",
+    "        merge_model_request.wait()\n",
+    "        final_embedding = torch.from_numpy(merge_model_request.get_tensor(\"final_embedding\").data)\n",
+    "    else:\n",
+    "        final_embedding = text_embeds\n",
+    "    if i>0:\n",
+    "        inputs = {}\n",
+    "    # Prepare inputs for the model\n",
+    "    inputs[\"inputs_embeds\"] = final_embedding\n",
+    "    inputs[\"attention_mask\"] = attention_mask\n",
+    "    inputs[\"position_ids\"] = position_ids\n",
+    "    if \"beam_idx\" in input_names:\n",
+    "        inputs[\"beam_idx\"] = np.arange(attention_mask.shape[0], dtype=int)\n",
+    "    \n",
+    "    # Start inference\n",
+    "    request.start_async(inputs, share_inputs=True)\n",
+    "    request.wait()\n",
+    "    \n",
+    "    # Get the logits and find the next token\n",
+    "    logits = torch.from_numpy(request.get_tensor(\"logits\").data)\n",
+    "    next_token = logits.argmax(-1)[0][-1]\n",
+    "    \n",
+    "    # Append the generated token\n",
+    "    generated_tokens.append(next_token)\n",
+    "    \n",
+    "    # Update input_ids with the new token\n",
+    "    current_input_ids = torch.cat([next_token.unsqueeze(0).unsqueeze(0)], dim=-1)\n",
+    "    \n",
+    "    # update the attention mask\n",
+    "    attention_mask = torch.cat([attention_mask, torch.ones_like(attention_mask[:, :1])], dim=-1)\n",
+    "\n",
+    "    # Update inputs for the next iteration\n",
+    "    position_ids = attention_mask.long().cumsum(-1) - 1\n",
+    "    position_ids.masked_fill_(attention_mask == 0, 1)\n",
+    "    position_ids = position_ids[:, -current_input_ids.shape[1] :]\n",
+    "    inputs[\"position_ids\"] = position_ids"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Question:\n",
+      " What is unusual on this picture?\n",
+      "Answer:\n",
+      "The unusual aspect of this image is that a cat is lying inside a cardboard box, which is not a typical place for a cat to rest. Cats are known for their curiosity and love for small, enclosed spaces, but in this case\n"
+     ]
+    }
+   ],
+   "source": [
+    "generated_text = processor.decode(generated_tokens, skip_special_tokens=True)\n",
+    "\n",
+    "image\n",
+    "print(\"Question:\\n What is unusual on this picture?\")\n",
+    "print(\"Answer:\")\n",
+    "print(generated_text)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 2. Import and Save LLAVA in Spark NLP\n",
+    "\n",
+    "- Let's install and setup Spark NLP in Google Colab\n",
+    "- This part is pretty easy via our simple script"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's start Spark with Spark NLP included via our simple `start()` function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "24/11/07 09:56:55 WARN Utils: Your hostname, minotaur resolves to a loopback address: 127.0.1.1; using 192.168.1.4 instead (on interface eno1)\n",
+      "24/11/07 09:56:55 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n",
+      "24/11/07 09:56:55 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "Setting default log level to \"WARN\".\n",
+      "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n"
+     ]
+    }
+   ],
+   "source": [
+    "import sparknlp\n",
+    "\n",
+    "# let's start Spark with Spark NLP\n",
+    "spark = sparknlp.start()\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "24/11/07 09:57:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native15331424460843812197/libtbb.so.2\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "WARNING: An illegal reflective access operation has occurred\n",
+      "WARNING: Illegal reflective access by org.apache.spark.util.SizeEstimator$ (file:/home/prabod/spark/jars/spark-core_2.12-3.3.2.jar) to field java.util.regex.Pattern.pattern\n",
+      "WARNING: Please consider reporting this to the maintainers of org.apache.spark.util.SizeEstimator$\n",
+      "WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n",
+      "WARNING: All illegal access operations will be denied in a future release\n"
+     ]
+    }
+   ],
+   "source": [
+    "imageClassifier = LLAVAForMultiModal.pretrained() \\\n",
+    "            .setInputCols(\"image_assembler\") \\\n",
+    "            .setOutputCol(\"answer\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "imageClassifier.write().overwrite().save(\"LLAVA_spark_nlp\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sparknlp\n",
+    "from sparknlp.base import *\n",
+    "from sparknlp.annotator import *\n",
+    "from pyspark.sql.functions import lit\n",
+    "from pyspark.ml import Pipeline\n",
+    "from pathlib import Path\n",
+    "import os\n",
+    "\n",
+    "# download two images to test into ./images folder\n",
+    "\n",
+    "url1 = \"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\"\n",
+    "url2 = \"http://images.cocodataset.org/val2017/000000039769.jpg\"\n",
+    "\n",
+    "Path(\"images\").mkdir(exist_ok=True)\n",
+    "\n",
+    "!wget -q -O images/image1.jpg {url1}\n",
+    "!wget -q -O images/image2.jpg {url2}\n",
+    "\n",
+    "\n",
+    "\n",
+    "images_path = \"file://\" + os.getcwd() + \"/images/\"\n",
+    "image_df = spark.read.format(\"image\").load(\n",
+    "    path=images_path\n",
+    ")\n",
+    "\n",
+    "test_df = image_df.withColumn(\"text\", lit(\"USER: \\n <|image|> \\n What's this picture about? \\n ASSISTANT:\\n\"))\n",
+    "\n",
+    "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n",
+    "\n",
+    "imageClassifier = LLAVAForMultiModal.load(\"LLAVA_spark_nlp\")\\\n",
+    "            .setMaxOutputLength(50) \\\n",
+    "            .setInputCols(\"image_assembler\") \\\n",
+    "            .setOutputCol(\"answer\")\n",
+    "\n",
+    "pipeline = Pipeline(\n",
+    "            stages=[\n",
+    "                image_assembler,\n",
+    "                imageClassifier,\n",
+    "            ]\n",
+    "        )\n",
+    "\n",
+    "model = pipeline.fit(test_df)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "image_path: /mnt/research/Projects/ModelZoo/LLAVA/images/image1.jpg\n",
+      "[Annotation(document, 0, 363, This image features a cat comfortably laying inside a cardboard box. The cat appears to be relaxed and enjoying its cozy spot. The scene takes place on a carpeted floor, which adds to the overall warm and inviting atmosphere of the image. The cat's position inside the box creates a sense of security and contentment, making it an endearing and heartwarming scene., Map(), [])]\n"
+     ]
+    }
+   ],
+   "source": [
+    "light_pipeline = LightPipeline(model)\n",
+    "image_path = os.getcwd() + \"/images/\" + \"image1.jpg\"\n",
+    "print(\"image_path: \" + image_path)\n",
+    "annotations_result = light_pipeline.fullAnnotateImage(\n",
+    "    image_path,\n",
+    "    \"USER: \\n <|image|> \\n What's this picture about? \\n ASSISTANT:\\n\"\n",
+    ")\n",
+    "\n",
+    "for result in annotations_result:\n",
+    "    print(result[\"answer\"])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "tempspark",
+   "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.8.16"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

From 64b6b2063b430d25ddc5e1e4ad2d274f1016ac32 Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Fri, 8 Nov 2024 06:33:37 +0000
Subject: [PATCH 048/108] Add custom model requirements

Signed-off-by: Prabod Rathnayaka 
---
 python/sparknlp/internal/__init__.py          |  8 +++
 .../ml/util/LoadExternalModel.scala           | 58 +++++++++++++------
 2 files changed, 47 insertions(+), 19 deletions(-)

diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py
index 4cb5321e8a8691..8b33dda9165ce6 100644
--- a/python/sparknlp/internal/__init__.py
+++ b/python/sparknlp/internal/__init__.py
@@ -299,6 +299,14 @@ def __init__(self, path, jspark):
             jspark,
         )
 
+class _LLAVAForMultiModalLoader(ExtendedJavaWrapper):
+    def __init__(self, path, jspark, use_openvino=False):
+        super(_LLAVAForMultiModalLoader, self).__init__(
+            "com.johnsnowlabs.nlp.annotators.cv.LLAVAForMultiModal.loadSavedModel",
+            path,
+            jspark,
+            use_openvino
+        )
 
 class _M2M100Loader(ExtendedJavaWrapper):
     def __init__(self, path, jspark, use_openvino=False):
diff --git a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala
index cd0761f0f9daa3..1b48fdf2c7b6d5 100644
--- a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala
+++ b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala
@@ -18,6 +18,7 @@ package com.johnsnowlabs.ml.util
 
 import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper
 import com.johnsnowlabs.nlp.util.io.{ExternalResource, ReadAs, ResourceHelper}
+import org.glassfish.jersey.internal.inject.Custom
 
 import java.io.File
 import java.nio.file.Paths
@@ -103,22 +104,39 @@ object LoadExternalModel {
 
   }
 
-  def isOpenvinoModel(modelPath: String, isEncoderDecoder: Boolean): Boolean = {
-    if (isEncoderDecoder) {
-      val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml")
-      val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin")
-      val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml")
-      val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin")
-      val ovDecoderModelWithPastXml = new File(modelPath, s"${Openvino.decoderModelWithPast}.xml")
-      val ovDecoderModelWithPastBin = new File(modelPath, s"${Openvino.decoderModelWithPast}.bin")
-
-      ovEncoderModelXml.exists() && ovEncoderModelBin.exists() &&
-      ovDecoderModelXml.exists() && ovDecoderModelBin.exists() &&
-      ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists()
+  def isOpenvinoModel(
+      modelPath: String,
+      isEncoderDecoder: Boolean,
+      custom: Option[List[String]] = None): Boolean = {
+
+    if (custom.isDefined) {
+      for (model <- custom.get) {
+        val ovModelXml = new File(modelPath, s"${model}.xml")
+        val ovModelBin = new File(modelPath, s"${model}.bin")
+        if (!ovModelXml.exists() || !ovModelBin.exists()) {
+          return false
+        }
+      }
+      true
     } else {
-      val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml")
-      val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin")
-      modelXml.exists() && modelBin.exists()
+      if (isEncoderDecoder) {
+        val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml")
+        val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin")
+        val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml")
+        val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin")
+        val ovDecoderModelWithPastXml =
+          new File(modelPath, s"${Openvino.decoderModelWithPast}.xml")
+        val ovDecoderModelWithPastBin =
+          new File(modelPath, s"${Openvino.decoderModelWithPast}.bin")
+
+        ovEncoderModelXml.exists() && ovEncoderModelBin.exists() &&
+        ovDecoderModelXml.exists() && ovDecoderModelBin.exists() &&
+        ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists()
+      } else {
+        val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml")
+        val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin")
+        modelXml.exists() && modelBin.exists()
+      }
     }
   }
 
@@ -126,7 +144,8 @@ object LoadExternalModel {
       modelPath: String,
       isEncoderDecoder: Boolean = false,
       withPast: Boolean = false,
-      isDecoder: Boolean = false): String = {
+      isDecoder: Boolean = false,
+      custom: Option[List[String]] = None): String = {
 
     /** Check if the path is correct */
     val f = new File(modelPath)
@@ -146,7 +165,7 @@ object LoadExternalModel {
     val onnxModelExist = isOnnxModel(modelPath, isEncoderDecoder, withPast, isDecoder)
 
     /*Openvino required model files*/
-    val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder)
+    val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder, custom)
 
     if (tfSavedModelExist) {
       TensorFlow.name
@@ -176,10 +195,11 @@ object LoadExternalModel {
       path: String,
       isEncoderDecoder: Boolean = false,
       withPast: Boolean = false,
-      isDecoder: Boolean = false): (String, String) = {
+      isDecoder: Boolean = false,
+      custom: Option[List[String]] = None): (String, String) = {
     val localPath: String = ResourceHelper.copyToLocal(path)
 
-    (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder))
+    (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder, custom))
   }
 
   def loadTextAsset(assetPath: String, assetName: String): Array[String] = {

From 2661bfbc1dbb0981004473d5dc2e50df589412fd Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Thu, 13 Feb 2025 07:11:30 +0000
Subject: [PATCH 049/108] update documentation and resource downloader entry

Signed-off-by: Prabod Rathnayaka 
---
 .../transformer_entries/LLAVAForMultiModal.md | 122 +++++++
 ...gingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb | 328 +++++++++++-------
 .../annotator/cv/llava_for_multimodal.py      |   8 +-
 .../annotators/cv/LLAVAForMultiModal.scala    |  18 +-
 .../nlp/pretrained/ResourceDownloader.scala   |   3 +-
 .../cv/LLAVAForMultiModalTestSpec.scala       |  27 +-
 6 files changed, 377 insertions(+), 129 deletions(-)
 create mode 100644 docs/en/transformer_entries/LLAVAForMultiModal.md

diff --git a/docs/en/transformer_entries/LLAVAForMultiModal.md b/docs/en/transformer_entries/LLAVAForMultiModal.md
new file mode 100644
index 00000000000000..4b41694569baf4
--- /dev/null
+++ b/docs/en/transformer_entries/LLAVAForMultiModal.md
@@ -0,0 +1,122 @@
+{%- capture title -%}
+LLAVAForMultiModal
+{%- endcapture -%}
+
+{%- capture description -%}
+Visual Question Answering using LLAVA.
+
+LLAVAForMultiModal can load LLAVA models for visual question answering.
+The model consists of a vision encoder, a text encoder as well as a text decoder.
+The vision encoder will encode the input image, the text encoder will encode the input question together
+with the encoding of the image, and the text decoder will output the answer to the question.
+
+Pretrained models can be loaded with `pretrained` of the companion object:
+
+```scala
+val visualQA = LLAVAForMultiModal.pretrained()
+     .setInputCols("image_assembler")
+     .setOutputCol("answer")
+```
+The default model is `"llava_1_5_7b_hf"`, if no name is provided.
+
+For available pretrained models please see the
+[Models Hub](https://sparknlp.org/models?task=Question+Answering).
+
+To see which models are compatible and how to import them see
+[Import Transformers into Spark NLP ๐Ÿš€](https://github.com/JohnSnowLabs/spark-nlp/discussions/5669).
+
+{%- endcapture -%}
+
+{%- capture input_anno -%}
+IMAGE
+{%- endcapture -%}
+
+{%- capture output_anno -%}
+DOCUMENT
+{%- endcapture -%}
+
+{%- capture python_example -%}
+import sparknlp
+from sparknlp.base import *
+from sparknlp.annotator import *
+from pyspark.ml import Pipeline
+from pyspark.sql.functions import lit
+
+image_df = spark.read.format("image").load(path=images_path) # Replace with your image path
+test_df = image_df.withColumn("text", lit("USER: \n <|image|> \n What's this picture about? \n ASSISTANT:\n"))
+
+imageAssembler = ImageAssembler()   
+          .setInputCol("image")   
+          .setOutputCol("image_assembler")
+
+visualQAClassifier = LLAVAForMultiModal.pretrained()   
+          .setInputCols("image_assembler")   
+          .setOutputCol("answer")
+
+pipeline = Pipeline().setStages([
+          imageAssembler,
+          visualQAClassifier
+])
+
+result = pipeline.fit(test_df).transform(test_df)
+result.select("image_assembler.origin", "answer.result").show(False)
+{%- endcapture -%}
+
+{%- capture scala_example -%}
+import spark.implicits._
+import com.johnsnowlabs.nlp.base._
+import com.johnsnowlabs.nlp.annotator._
+import org.apache.spark.ml.Pipeline
+import org.apache.spark.sql.DataFrame
+import org.apache.spark.sql.functions.lit
+
+val imageFolder = "path/to/your/images" // Replace with your image path
+
+val imageDF: DataFrame = spark.read
+     .format("image")
+     .option("dropInvalid", value = true)
+     .load(imageFolder)
+
+val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n <|image|> \nWhat is unusual on this picture? \n ASSISTANT:\n"))
+
+val imageAssembler: ImageAssembler = new ImageAssembler()
+     .setInputCol("image")
+     .setOutputCol("image_assembler")
+
+val visualQAClassifier = LLAVAForMultiModal.pretrained()
+     .setInputCols("image_assembler")
+     .setOutputCol("answer")
+
+val pipeline = new Pipeline().setStages(Array(
+     imageAssembler,
+     visualQAClassifier
+))
+
+val result = pipeline.fit(testDF).transform(testDF)
+
+result.select("image_assembler.origin", "answer.result").show(false)
+{%- endcapture -%}
+
+{%- capture api_link -%}
+[LLAVAForMultiModal](https://www.google.com/url?sa=E&source=gmail&q=/api/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal)
+{%- endcapture -%}
+
+{%- capture python_api_link -%}
+[LLAVAForMultiModal](https://www.google.com/url?sa=E&source=gmail&q=/api/python/reference/autosummary/sparknlp/annotator/cv/llava_for_multimodal/index.html#sparknlp.annotator.cv.llava_for_multimodal.LLAVAForMultiModal)
+{%- endcapture -%}
+
+{%- capture source_link -%}
+[LLAVAForMultiModal](https://www.google.com/url?sa=E&source=gmail&q=https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala)
+{%- endcapture -%}
+
+{% include templates/anno_template.md
+title=title
+description=description
+input_anno=input_anno
+output_anno=output_anno
+python_example=python_example
+scala_example=scala_example
+api_link=api_link
+python_api_link=python_api_link
+source_link=source_link
+%}
\ No newline at end of file
diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb
index a12f939f35e6f4..5b5020df3a140e 100644
--- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb
+++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_LLAVA.ipynb
@@ -38,30 +38,34 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 3,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n"
+     ]
+    }
+   ],
    "source": [
-    "from pathlib import Path\n",
-    "import requests"
+    "\n",
+    "%pip install -q \"nncf>=2.14.0\" \"torch>=2.1\" \"transformers>=4.39.1\" \"accelerate\" \"pillow\" \"gradio>=4.26\" \"datasets>=2.14.6\" \"tqdm\" --extra-index-url https://download.pytorch.org/whl/cpu\n",
+    "%pip install -q -U \"openvino>=2024.5.0\" \"openvino-tokenizers>=2024.5.0\" \"openvino-genai>=2024.5\"\n",
+    "%pip install -q \"git+https://github.com/huggingface/optimum-intel.git\" --extra-index-url https://download.pytorch.org/whl/cpu\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [],
    "source": [
-    "\n",
-    "%pip install -q --upgrade transformers==4.41.2\n",
-    "%pip install -q --upgrade openvino==2024.1\n",
-    "%pip install -q \"git+https://github.com/eaidova/optimum-intel.git@ea/minicpmv\"\n",
-    "%pip install -q  \"nncf>=2.13.0\"  \"sentencepiece\" \"tokenizers>=0.12.1\" \"transformers>=4.45.0\" \"gradio>=4.36\"\n",
-    "%pip install -q -U --pre --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly openvino-tokenizers openvino openvino-genai\n",
-    "%pip install -q --upgrade huggingface_hub\n",
-    "%pip install -q --upgrade onnx==1.15.0\n",
-    "%pip install -q --upgrade torch==2.2.1\n",
-    "\n",
+    "from pathlib import Path\n",
+    "import requests\n",
     "\n",
     "utility_files = [\"notebook_utils.py\", \"cmd_helper.py\"]\n",
     "\n",
@@ -86,7 +90,52 @@
    "cell_type": "code",
    "execution_count": 2,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "data": {
+      "text/markdown": [
+       "**Export command:**"
+      ],
+      "text/plain": [
+       ""
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/markdown": [
+       "`optimum-cli export openvino --model llava-hf/llava-1.5-7b-hf llava-1.5-7b-hf/FP16 --weight-format fp16`"
+      ],
+      "text/plain": [
+       ""
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/prabod/anaconda3/envs/llava/lib/python3.9/importlib/util.py:245: DeprecationWarning: The `openvino.runtime` module is deprecated and will be removed in the 2026.0 release. Please replace `openvino.runtime` with `openvino`.\n",
+      "  self.__spec__.loader.exec_module(self)\n",
+      "Downloading shards: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 3/3 [00:00<00:00,  3.84it/s]\n",
+      "Loading checkpoint shards: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 3/3 [00:05<00:00,  1.90s/it]\n",
+      "Using a slow image processor as `use_fast` is unset and a slow processor was saved with this model. `use_fast=True` will be the default behavior in v4.48, even if the model was saved with a slow processor. This will result in minor differences in outputs. You'll still be able to use a slow processor with `use_fast=False`.\n",
+      "`loss_type=None` was set in the config but it is unrecognised.Using the default loss: `ForCausalLMLoss`.\n",
+      "/home/prabod/anaconda3/envs/llava/lib/python3.9/site-packages/transformers/cache_utils.py:460: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n",
+      "  or len(self.key_cache[layer_idx]) == 0  # the layer has no cache\n",
+      "/home/prabod/anaconda3/envs/llava/lib/python3.9/site-packages/optimum/exporters/openvino/model_patcher.py:515: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
+      "  if sequence_length != 1:\n",
+      "/home/prabod/anaconda3/envs/llava/lib/python3.9/site-packages/transformers/cache_utils.py:444: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n",
+      "  len(self.key_cache[layer_idx]) == 0\n",
+      "/home/prabod/anaconda3/envs/llava/lib/python3.9/site-packages/transformers/models/clip/modeling_clip.py:243: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
+      "  if not interpolate_pos_encoding and (height != self.image_size or width != self.image_size):\n"
+     ]
+    }
+   ],
    "source": [
     "from cmd_helper import optimum_cli\n",
     "\n",
@@ -99,7 +148,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
@@ -107,27 +156,13 @@
      "output_type": "stream",
      "text": [
       "INFO:nncf:Statistics of the bitwidth distribution:\n",
-      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n",
-      "โ”‚   Num bits (N) โ”‚ % all parameters (layers)   โ”‚ % ratio-defining parameters (layers)   โ”‚\n",
-      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n",
-      "โ”‚              4 โ”‚ 100% (225 / 225)            โ”‚ 100% (225 / 225)                       โ”‚\n",
-      "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n"
+      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n",
+      "โ”‚ Weight compression mode   โ”‚ % all parameters (layers)   โ”‚ % ratio-defining parameters (layers)   โ”‚\n",
+      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n",
+      "โ”‚ int4_asym                 โ”‚ 100% (225 / 225)            โ”‚ 100% (225 / 225)                       โ”‚\n",
+      "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n"
      ]
     },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "e8f9bad3e593468db17c882e77311335",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
     {
      "data": {
       "text/html": [
@@ -183,7 +218,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -196,15 +231,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/torch/cuda/__init__.py:619: UserWarning: Can't initialize NVML\n",
-      "  warnings.warn(\"Can't initialize NVML\")\n"
+      "/home/prabod/anaconda3/envs/llava/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
+      "  from .autonotebook import tqdm as notebook_tqdm\n",
+      "Using a slow image processor as `use_fast` is unset and a slow processor was saved with this model. `use_fast=True` will be the default behavior in v4.48, even if the model was saved with a slow processor. This will result in minor differences in outputs. You'll still be able to use a slow processor with `use_fast=False`.\n"
      ]
     }
    ],
@@ -252,7 +288,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 8,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -274,7 +310,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 9,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -306,7 +342,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 10,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -318,7 +354,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 11,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -332,14 +368,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 12,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "WARNING:nncf:NNCF provides best results with torch==2.4.*, while current torch version is 2.3.1+cu121. If you encounter issues, consider switching to torch==2.4.*\n"
+      "WARNING:nncf:NNCF provides best results with torch==2.5.*, while current torch version is 2.6.0+cpu. If you encounter issues, consider switching to torch==2.5.*\n"
      ]
     },
     {
@@ -350,6 +386,56 @@
       "To disable this warning, you can either:\n",
       "\t- Avoid using `tokenizers` before the fork if possible\n",
       "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n",
+      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
+      "To disable this warning, you can either:\n",
+      "\t- Avoid using `tokenizers` before the fork if possible\n",
+      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
       "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
       "To disable this warning, you can either:\n",
       "\t- Avoid using `tokenizers` before the fork if possible\n",
@@ -374,7 +460,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 13,
    "metadata": {},
    "outputs": [
     {
@@ -419,9 +505,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 9,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/prabod/anaconda3/envs/llava/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
+      "  from .autonotebook import tqdm as notebook_tqdm\n"
+     ]
+    }
+   ],
    "source": [
     "assets_dir = model_dir / \"assets\"\n",
     "assets_dir.mkdir(exist_ok=True)\n",
@@ -435,52 +530,48 @@
     "for file in model_dir.glob(\"*.json\"):\n",
     "    shutil.copy(file, assets_dir)\n",
     "\n",
+    "from transformers import AutoConfig\n",
+    "\n",
+    "model_id = \"llava-hf/llava-1.5-7b-hf\"\n",
+    "\n",
+    "config = AutoConfig.from_pretrained(model_id)\n",
+    "config.save_pretrained(assets_dir)\n",
     "    \n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n",
-      "To disable this warning, you can either:\n",
-      "\t- Avoid using `tokenizers` before the fork if possible\n",
-      "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n"
-     ]
-    },
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
       "total 4.1G\n",
-      "-rw-rw-r-- 1 prabod prabod   41 Nov  7 04:33 added_tokens.json\n",
-      "drwxrwxr-x 2 prabod prabod 4.0K Nov  7 04:37 assets\n",
-      "-rw-rw-r-- 1 prabod prabod  701 Nov  7 04:33 chat_template.json\n",
-      "-rw-rw-r-- 1 prabod prabod 4.7K Nov  7 04:33 config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  136 Nov  7 04:33 generation_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod 332K Nov  7 04:33 openvino_detokenizer.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 8.8K Nov  7 04:33 openvino_detokenizer.xml\n",
-      "-rw-rw-r-- 1 prabod prabod 3.2G Nov  7 04:33 openvino_language_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 2.9M Nov  7 04:33 openvino_language_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod   40 Nov  7 04:36 openvino_merge_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 9.8K Nov  7 04:36 openvino_merge_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod 251M Nov  7 04:33 openvino_text_embeddings_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 3.1K Nov  7 04:33 openvino_text_embeddings_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod 1.2M Nov  7 04:33 openvino_tokenizer.bin\n",
-      "-rw-rw-r-- 1 prabod prabod  25K Nov  7 04:33 openvino_tokenizer.xml\n",
-      "-rw-rw-r-- 1 prabod prabod 595M Nov  7 04:33 openvino_vision_embeddings_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 929K Nov  7 04:33 openvino_vision_embeddings_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod  505 Nov  7 04:33 preprocessor_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  134 Nov  7 04:33 processor_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  552 Nov  7 04:33 special_tokens_map.json\n",
-      "-rw-rw-r-- 1 prabod prabod 1.4K Nov  7 04:33 tokenizer_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod 3.5M Nov  7 04:33 tokenizer.json\n",
-      "-rw-rw-r-- 1 prabod prabod 489K Nov  7 04:33 tokenizer.model\n"
+      "-rw-rw-r-- 1 prabod prabod   41 Feb 13 05:09 added_tokens.json\n",
+      "drwxrwxr-x 2 prabod prabod 4.0K Feb 13 05:10 assets\n",
+      "-rw-rw-r-- 1 prabod prabod  701 Feb 13 05:09 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.1K Feb 13 05:09 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  136 Feb 13 05:09 generation_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 332K Feb 13 05:09 openvino_detokenizer.bin\n",
+      "-rw-rw-r-- 1 prabod prabod  12K Feb 13 05:09 openvino_detokenizer.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 3.2G Feb 13 05:09 openvino_language_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9M Feb 13 05:09 openvino_language_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod   40 Feb 13 05:10 openvino_merge_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 9.9K Feb 13 05:10 openvino_merge_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 251M Feb 13 05:09 openvino_text_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 3.1K Feb 13 05:09 openvino_text_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 1.2M Feb 13 05:09 openvino_tokenizer.bin\n",
+      "-rw-rw-r-- 1 prabod prabod  25K Feb 13 05:09 openvino_tokenizer.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 595M Feb 13 05:09 openvino_vision_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 928K Feb 13 05:09 openvino_vision_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  505 Feb 13 05:09 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  173 Feb 13 05:09 processor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  580 Feb 13 05:09 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.5K Feb 13 05:09 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 3.5M Feb 13 05:09 tokenizer.json\n",
+      "-rw-rw-r-- 1 prabod prabod 489K Feb 13 05:09 tokenizer.model\n"
      ]
     }
    ],
@@ -490,7 +581,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 16,
    "metadata": {},
    "outputs": [
     {
@@ -508,15 +599,15 @@
      "output_type": "stream",
      "text": [
       "total 3.5M\n",
-      "-rw-rw-r-- 1 prabod prabod   41 Nov  7 04:37 added_tokens.json\n",
-      "-rw-rw-r-- 1 prabod prabod  701 Nov  7 04:37 chat_template.json\n",
-      "-rw-rw-r-- 1 prabod prabod 4.7K Nov  7 04:37 config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  136 Nov  7 04:37 generation_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  505 Nov  7 04:37 preprocessor_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  134 Nov  7 04:37 processor_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  552 Nov  7 04:37 special_tokens_map.json\n",
-      "-rw-rw-r-- 1 prabod prabod 1.4K Nov  7 04:37 tokenizer_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod 3.5M Nov  7 04:37 tokenizer.json\n"
+      "-rw-rw-r-- 1 prabod prabod   41 Feb 13 05:10 added_tokens.json\n",
+      "-rw-rw-r-- 1 prabod prabod  701 Feb 13 05:10 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.1K Feb 13 05:10 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  136 Feb 13 05:10 generation_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  505 Feb 13 05:10 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  173 Feb 13 05:10 processor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  580 Feb 13 05:10 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.5K Feb 13 05:10 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 3.5M Feb 13 05:10 tokenizer.json\n"
      ]
     }
    ],
@@ -533,7 +624,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 17,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -546,7 +637,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 18,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -560,7 +651,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 19,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -652,7 +743,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 20,
    "metadata": {},
    "outputs": [
     {
@@ -733,14 +824,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": null,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "24/11/07 09:57:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native15331424460843812197/libtbb.so.2\n"
+      "25/02/13 06:30:15 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native10897903401200889289/libtbb.so.2\n"
      ]
     },
     {
@@ -756,23 +847,31 @@
     }
    ],
    "source": [
-    "imageClassifier = LLAVAForMultiModal.pretrained() \\\n",
+    "imageClassifier = LLAVAForMultiModal.loadSavedModel(str(model_dir),spark) \\\n",
     "            .setInputCols(\"image_assembler\") \\\n",
     "            .setOutputCol(\"answer\")"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 4,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "                                                                                \r"
+     ]
+    }
+   ],
    "source": [
-    "imageClassifier.write().overwrite().save(\"LLAVA_spark_nlp\")"
+    "imageClassifier.write().overwrite().save(\"file:///tmp/LLAVA_spark_nlp\")"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -805,7 +904,7 @@
     "\n",
     "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n",
     "\n",
-    "imageClassifier = LLAVAForMultiModal.load(\"LLAVA_spark_nlp\")\\\n",
+    "imageClassifier = LLAVAForMultiModal.load(\"file:///tmp/LLAVA_spark_nlp\")\\\n",
     "            .setMaxOutputLength(50) \\\n",
     "            .setInputCols(\"image_assembler\") \\\n",
     "            .setOutputCol(\"answer\")\n",
@@ -822,21 +921,21 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "image_path: /mnt/research/Projects/ModelZoo/LLAVA/images/image1.jpg\n",
-      "[Annotation(document, 0, 363, This image features a cat comfortably laying inside a cardboard box. The cat appears to be relaxed and enjoying its cozy spot. The scene takes place on a carpeted floor, which adds to the overall warm and inviting atmosphere of the image. The cat's position inside the box creates a sense of security and contentment, making it an endearing and heartwarming scene., Map(), [])]\n"
+      "image_path: file:///home/prabod/Projects/spark-nlp/examples/python/transformers/openvino/images/image1.jpg\n",
+      "[Annotation(document, 0, 207, This image features a cat comfortably laying inside a cardboard box. The cat appears to be relaxed and enjoying its cozy spot. The scene takes place on a carpeted floor, which adds to the overall warm and inv, Map(), [])]\n"
      ]
     }
    ],
    "source": [
     "light_pipeline = LightPipeline(model)\n",
-    "image_path = os.getcwd() + \"/images/\" + \"image1.jpg\"\n",
+    "image_path = \"file://\"+os.getcwd() + \"/images/\" + \"image1.jpg\"\n",
     "print(\"image_path: \" + image_path)\n",
     "annotations_result = light_pipeline.fullAnnotateImage(\n",
     "    image_path,\n",
@@ -846,18 +945,11 @@
     "for result in annotations_result:\n",
     "    print(result[\"answer\"])"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "tempspark",
+   "display_name": "llava",
    "language": "python",
    "name": "python3"
   },
@@ -871,7 +963,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.16"
+   "version": "3.9.21"
   }
  },
  "nbformat": 4,
diff --git a/python/sparknlp/annotator/cv/llava_for_multimodal.py b/python/sparknlp/annotator/cv/llava_for_multimodal.py
index 9e06e9e599e5b4..b203545b82f393 100644
--- a/python/sparknlp/annotator/cv/llava_for_multimodal.py
+++ b/python/sparknlp/annotator/cv/llava_for_multimodal.py
@@ -32,7 +32,7 @@ class LLAVAForMultiModal(AnnotatorModel,
     ...     .setInputCols(["image_assembler"]) \\
     ...     .setOutputCol("answer")
 
-    The default model is ``"llava"``, if no name is
+    The default model is ``"llava_1_5_7b_hf"``, if no name is
     provided.
 
     For available pretrained models please see the `Models Hub
@@ -305,14 +305,14 @@ def loadSavedModel(folder, spark_session, use_openvino=False):
         return LLAVAForMultiModal(java_model=jModel)
 
     @staticmethod
-    def pretrained(name="phi3v", lang="en", remote_loc=None):
+    def pretrained(name="llava_1_5_7b_hf", lang="en", remote_loc=None):
         """Downloads and loads a pretrained model.
 
         Parameters
         ----------
         name : str, optional
             Name of the pretrained model, by default
-            "phi3v"
+            "llava_1_5_7b_hf"
         lang : str, optional
             Language of the pretrained model, by default "en"
         remote_loc : str, optional
@@ -321,7 +321,7 @@ def pretrained(name="phi3v", lang="en", remote_loc=None):
 
         Returns
         -------
-        CLIPForZeroShotClassification
+        LLAVAForMultiModal
             The restored model
         """
         from sparknlp.pretrained import ResourceDownloader
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala
index dc823cb5d1f011..0e4784f0ce204b 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModal.scala
@@ -50,7 +50,7 @@ import org.apache.spark.sql.SparkSession
   *   .setInputCols("image_assembler")
   *   .setOutputCol("answer")
   * }}}
-  * The default model is `"llava"`, if no name is provided.
+  * The default model is `"llava_1_5_7b_hf"`, if no name is provided.
   *
   * For available pretrained models please see the
   * [[https://sparknlp.org/models?task=Question+Answering Models Hub]].
@@ -371,7 +371,7 @@ trait ReadablePretrainedLLAVAForMultiModal
     extends ParamsAndFeaturesReadable[LLAVAForMultiModal]
     with HasPretrained[LLAVAForMultiModal] {
 
-  override val defaultModelName: Some[String] = Some("llava")
+  override val defaultModelName: Some[String] = Some("llava_1_5_7b_hf")
 
   /** Java compliant-overrides */
   override def pretrained(): LLAVAForMultiModal = super.pretrained()
@@ -446,6 +446,10 @@ trait ReadLLAVAForMultiModalDLModel extends ReadOpenvinoModel {
             "openvino_merge_model")))
     val modelConfig: JValue =
       parse(loadJsonStringAsset(localModelPath, "config.json"))
+
+    val generationConfigJson: JValue = parse(
+      loadJsonStringAsset(localModelPath, "generation_config.json"))
+
     val preprocessorConfigJsonContent =
       loadJsonStringAsset(localModelPath, "preprocessor_config.json")
     val preprocessorConfig = Preprocessor.loadPreprocessorConfig(preprocessorConfigJsonContent)
@@ -467,9 +471,9 @@ trait ReadLLAVAForMultiModalDLModel extends ReadOpenvinoModel {
     def arrayOrNone[T](array: Array[T]): Option[Array[T]] =
       if (array.nonEmpty) Some(array) else None
 
-    val bosTokenId = (modelConfig \ "text_config" \ "bos_token_id").extract[Int]
-    val eosTokenId = (modelConfig \ "text_config" \ "eos_token_id").extract[Int]
-    val padTokenId = (modelConfig \ "text_config" \ "eos_token_id").extract[Int]
+    val bosTokenId = (generationConfigJson \ "bos_token_id").extract[Int]
+    val eosTokenId = (generationConfigJson \ "eos_token_id").extract[Int]
+    val padTokenId = (generationConfigJson \ "pad_token_id").extract[Int]
     val vocabSize = (modelConfig \ "text_config" \ "vocab_size").extract[Int]
 
     val imageToken = (modelConfig \ "image_token_index").extract[Int]
@@ -545,6 +549,10 @@ trait ReadLLAVAForMultiModalDLModel extends ReadOpenvinoModel {
       .setAddedTokens(addedTokens)
       .setImageToken(imageToken)
       .setImageTokenLength(imageTokenLength)
+      .setSize(preprocessorConfig.size)
+      .setImageMean(preprocessorConfig.image_mean)
+      .setImageStd(preprocessorConfig.image_std)
+      .setResample(preprocessorConfig.resample)
 
     val modelEngine =
       if (useOpenvino)
diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala
index 0e457d4d6e20df..573d8d60e1804e 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala
@@ -697,7 +697,8 @@ object PythonResourceDownloader {
     "NLLBTransformer" -> NLLBTransformer,
     "Phi3Transformer" -> Phi3Transformer,
     "QwenTransformer" -> QwenTransformer,
-    "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings)
+    "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings,
+    "LLAVAForMultiModal" -> LLAVAForMultiModal)
 
   // List pairs of types such as the one with key type can load a pretrained model from the value type
   val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering")
diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala
index 29a37ca9aee7a6..afa1fd86afe500 100644
--- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala
+++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/LLAVAForMultiModalTestSpec.scala
@@ -168,7 +168,32 @@ class LLAVAForMultiModalTestSpec extends AnyFlatSpec {
     val newPipeline: Pipeline =
       new Pipeline().setStages(Array(imageAssembler, loadModel))
 
-    newPipeline.fit(testDF)
+    val pipelineModel = newPipeline.fit(testDF)
+
+    pipelineModel
+      .transform(testDF)
+      .show(truncate = false)
+
+    pipelineModel
+      .transform(testDF)
+      .show(truncate = false)
+
+    pipelineModel.stages.last
+      .asInstanceOf[LLAVAForMultiModal]
+      .write
+      .overwrite()
+      .save("/tmp/llava-7b-4bit-model")
+
+    val loadedLLAMA3 = LLAVAForMultiModal.load("/tmp/llava-7b-4bit-model")
+
+    val loadedPipeline = new Pipeline().setStages(Array(imageAssembler, loadedLLAMA3))
+
+    loadedPipeline
+      .fit(testDF)
+      .transform(testDF)
+      .show(truncate = false)
+
+    pipelineModel
   }
 
   private def getTestDF: DataFrame = {

From af28bf2ed77e1906ba99b2cb82c93c0f41522bfb Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Wed, 13 Nov 2024 04:00:20 +0000
Subject: [PATCH 050/108] cohere scala and python api

---
 python/sparknlp/annotator/seq2seq/__init__.py |   1 +
 .../annotator/seq2seq/cohere_transformer.py   | 357 ++++++++++++
 python/sparknlp/internal/__init__.py          |   9 +
 .../seq2seq/cohere_transformer_test.py        |  55 ++
 .../scala/com/johnsnowlabs/ml/ai/CoHere.scala | 487 ++++++++++++++++
 .../seq2seq/CoHereTransformer.scala           | 520 ++++++++++++++++++
 .../annotators/seq2seq/CoHereTestSpec.scala   |  82 +++
 7 files changed, 1511 insertions(+)
 create mode 100644 python/sparknlp/annotator/seq2seq/cohere_transformer.py
 create mode 100644 python/test/annotator/seq2seq/cohere_transformer_test.py
 create mode 100644 src/main/scala/com/johnsnowlabs/ml/ai/CoHere.scala
 create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala
 create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTestSpec.scala

diff --git a/python/sparknlp/annotator/seq2seq/__init__.py b/python/sparknlp/annotator/seq2seq/__init__.py
index e9c3984c21ecc1..ab1141056b9763 100644
--- a/python/sparknlp/annotator/seq2seq/__init__.py
+++ b/python/sparknlp/annotator/seq2seq/__init__.py
@@ -28,3 +28,4 @@
 from sparknlp.annotator.seq2seq.qwen_transformer import *
 from sparknlp.annotator.seq2seq.starcoder_transformer import *
 from sparknlp.annotator.seq2seq.llama3_transformer import *
+from sparknlp.annotator.seq2seq.cohere_transformer import *
diff --git a/python/sparknlp/annotator/seq2seq/cohere_transformer.py b/python/sparknlp/annotator/seq2seq/cohere_transformer.py
new file mode 100644
index 00000000000000..87419d8edc0a27
--- /dev/null
+++ b/python/sparknlp/annotator/seq2seq/cohere_transformer.py
@@ -0,0 +1,357 @@
+#  Copyright 2017-2022 John Snow Labs
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+"""Contains classes for the CoHereTransformer."""
+
+from sparknlp.common import *
+
+
+class CoHereTransformer(AnnotatorModel, HasBatchedAnnotate, HasEngine):
+    """Cohere: Command-R Transformer
+    
+        C4AI Command-R is a research release of a 35 billion parameter highly performant generative model.
+        Command-R is a large language model with open weights optimized for a variety of use cases including reasoning,
+        summarization, and question answering. Command-R has the capability for multilingual generation evaluated
+        in 10 languages and highly performant RAG capabilities.
+
+        Pretrained models can be loaded with :meth:`.pretrained` of the companion
+        object:
+    
+        >>> CoHere = CoHereTransformer.pretrained() \\
+        ...     .setInputCols(["document"]) \\
+        ...     .setOutputCol("generation")
+    
+    
+        The default model is ``"CoHere-7b"``, if no name is provided. For available
+        pretrained models please see the `Models Hub
+        `__.
+    
+        ====================== ======================
+        Input Annotation types Output Annotation type
+        ====================== ======================
+        ``DOCUMENT``           ``DOCUMENT``
+        ====================== ======================
+    
+        Parameters
+        ----------
+        configProtoBytes
+            ConfigProto from tensorflow, serialized into byte array.
+        minOutputLength
+            Minimum length of the sequence to be generated, by default 0
+        maxOutputLength
+            Maximum length of output text, by default 60
+        doSample
+            Whether or not to use sampling; use greedy decoding otherwise, by default False
+        temperature
+            The value used to modulate the next token probabilities, by default 1.0
+        topK
+            The number of highest probability vocabulary tokens to keep for
+            top-k-filtering, by default 40
+        topP
+            Top cumulative probability for vocabulary tokens, by default 1.0
+    
+            If set to float < 1, only the most probable tokens with probabilities
+            that add up to ``topP`` or higher are kept for generation.
+        repetitionPenalty
+            The parameter for repetition penalty, 1.0 means no penalty. , by default
+            1.0
+        noRepeatNgramSize
+            If set to int > 0, all ngrams of that size can only occur once, by
+            default 0
+        ignoreTokenIds
+            A list of token ids which are ignored in the decoder's output, by
+            default []
+    
+        Notes
+        -----
+        This is a very computationally expensive module, especially on larger
+        sequences. The use of an accelerator such as GPU is recommended.
+    
+        References
+        ----------
+        - `Cohere `__
+
+    
+        Examples
+        --------
+        >>> import sparknlp
+        >>> from sparknlp.base import *
+        >>> from sparknlp.annotator import *
+        >>> from pyspark.ml import Pipeline
+        >>> documentAssembler = DocumentAssembler() \\
+        ...     .setInputCol("text") \\
+        ...     .setOutputCol("documents")
+        >>> CoHere = CoHereTransformer.pretrained() \\
+        ...     .setInputCols(["documents"]) \\
+        ...     .setMaxOutputLength(60) \\
+        ...     .setOutputCol("generation")
+        >>> pipeline = Pipeline().setStages([documentAssembler, CoHere])
+        >>> data = spark.createDataFrame([
+        ...     (
+        ...         1,
+        ...         "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hello, how are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>"
+        ...         )
+        ... ]).toDF("id", "text")
+        >>> result = pipeline.fit(data).transform(data)
+        >>> result.select("generation.result").show(truncate=False)
+        +------------------------------------------------+
+        |result                                          |
+        +------------------------------------------------+
+        |[Hello! I'm doing well, thank you for asking! I'm excited to help you with whatever questions you have today. How can I assist you?]|
+        +------------------------------------------------+
+    """
+
+    name = "CoHereTransformer"
+
+    inputAnnotatorTypes = [AnnotatorType.DOCUMENT]
+
+    outputAnnotatorType = AnnotatorType.DOCUMENT
+
+    configProtoBytes = Param(Params._dummy(),
+                             "configProtoBytes",
+                             "ConfigProto from tensorflow, serialized into byte array. Get with config_proto.SerializeToString()",
+                             TypeConverters.toListInt)
+
+    minOutputLength = Param(Params._dummy(), "minOutputLength", "Minimum length of the sequence to be generated",
+                            typeConverter=TypeConverters.toInt)
+
+    maxOutputLength = Param(Params._dummy(), "maxOutputLength", "Maximum length of output text",
+                            typeConverter=TypeConverters.toInt)
+
+    doSample = Param(Params._dummy(), "doSample", "Whether or not to use sampling; use greedy decoding otherwise",
+                     typeConverter=TypeConverters.toBoolean)
+
+    temperature = Param(Params._dummy(), "temperature", "The value used to module the next token probabilities",
+                        typeConverter=TypeConverters.toFloat)
+
+    topK = Param(Params._dummy(), "topK",
+                 "The number of highest probability vocabulary tokens to keep for top-k-filtering",
+                 typeConverter=TypeConverters.toInt)
+
+    topP = Param(Params._dummy(), "topP",
+                 "If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or higher are kept for generation",
+                 typeConverter=TypeConverters.toFloat)
+
+    repetitionPenalty = Param(Params._dummy(), "repetitionPenalty",
+                              "The parameter for repetition penalty. 1.0 means no penalty. See `this paper `__ for more details",
+                              typeConverter=TypeConverters.toFloat)
+
+    noRepeatNgramSize = Param(Params._dummy(), "noRepeatNgramSize",
+                              "If set to int > 0, all ngrams of that size can only occur once",
+                              typeConverter=TypeConverters.toInt)
+
+    ignoreTokenIds = Param(Params._dummy(), "ignoreTokenIds",
+                           "A list of token ids which are ignored in the decoder's output",
+                           typeConverter=TypeConverters.toListInt)
+
+    beamSize = Param(Params._dummy(), "beamSize",
+                     "The number of beams to use for beam search",
+                     typeConverter=TypeConverters.toInt)
+
+    stopTokenIds = Param(Params._dummy(), "stopTokenIds",
+                         "A list of token ids which are considered as stop tokens in the decoder's output",
+                         typeConverter=TypeConverters.toListInt)
+
+    def setIgnoreTokenIds(self, value):
+        """A list of token ids which are ignored in the decoder's output.
+
+        Parameters
+        ----------
+        value : List[int]
+            The words to be filtered out
+        """
+        return self._set(ignoreTokenIds=value)
+
+    def setConfigProtoBytes(self, b):
+        """Sets configProto from tensorflow, serialized into byte array.
+
+        Parameters
+        ----------
+        b : List[int]
+            ConfigProto from tensorflow, serialized into byte array
+        """
+        return self._set(configProtoBytes=b)
+
+    def setMinOutputLength(self, value):
+        """Sets minimum length of the sequence to be generated.
+
+        Parameters
+        ----------
+        value : int
+            Minimum length of the sequence to be generated
+        """
+        return self._set(minOutputLength=value)
+
+    def setMaxOutputLength(self, value):
+        """Sets maximum length of output text.
+
+        Parameters
+        ----------
+        value : int
+            Maximum length of output text
+        """
+        return self._set(maxOutputLength=value)
+
+    def setDoSample(self, value):
+        """Sets whether or not to use sampling, use greedy decoding otherwise.
+
+        Parameters
+        ----------
+        value : bool
+            Whether or not to use sampling; use greedy decoding otherwise
+        """
+        return self._set(doSample=value)
+
+    def setTemperature(self, value):
+        """Sets the value used to module the next token probabilities.
+
+        Parameters
+        ----------
+        value : float
+            The value used to module the next token probabilities
+        """
+        return self._set(temperature=value)
+
+    def setTopK(self, value):
+        """Sets the number of highest probability vocabulary tokens to keep for
+        top-k-filtering.
+
+        Parameters
+        ----------
+        value : int
+            Number of highest probability vocabulary tokens to keep
+        """
+        return self._set(topK=value)
+
+    def setTopP(self, value):
+        """Sets the top cumulative probability for vocabulary tokens.
+
+        If set to float < 1, only the most probable tokens with probabilities
+        that add up to ``topP`` or higher are kept for generation.
+
+        Parameters
+        ----------
+        value : float
+            Cumulative probability for vocabulary tokens
+        """
+        return self._set(topP=value)
+
+    def setRepetitionPenalty(self, value):
+        """Sets the parameter for repetition penalty. 1.0 means no penalty.
+
+        Parameters
+        ----------
+        value : float
+            The repetition penalty
+
+        References
+        ----------
+        See `Ctrl: A Conditional Transformer Language Model For Controllable
+        Generation `__ for more details.
+        """
+        return self._set(repetitionPenalty=value)
+
+    def setNoRepeatNgramSize(self, value):
+        """Sets size of n-grams that can only occur once.
+
+        If set to int > 0, all ngrams of that size can only occur once.
+
+        Parameters
+        ----------
+        value : int
+            N-gram size can only occur once
+        """
+        return self._set(noRepeatNgramSize=value)
+
+    def setBeamSize(self, value):
+        """Sets the number of beams to use for beam search.
+
+        Parameters
+        ----------
+        value : int
+            The number of beams to use for beam search
+        """
+        return self._set(beamSize=value)
+
+    def setStopTokenIds(self, value):
+        """Sets a list of token ids which are considered as stop tokens in the decoder's output.
+
+        Parameters
+        ----------
+        value : List[int]
+            The words to be considered as stop tokens
+        """
+        return self._set(stopTokenIds=value)
+
+    @keyword_only
+    def __init__(self, classname="com.johnsnowlabs.nlp.annotators.seq2seq.CoHereTransformer", java_model=None):
+        super(CoHereTransformer, self).__init__(
+            classname=classname,
+            java_model=java_model
+        )
+        self._setDefault(
+            minOutputLength=0,
+            maxOutputLength=20,
+            doSample=False,
+            temperature=0.6,
+            topK=-1,
+            topP=0.9,
+            repetitionPenalty=1.0,
+            noRepeatNgramSize=3,
+            ignoreTokenIds=[],
+            batchSize=1,
+            beamSize=1,
+            stopTokenIds=[128001, ]
+        )
+
+    @staticmethod
+    def loadSavedModel(folder, spark_session, use_openvino=False):
+        """Loads a locally saved model.
+
+        Parameters
+        ----------
+        folder : str
+            Folder of the saved model
+        spark_session : pyspark.sql.SparkSession
+            The current SparkSession
+
+        Returns
+        -------
+        CoHereTransformer
+            The restored model
+        """
+        from sparknlp.internal import _CoHereLoader
+        jModel = _CoHereLoader(folder, spark_session._jsparkSession, use_openvino)._java_obj
+        return CoHereTransformer(java_model=jModel)
+
+    @staticmethod
+    def pretrained(name="cohere_35b_int4", lang="en", remote_loc=None):
+        """Downloads and loads a pretrained model.
+
+        Parameters
+        ----------
+        name : str, optional
+            Name of the pretrained model, by default "llama_2_7b_chat_hf_int4"
+        lang : str, optional
+            Language of the pretrained model, by default "en"
+        remote_loc : str, optional
+            Optional remote address of the resource, by default None. Will use
+            Spark NLPs repositories otherwise.
+
+        Returns
+        -------
+        CoHereTransformer
+            The restored model
+        """
+        from sparknlp.pretrained import ResourceDownloader
+        return ResourceDownloader.downloadModel(CoHereTransformer, name, lang, remote_loc)
diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py
index 4cb5321e8a8691..37400bf9535ef2 100644
--- a/python/sparknlp/internal/__init__.py
+++ b/python/sparknlp/internal/__init__.py
@@ -121,6 +121,15 @@ def __init__(self, path, jspark):
             jspark,
         )
 
+class _CoHereLoader(ExtendedJavaWrapper):
+    def __init__(self, path, jspark, use_openvino=False):
+        super(_CoHereLoader, self).__init__(
+            "com.johnsnowlabs.nlp.annotators.seq2seq.CoHereTransformer.loadSavedModel",
+            path,
+            jspark,
+            use_openvino,
+        )
+
 class _DeBERTaLoader(ExtendedJavaWrapper):
     def __init__(self, path, jspark):
         super(_DeBERTaLoader, self).__init__(
diff --git a/python/test/annotator/seq2seq/cohere_transformer_test.py b/python/test/annotator/seq2seq/cohere_transformer_test.py
new file mode 100644
index 00000000000000..fb3f2f81b978de
--- /dev/null
+++ b/python/test/annotator/seq2seq/cohere_transformer_test.py
@@ -0,0 +1,55 @@
+#  Copyright 2017-2024 John Snow Labs
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+import unittest
+
+import pytest
+
+from sparknlp.annotator import *
+from sparknlp.base import *
+from test.util import SparkContextForTest
+
+
+@pytest.mark.slow
+class CoHereTransformerTextGenerationTestSpec(unittest.TestCase):
+    def setUp(self):
+        self.spark = SparkContextForTest.spark
+
+    def runTest(self):
+        data = self.spark.createDataFrame([
+            (
+                1,
+                "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hello, how are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>"
+            )
+        ]).toDF("id", "text")
+        document_assembler = DocumentAssembler() \
+            .setInputCol("text") \
+            .setOutputCol("documents")
+
+        CoHere = CoHereTransformer \
+            .pretrained() \
+            .setMaxOutputLength(50) \
+            .setDoSample(False) \
+            .setBeamSize(1) \
+            .setTemperature(0.6) \
+            .setTopK(-1) \
+            .setTopP(0.9) \
+            .setStopTokenIds([255001]) \
+            .setInputCols(["documents"]) \
+            .setOutputCol("generation")
+
+        pipeline = Pipeline().setStages([document_assembler, CoHere])
+        results = (pipeline.fit(data).transform(data))
+
+        results.select("generation.result").show(truncate=False)
+
diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/CoHere.scala b/src/main/scala/com/johnsnowlabs/ml/ai/CoHere.scala
new file mode 100644
index 00000000000000..314fd63548963d
--- /dev/null
+++ b/src/main/scala/com/johnsnowlabs/ml/ai/CoHere.scala
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2017 - 2023  John Snow Labs
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+package com.johnsnowlabs.ml.ai
+
+import ai.onnxruntime.{OnnxTensor, OrtEnvironment, OrtSession}
+import com.johnsnowlabs.ml.ai.util.Generation.{Generate, GenerationConfig}
+import com.johnsnowlabs.ml.onnx.OnnxSession
+import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers
+import com.johnsnowlabs.ml.onnx.TensorResources.implicits._
+import com.johnsnowlabs.ml.openvino.OpenvinoWrapper
+import com.johnsnowlabs.ml.util.{ONNX, Openvino, TensorFlow}
+import com.johnsnowlabs.nlp.Annotation
+import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT
+import com.johnsnowlabs.nlp.annotators.common.SentenceSplit
+import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{
+  BpeTokenizer,
+  LLAMA3Tokenizer,
+  SpecialTokens
+}
+import org.intel.openvino.InferRequest
+import org.tensorflow.{Session, Tensor}
+
+import scala.collection.JavaConverters._
+
+private[johnsnowlabs] class CoHere(
+    val onnxWrappers: Option[DecoderWrappers],
+    val openvinoWrapper: Option[OpenvinoWrapper],
+    merges: Map[(String, String), Int],
+    vocabulary: Map[String, Int],
+    addedTokens: Map[String, Int],
+    generationConfig: GenerationConfig)
+    extends Serializable
+    with Generate {
+
+  private val onnxSessionOptions: Map[String, String] = new OnnxSession().getSessionOptions
+  val detectedEngine: String =
+    if (onnxWrappers.isDefined) ONNX.name
+    else if (openvinoWrapper.isDefined) Openvino.name
+    else ONNX.name
+  private var nextPositionId: Option[Array[Long]] = None
+  private val GenerationConfig(
+    bosTokenId: Int,
+    paddingTokenId: Int,
+    eosTokenId: Int,
+    vocabSize: Int,
+    beginSuppressTokens,
+    suppressTokenIds,
+    forcedDecoderIds) =
+    generationConfig
+
+  val reversedVocabulary: Map[Int, String] = vocabulary.map(_.swap)
+  val specialTokens: SpecialTokens = SpecialTokens(
+    vocabulary,
+    startTokenString = reversedVocabulary(bosTokenId),
+    endTokenString = reversedVocabulary(eosTokenId),
+    unkTokenString = reversedVocabulary(eosTokenId),
+    maskTokenString = reversedVocabulary(eosTokenId),
+    padTokenString = reversedVocabulary(eosTokenId),
+    additionalStrings = addedTokens.keys.toArray)
+
+  val bpeTokenizer: LLAMA3Tokenizer = BpeTokenizer
+    .forModel(
+      "llama3",
+      merges = merges,
+      vocab = vocabulary,
+      specialTokens = Some(specialTokens),
+      addPrefixSpaceToSentence = true)
+    .asInstanceOf[LLAMA3Tokenizer]
+
+  /** Decode a sequence of sentences
+    * @param sentences
+    *   Sequence of sentences
+    * @return
+    *   Sequence of decoded sentences
+    */
+  def decode(sentences: Array[Array[Int]]): Seq[String] = {
+    sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt)))
+  }
+
+  /** Encode a sequence of sentences
+    * @param sentences
+    *   Sequence of sentences
+    * @return
+    *   Sequence of encoded sentences
+    */
+  def encode(sentences: Seq[Annotation]): Seq[Array[Int]] = {
+    SentenceSplit
+      .unpack(sentences)
+      .map(s => {
+        val sentWithTask = s
+        bpeTokenizer
+          .tokenize(sentWithTask)
+          .map(bpeTokenizer.encode)
+          .flatMap(_.map(_.pieceId))
+      })
+  }
+
+  def tag(
+      batch: Seq[Array[Int]],
+      minOutputLength: Int,
+      maxOutputLength: Int,
+      doSample: Boolean,
+      temperature: Double,
+      topK: Int,
+      topP: Double,
+      repetitionPenalty: Double,
+      noRepeatNgramSize: Int,
+      randomSeed: Option[Long],
+      ignoreTokenIds: Array[Int] = Array(),
+      beamSize: Int,
+      maxInputLength: Int,
+      stopTokenIds: Array[Int]): Array[Array[Int]] = {
+    val ignoreTokenIdsInt = ignoreTokenIds
+    val expandedDecoderInputsVals = batch
+    val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray
+    val maxSentenceLength = sequencesLength.max // - curLen
+
+    val numReturn_sequences = 1
+    // from config
+
+    var effectiveBatch_size = 1
+    var effectiveBatch_mult = 1
+
+    if (doSample) {
+      effectiveBatch_size = expandedDecoderInputsVals.length * numReturn_sequences
+      effectiveBatch_mult = numReturn_sequences
+    } else {
+      effectiveBatch_size = expandedDecoderInputsVals.length
+      effectiveBatch_mult = 1
+    }
+
+    // Run the prompt through the decoder and get the past
+//    val decoderOutputs =
+//      generateGreedyOnnx(
+//        expandedDecoderInputsVals.toArray,
+//        (encoderSession, env),
+//        maxOutputLength)
+    val (decoderEncoderStateTensors, encoderAttentionMaskTensors, session) =
+      detectedEngine match {
+        case ONNX.name =>
+          // dummy tensors for decoder encode state and attention mask
+          val (encoderSession, env) = onnxWrappers.get.decoder.getSession(onnxSessionOptions)
+          (
+            Right(OnnxTensor.createTensor(env, Array(0))),
+            Right(OnnxTensor.createTensor(env, Array(1))),
+            Right((env, encoderSession)))
+        case Openvino.name =>
+          // not needed
+          (null, null, null)
+      }
+    val ovInferRequest: Option[InferRequest] = detectedEngine match {
+      case ONNX.name => None
+      case Openvino.name => Some(openvinoWrapper.get.getCompiledModel().create_infer_request())
+    }
+    // output with beam search
+    val modelOutputs = generate(
+      batch,
+      decoderEncoderStateTensors,
+      encoderAttentionMaskTensors,
+      expandedDecoderInputsVals.toArray,
+      maxOutputLength + maxSentenceLength,
+      minOutputLength,
+      doSample,
+      beamSize,
+      1,
+      temperature,
+      topK,
+      topP,
+      repetitionPenalty,
+      noRepeatNgramSize,
+      this.vocabSize,
+      this.eosTokenId,
+      this.paddingTokenId,
+      randomSeed,
+      ignoreTokenIdsInt,
+      session,
+      applySoftmax = false,
+      ovInferRequest = ovInferRequest,
+      stopTokenIds = stopTokenIds)
+
+//    decoderOutputs
+    modelOutputs
+  }
+
+  def predict(
+      sentences: Seq[Annotation],
+      batchSize: Int,
+      minOutputLength: Int,
+      maxOutputLength: Int,
+      doSample: Boolean,
+      temperature: Double,
+      topK: Int,
+      topP: Double,
+      repetitionPenalty: Double,
+      noRepeatNgramSize: Int,
+      randomSeed: Option[Long] = None,
+      ignoreTokenIds: Array[Int] = Array(),
+      beamSize: Int,
+      maxInputLength: Int,
+      stopTokenIds: Array[Int]): Seq[Annotation] = {
+
+    val batchDecoder = sentences.grouped(batchSize).toArray.flatMap { batch =>
+      val batchSP = encode(batch)
+      val spIds = tag(
+        batchSP,
+        minOutputLength,
+        maxOutputLength,
+        doSample,
+        temperature,
+        topK,
+        topP,
+        repetitionPenalty,
+        noRepeatNgramSize,
+        randomSeed,
+        ignoreTokenIds,
+        beamSize,
+        maxInputLength,
+        stopTokenIds)
+
+      decode(spIds)
+
+    }
+
+    var sentBegin, nextSentEnd = 0
+    val annotations = batchDecoder.zip(sentences).map { case (content, sent) =>
+      nextSentEnd += content.length - 1
+      val annots = new Annotation(
+        annotatorType = DOCUMENT,
+        begin = sentBegin,
+        end = nextSentEnd,
+        result = content,
+        metadata = sent.metadata)
+      sentBegin += nextSentEnd + 1
+      annots
+    }
+    annotations
+  }
+
+  private def getDecoderOutputsWithPast(
+      inputIds: Array[Array[Int]],
+      decoderPast: Map[String, OnnxTensor],
+      onnxSession: (OrtSession, OrtEnvironment))
+      : (Array[Array[Float]], Map[String, OnnxTensor]) = {
+    val (session, env) = onnxSession
+
+    val lastTokens: Array[Array[Long]] =
+      inputIds.map { tokenIds =>
+        Array(tokenIds.last.toLong)
+      }
+
+    val lastTokensTensor: OnnxTensor =
+      OnnxTensor.createTensor(env, lastTokens)
+    val decoderAttentionMask: OnnxTensor =
+      OnnxTensor.createTensor(env, lastTokens.map(_.map(_ => 1L)))
+    val decoderWithPastInputs: java.util.Map[String, OnnxTensor] = (Map(
+      OnnxSignatures.decoderInputIDs -> lastTokensTensor,
+      OnnxSignatures.decoderAttentionMask -> decoderAttentionMask) ++ decoderPast).asJava
+    val sessionOutput = session.run(decoderWithPastInputs)
+    val logits = sessionOutput.getFloatArray(OnnxSignatures.decoderOutput)
+    val decoderPresent = sessionOutput.getOnnxTensors(OnnxSignatures.decoderPresent)
+    lastTokensTensor.close()
+    val batchLogits = logits.grouped(vocabSize).toArray
+    (batchLogits, decoderPresent)
+
+  }
+
+  override def getModelOutput(
+      encoderInputIds: Seq[Array[Int]],
+      decoderInputIds: Seq[Array[Int]],
+      decoderEncoderStateTensors: Either[Tensor, OnnxTensor],
+      encoderAttentionMaskTensors: Either[Tensor, OnnxTensor],
+      maxLength: Int,
+      session: Either[Session, (OrtEnvironment, OrtSession)],
+      ovInferRequest: Option[InferRequest]): Array[Array[Float]] = {
+    detectedEngine match {
+      case TensorFlow.name =>
+        // not implemented yet
+        Array()
+      case ONNX.name =>
+        val (env, decoderSession) = session.right.get
+        val decoderOutputs =
+          getDecoderOutputs(decoderInputIds.toArray, onnxSession = (decoderSession, env))
+        decoderOutputs
+      case Openvino.name =>
+        val decoderOutputs =
+          getDecoderOutputsOv(
+            encoderInputIds.toArray,
+            decoderInputIds.toArray,
+            ovInferRequest.get)
+        decoderOutputs
+    }
+  }
+
+  private def getDecoderOutputsOv(
+      encoderInputIds: Array[Array[Int]],
+      decoderInputIds: Array[Array[Int]],
+      inferRequest: InferRequest): (Array[Array[Float]]) = {
+
+    val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) =
+      if (encoderInputIds.head.length == decoderInputIds.head.length) {
+        // First pass
+        val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) }
+        val posIdsLong = decoderInputIds.flatMap { tokenIds =>
+          tokenIds.zipWithIndex.map { case (_, i) =>
+            i.toLong
+          }
+        }
+        (inpIdsLong, posIdsLong)
+      } else {
+        // Subsequent passes
+        val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong }
+        val posIdsLong = decoderInputIds.map { tokenIds =>
+          tokenIds.zipWithIndex.map { case (_, i) =>
+            i.toLong
+          }.last
+        }
+        (inpIdsLong, posIdsLong)
+      }
+    val attentionMask: Array[Long] =
+      decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) }
+
+    val batchSize: Int = decoderInputIds.length
+    val beamIdx: Array[Int] = new Array[Int](batchSize)
+    val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize)
+
+    val inputIdsLongTensor: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(shape, inputIdsLong)
+    val decoderAttentionMask: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask)
+    val decoderPositionIDs: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(shape, inputPositionIDsLong)
+    val beamIdxTensor: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(Array(batchSize), beamIdx)
+
+    inferRequest.set_tensor(OpenVinoSignatures.decoderInputIDs, inputIdsLongTensor)
+    inferRequest.set_tensor(OpenVinoSignatures.decoderAttentionMask, decoderAttentionMask)
+    inferRequest.set_tensor(OpenVinoSignatures.decoderPositionIDs, decoderPositionIDs)
+    inferRequest.set_tensor(OpenVinoSignatures.decoderBeamIdx, beamIdxTensor)
+
+    inferRequest.infer()
+
+    val result = inferRequest.get_tensor(OpenVinoSignatures.decoderOutput)
+    val logitsRaw = result.data()
+
+    val sequenceLength = inputIdsLong.length / batchSize
+    val decoderOutputs = (0 until batchSize).map(i => {
+      logitsRaw
+        .slice(
+          i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize,
+          i * sequenceLength * vocabSize + sequenceLength * vocabSize)
+    })
+    decoderOutputs.toArray
+  }
+  private def getDecoderOutputs(
+      inputIds: Array[Array[Int]],
+      onnxSession: (OrtSession, OrtEnvironment)): (Array[Array[Float]]) = {
+    val (session, env) = onnxSession
+
+    val inputIdsLong: Array[Array[Long]] =
+      inputIds.map { tokenIds => tokenIds.map(_.toLong) }
+
+    val inputPositionIDsLong: Array[Array[Long]] =
+      inputIds.map { tokenIds =>
+        tokenIds.zipWithIndex.map { case (_, i) =>
+          i.toLong
+        }
+      }
+
+    val inputIdsLongTensor: OnnxTensor =
+      OnnxTensor.createTensor(env, inputIdsLong)
+    val decoderAttentionMask: OnnxTensor =
+      OnnxTensor.createTensor(env, inputIdsLong.map(_.map(_ => 1L)))
+    val decoderPositionIDs: OnnxTensor =
+      OnnxTensor.createTensor(env, inputPositionIDsLong)
+
+    val decoderInputs: java.util.Map[String, OnnxTensor] = Map(
+      OnnxSignatures.decoderInputIDs -> inputIdsLongTensor,
+      OnnxSignatures.decoderAttentionMask -> decoderAttentionMask,
+      OnnxSignatures.decoderPositionIDs -> decoderPositionIDs).asJava
+    val sessionOutput = session.run(decoderInputs)
+
+    val sequenceLength = inputIds.head.length
+    val batchSize = inputIds.length
+
+//    val logits = sessionOutput.getFloatArray(OnnxSignatures.decoderOutput)
+//    inputIdsLongTensor.close()
+//    decoderPositionIDs.close()
+//    decoderAttentionMask.close()
+//    val batchLogits = logits.grouped(vocabSize).toArray
+//    batchLogits
+
+    val logitsRaw = sessionOutput.getFloatArray(OnnxSignatures.decoderOutput)
+    val decoderOutputs = (0 until batchSize).map(i => {
+      logitsRaw
+        .slice(
+          i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize,
+          i * sequenceLength * vocabSize + sequenceLength * vocabSize)
+    })
+    decoderOutputs.toArray
+  }
+
+  /** Gets the index with the highest score
+    *
+    * @param scores
+    *   Array of Scores to max
+    * @return
+    *   Index of the highest score
+    */
+  private def argmax(scores: Array[Float]): Int =
+    scores.zipWithIndex.maxBy { case (score, _) =>
+      score
+    }._2
+  private def greedyGenerationFinished(
+      decoderIds: Seq[Array[Int]],
+      eosTokenId: Int,
+      maxOutputLength: Int): Boolean =
+    decoderIds.map(_.last).forall(_ == eosTokenId) || decoderIds.head.length == maxOutputLength
+
+  private def generateGreedyOnnx(
+      inputIds: Array[Array[Int]],
+      onnxSession: (OrtSession, OrtEnvironment),
+      maxOutputLength: Int): (Array[Array[Int]]) = {
+
+    val sequencesLength = inputIds.map(x => x.length).toArray
+    val maxSentenceLength = sequencesLength.max // - curLen
+    var generatedIds: Array[Array[Int]] = inputIds
+    while (!greedyGenerationFinished(
+        generatedIds,
+        eosTokenId,
+        maxOutputLength + maxSentenceLength)) {
+
+      val (batchLogits: Array[Array[Float]]) =
+        Array(getDecoderOutputs(generatedIds, onnxSession).last)
+
+      val nextTokenIds: Array[Int] = batchLogits.map(argmax)
+      generatedIds =
+        generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) =>
+          currentIds ++ Array(nextId)
+        }
+    }
+    generatedIds
+  }
+
+  private object OnnxSignatures {
+    val decoderInputIDs: String = "input_ids"
+    val decoderAttentionMask: String = "attention_mask"
+    val decoderPositionIDs: String = "position_ids"
+
+    // create decoder past for 32 layers of key and value eg. past_key_values.0.key and past_key_values.0.value
+    val decoderPast: Array[String] = (0 until 32)
+      .flatMap(i => Seq(s"past_key_values.$i.key", s"past_key_values.$i.value"))
+      .toArray
+    val decoderOutput: String = "logits"
+    val decoderPresent: Array[String] =
+      (0 until 32).flatMap(i => Seq(s"present.$i.key", s"present.$i.value")).toArray
+  }
+
+  private object OpenVinoSignatures {
+    val encoderInputIDs: String = "input_ids"
+    val encoderAttentionMask: String = "attention_mask"
+
+    val encoderOutput: String = "last_hidden_state"
+
+    val decoderInputIDs: String = "input_ids"
+    val decoderEncoderAttentionMask: String = "encoder_attention_mask"
+    val decoderAttentionMask: String = "attention_mask"
+    val decoderPositionIDs: String = "position_ids"
+    val decoderBeamIdx: String = "beam_idx"
+    val decoderEncoderState: String = "encoder_hidden_states"
+
+    val decoderOutput: String = "logits"
+  }
+}
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala
new file mode 100644
index 00000000000000..486c22ff5ec9d2
--- /dev/null
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala
@@ -0,0 +1,520 @@
+/*
+ * Copyright 2017-2024 John Snow Labs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.johnsnowlabs.nlp.annotators.seq2seq
+
+import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig
+import com.johnsnowlabs.ml.ai.CoHere
+import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers
+import com.johnsnowlabs.ml.onnx.{OnnxWrapper, ReadOnnxModel, WriteOnnxModel}
+import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel}
+import com.johnsnowlabs.ml.util.LoadExternalModel.{
+  loadJsonStringAsset,
+  loadSentencePieceAsset,
+  loadTextAsset,
+  modelSanityCheck,
+  notSupportedEngineError
+}
+import com.johnsnowlabs.ml.util.{ONNX, Openvino}
+import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT
+import com.johnsnowlabs.nlp._
+import com.johnsnowlabs.ml.tensorflow.sentencepiece.{
+  ReadSentencePieceModel,
+  SentencePieceWrapper,
+  WriteSentencePieceModel
+}
+import com.johnsnowlabs.nlp.serialization.MapFeature
+import org.apache.spark.broadcast.Broadcast
+import org.apache.spark.ml.param._
+import org.apache.spark.ml.util.Identifiable
+import org.apache.spark.sql.SparkSession
+import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature}
+import org.json4s._
+import org.json4s.jackson.JsonMethods._
+
+/** Cohere: Command-R Transformer
+  *
+  * C4AI Command-R is a research release of a 35 billion parameter highly performant generative
+  * model. Command-R is a large language model with open weights optimized for a variety of use
+  * cases including reasoning, summarization, and question answering. Command-R has the capability
+  * for multilingual generation evaluated in 10 languages and highly performant RAG capabilities.
+  *
+  * Pretrained models can be loaded with `pretrained` of the companion object:
+  * {{{
+  * val CoHere = CoHereTransformer.pretrained()
+  *   .setInputCols("document")
+  *   .setOutputCol("generation")
+  * }}}
+  * The default model is `"cohere_35b_int4"`, if no name is provided. For available pretrained
+  * models please see the [[https://sparknlp.org/models?q=CoHere Models Hub]].
+  *
+  * For extended examples of usage, see
+  * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTestSpec.scala CoHereTestSpec]].
+  *
+  * '''References:'''
+  *   - [[https://cohere.for.ai CoHere]]
+  *
+  * '''Note:'''
+  *
+  * This is a resource-intensive module, especially with larger models and sequences. Use of
+  * accelerators such as GPUs is strongly recommended.
+  *
+  * ==Example==
+  * {{{
+  * import spark.implicits._
+  * import com.johnsnowlabs.nlp.base.DocumentAssembler
+  * import com.johnsnowlabs.nlp.annotators.seq2seq.CoHereTransformer
+  * import org.apache.spark.ml.Pipeline
+  *
+  * val documentAssembler = new DocumentAssembler()
+  *   .setInputCol("text")
+  *   .setOutputCol("documents")
+  *
+  * val CoHere = CoHereTransformer.pretrained("CoHere_3_7b_chat_hf_int8")
+  *   .setInputCols(Array("documents"))
+  *   .setMinOutputLength(15)
+  *   .setMaxOutputLength(60)
+  *   .setDoSample(false)
+  *   .setTopK(40)
+  *   .setNoRepeatNgramSize(3)
+  *   .setOutputCol("generation")
+  *
+  * val pipeline = new Pipeline().setStages(Array(documentAssembler, CoHere))
+  *
+  * val data = Seq(
+  *   (
+  *     1,
+  *     """
+  *     <|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hello, how are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>
+  *     """.stripMargin)
+  * ).toDF("id", "text")
+  *
+  * val result = pipeline.fit(data).transform(data)
+  *
+  * result.select("generation.result").show(truncate = false)
+  * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+  * |result                                                                                                                                                                                                  |
+  * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+  * |[Hello! I'm doing well, thank you for asking! I'm excited to help you with whatever questions you have today. How can I assist you?]                                                                         |
+  * +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+  * }}}
+  *
+  * @param uid
+  *   required uid for storing annotator to disk
+  * @groupname anno Annotator types
+  * @groupdesc anno
+  *   Required input and expected output annotator types
+  * @groupname Ungrouped Members
+  * @groupname param Parameters
+  * @groupname setParam Parameter setters
+  * @groupname getParam Parameter getters
+  * @groupname Ungrouped Members
+  * @groupprio param  1
+  * @groupprio anno  2
+  * @groupprio Ungrouped 3
+  * @groupprio setParam  4
+  * @groupprio getParam  5
+  * @groupdesc param
+  *   A list of (hyper-)parameter keys this annotator can take. Users can set and get the
+  *   parameter values through setters and getters, respectively.
+  */
+
+class CoHereTransformer(override val uid: String)
+    extends AnnotatorModel[CoHereTransformer]
+    with HasBatchedAnnotate[CoHereTransformer]
+    with ParamsAndFeaturesWritable
+    with WriteOnnxModel
+    with WriteOpenvinoModel
+    with HasGeneratorProperties
+    with HasEngine {
+
+  def this() = this(Identifiable.randomUID("CoHereTRANSFORMER"))
+
+  /** Input annotator type : DOCUMENT
+    *
+    * @group param
+    */
+  override val inputAnnotatorTypes: Array[AnnotatorType] = Array(DOCUMENT)
+
+  /** Output annotator type : DOCUMENT
+    *
+    * @group param
+    */
+  override val outputAnnotatorType: String = DOCUMENT
+
+  /** @group setParam */
+  def setRandomSeed(value: Int): CoHereTransformer.this.type = {
+    if (randomSeed.isEmpty) {
+      this.randomSeed = Some(value)
+    }
+    this
+  }
+
+  /** A list of token ids which are ignored in the decoder's output (Default: `Array()`)
+    *
+    * @group param
+    */
+  var ignoreTokenIds = new IntArrayParam(
+    this,
+    "ignoreTokenIds",
+    "A list of token ids which are ignored in the decoder's output")
+
+  /** @group setParam */
+  def setIgnoreTokenIds(tokenIds: Array[Int]): CoHereTransformer.this.type = {
+    set(ignoreTokenIds, tokenIds)
+  }
+
+  /** @group getParam */
+  def getIgnoreTokenIds: Array[Int] = $(ignoreTokenIds)
+
+  /** Vocabulary used to encode the words to ids with bpeTokenizer.encode
+    *
+    * @group param
+    */
+  val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected()
+
+  /** @group setParam */
+  def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value)
+
+  /** Holding merges.txt coming from RoBERTa model
+    *
+    * @group param
+    */
+  val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected()
+
+  /** @group setParam */
+  def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value)
+
+  /** Additional tokens to be added to the vocabulary
+    *
+    * @group param
+    */
+  val addedTokens: MapFeature[String, Int] = new MapFeature(this, "addedTokens").setProtected()
+
+  /** @group setParam */
+  def setAddedTokens(value: Map[String, Int]): this.type = set(addedTokens, value)
+
+  /** Stop tokens to terminate the generation
+    *
+    * @group param
+    */
+  override val stopTokenIds =
+    new IntArrayParam(this, "stopTokenIds", "Stop tokens to terminate the generation")
+
+  /** @group setParam */
+  override def setStopTokenIds(value: Array[Int]): this.type = {
+    set(stopTokenIds, value)
+  }
+
+  /** @group getParam */
+  override def getStopTokenIds: Array[Int] = $(stopTokenIds)
+
+  private var _model: Option[Broadcast[CoHere]] = None
+
+  val generationConfig: StructFeature[GenerationConfig] =
+    new StructFeature(this, "generationConfig").setProtected()
+
+  def setGenerationConfig(value: GenerationConfig): this.type =
+    set(generationConfig, value)
+
+  def getGenerationConfig: GenerationConfig = $$(generationConfig)
+
+  /** @group setParam */
+  def setModelIfNotSet(
+      spark: SparkSession,
+      onnxWrappers: Option[DecoderWrappers],
+      openvinoWrapper: Option[OpenvinoWrapper]): this.type = {
+    if (_model.isEmpty) {
+      _model = Some(
+        spark.sparkContext.broadcast(
+          new CoHere(
+            onnxWrappers,
+            openvinoWrapper,
+            $$(merges),
+            $$(vocabulary),
+            $$(addedTokens),
+            generationConfig = getGenerationConfig)))
+    }
+    this
+  }
+
+  /** @group getParam */
+  def getModelIfNotSet: CoHere = _model.get.value
+
+  setDefault(
+    minOutputLength -> 0,
+    maxOutputLength -> 20,
+    doSample -> false,
+    temperature -> 0.6,
+    topK -> -1,
+    topP -> 0.9,
+    repetitionPenalty -> 1.0,
+    noRepeatNgramSize -> 3,
+    ignoreTokenIds -> Array(),
+    batchSize -> 1,
+    beamSize -> 1,
+    maxInputLength -> 4096,
+    stopTokenIds -> Array(128001))
+
+  /** takes a document and annotations and produces new annotations of this annotator's annotation
+    * type
+    *
+    * @param batchedAnnotations
+    *   Annotations that correspond to inputAnnotationCols generated by previous annotators if any
+    * @return
+    *   any number of annotations processed for every input annotation. Not necessary one to one
+    *   relationship
+    */
+  override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = {
+
+    val allAnnotations = batchedAnnotations
+      .filter(_.nonEmpty)
+      .zipWithIndex
+      .flatMap { case (annotations, i) =>
+        annotations.filter(_.result.nonEmpty).map(x => (x, i))
+      }
+    val processedAnnotations = if (allAnnotations.nonEmpty) {
+      this.getModelIfNotSet.predict(
+        sentences = allAnnotations.map(_._1),
+        batchSize = $(batchSize),
+        minOutputLength = $(minOutputLength),
+        maxOutputLength = $(maxOutputLength),
+        doSample = $(doSample),
+        temperature = $(temperature),
+        topK = $(topK),
+        topP = $(topP),
+        repetitionPenalty = $(repetitionPenalty),
+        noRepeatNgramSize = $(noRepeatNgramSize),
+        randomSeed = this.randomSeed,
+        ignoreTokenIds = $(ignoreTokenIds),
+        beamSize = $(beamSize),
+        maxInputLength = $(maxInputLength),
+        stopTokenIds = $(stopTokenIds))
+    } else {
+      Seq()
+    }
+    Seq(processedAnnotations)
+  }
+
+  override def onWrite(path: String, spark: SparkSession): Unit = {
+    super.onWrite(path, spark)
+    getEngine match {
+      case ONNX.name =>
+        val wrappers = getModelIfNotSet.onnxWrappers
+        writeOnnxModels(
+          path,
+          spark,
+          Seq((wrappers.get.decoder, "decoder_model.onnx")),
+          CoHereTransformer.suffix)
+      case Openvino.name =>
+        val wrappers = getModelIfNotSet.openvinoWrapper
+        writeOpenvinoModel(
+          path,
+          spark,
+          wrappers.get,
+          CoHereTransformer.suffix,
+          CoHereTransformer.openvinoFile)
+    }
+  }
+}
+
+trait ReadablePretrainedCoHereTransformerModel
+    extends ParamsAndFeaturesReadable[CoHereTransformer]
+    with HasPretrained[CoHereTransformer] {
+  override val defaultModelName: Some[String] = Some("cohere_35b_int4")
+
+  /** Java compliant-overrides */
+  override def pretrained(): CoHereTransformer = super.pretrained()
+
+  override def pretrained(name: String): CoHereTransformer = super.pretrained(name)
+
+  override def pretrained(name: String, lang: String): CoHereTransformer =
+    super.pretrained(name, lang)
+
+  override def pretrained(name: String, lang: String, remoteLoc: String): CoHereTransformer =
+    super.pretrained(name, lang, remoteLoc)
+}
+
+trait ReadCoHereTransformerDLModel extends ReadOnnxModel with ReadOpenvinoModel {
+  this: ParamsAndFeaturesReadable[CoHereTransformer] =>
+
+  override val onnxFile: String = "CoHere_onnx"
+  val suffix: String = "_CoHere"
+  override val openvinoFile: String = "CoHere_openvino"
+
+  def readModel(instance: CoHereTransformer, path: String, spark: SparkSession): Unit = {
+    instance.getEngine match {
+      case ONNX.name =>
+        val wrappers =
+          readOnnxModels(path, spark, Seq("decoder_model.onnx"), suffix)
+        val onnxWrappers =
+          DecoderWrappers(decoder = wrappers("decoder_model.onnx"))
+        instance.setModelIfNotSet(spark, Some(onnxWrappers), None)
+      case Openvino.name =>
+        val ovWrapper =
+          readOpenvinoModel(path, spark, "_CoHere_ov")
+        instance.setModelIfNotSet(spark, None, Some(ovWrapper))
+      case _ =>
+        throw new Exception(notSupportedEngineError)
+    }
+  }
+
+  addReader(readModel)
+
+  def loadSavedModel(
+      modelPath: String,
+      spark: SparkSession,
+      useOpenvino: Boolean = false): CoHereTransformer = {
+    implicit val formats: DefaultFormats.type = DefaultFormats // for json4
+    val (localModelPath, detectedEngine) =
+      modelSanityCheck(modelPath, isDecoder = true)
+    val modelConfig: JValue =
+      parse(loadJsonStringAsset(localModelPath, "config.json"))
+
+    val beginSuppressTokens: Array[Int] =
+      (modelConfig \ "begin_suppress_tokens").extract[Array[Int]]
+
+    val suppressTokenIds: Array[Int] =
+      (modelConfig \ "suppress_tokens").extract[Array[Int]]
+
+    val forcedDecoderIds: Array[(Int, Int)] =
+      (modelConfig \ "forced_decoder_ids").extract[Array[Array[Int]]].map {
+        case idxWithTokenId: Array[Int] if idxWithTokenId.length == 2 =>
+          (idxWithTokenId(0), idxWithTokenId(1))
+        case _ =>
+          throw new Exception(
+            "Could not extract forced_decoder_ids. Should be a list of tuples with 2 entries.")
+      }
+
+    def arrayOrNone[T](array: Array[T]): Option[Array[T]] =
+      if (array.nonEmpty) Some(array) else None
+
+    val bosTokenId = (modelConfig \ "bos_token_id").extract[Int]
+    val eosTokenId = (modelConfig \ "eos_token_id").extract[Int]
+    val padTokenId = (modelConfig \ "eos_token_id").extract[Int]
+    val vocabSize = (modelConfig \ "vocab_size").extract[Int]
+
+    // Check if tokenizer.json exists
+    val tokenizerPath = s"$localModelPath/assets/tokenizer.json"
+    val tokenizerExists = new java.io.File(tokenizerPath).exists()
+    val (vocabs, addedTokens, bytePairs) = if (tokenizerExists) {
+      val tokenizerConfig: JValue = parse(loadJsonStringAsset(localModelPath, "tokenizer.json"))
+      // extract vocab from tokenizer.json ( model -> vocab)
+      var vocabs: Map[String, Int] =
+        (tokenizerConfig \ "model" \ "vocab").extract[Map[String, Int]]
+
+      // extract merges from tokenizer.json ( model -> merges)
+      val bytePairs = (tokenizerConfig \ "model" \ "merges")
+        .extract[List[Array[String]]]
+        .filter(w => w.length == 2)
+        .map { case Array(c1, c2) => (c1, c2) }
+        .zipWithIndex
+        .toMap
+
+      // extract added_tokens from tokenizer.json (added_tokens)
+      // "added_tokens": [
+      //    {
+      //      "id": 128000,
+      //      "content": "<|begin_of_text|>",
+      //      "single_word": false,
+      //      "lstrip": false,
+      //      "rstrip": false,
+      //      "normalized": false,
+      //      "special": true
+      //    }, ...
+      //  ]
+      val addedTokens = (tokenizerConfig \ "added_tokens")
+        .extract[List[Map[String, Any]]]
+        .map { token =>
+          val id = token("id").asInstanceOf[BigInt].intValue()
+          val content = token("content").asInstanceOf[String]
+          (content, id)
+        }
+        .toMap
+
+      // update vocab with added tokens
+      addedTokens.foreach { case (content, id) =>
+        vocabs += (content -> id)
+      }
+      (vocabs, addedTokens, bytePairs)
+    } else {
+      val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap
+      val addedTokens = loadTextAsset(localModelPath, "added_tokens.txt").zipWithIndex.toMap
+      val bytePairs = loadTextAsset(localModelPath, "merges.txt")
+        .map(_.split(" "))
+        .filter(w => w.length == 2)
+        .map { case Array(c1, c2) => (c1, c2) }
+        .zipWithIndex
+        .toMap
+      (vocabs, addedTokens, bytePairs)
+    }
+    val annotatorModel = new CoHereTransformer()
+      .setGenerationConfig(
+        GenerationConfig(
+          bosTokenId,
+          padTokenId,
+          eosTokenId,
+          vocabSize,
+          arrayOrNone(beginSuppressTokens),
+          arrayOrNone(suppressTokenIds),
+          arrayOrNone(forcedDecoderIds)))
+      .setVocabulary(vocabs)
+      .setMerges(bytePairs)
+      .setAddedTokens(addedTokens)
+
+    val modelEngine =
+      if (useOpenvino)
+        Openvino.name
+      else
+        detectedEngine
+    annotatorModel.set(annotatorModel.engine, modelEngine)
+
+    detectedEngine match {
+      case ONNX.name =>
+        val onnxWrapperDecoder =
+          OnnxWrapper.read(
+            spark,
+            localModelPath,
+            zipped = false,
+            useBundle = true,
+            modelName = "decoder_model")
+
+        val onnxWrappers = DecoderWrappers(onnxWrapperDecoder)
+
+        annotatorModel
+          .setModelIfNotSet(spark, Some(onnxWrappers), None)
+      case Openvino.name =>
+        val openvinoWrapper =
+          OpenvinoWrapper.read(
+            spark,
+            localModelPath,
+            zipped = false,
+            useBundle = true,
+            detectedEngine = detectedEngine)
+        annotatorModel.setModelIfNotSet(spark, None, Some(openvinoWrapper))
+
+      case _ =>
+        throw new Exception(notSupportedEngineError)
+    }
+
+    annotatorModel
+  }
+
+}
+
+object CoHereTransformer
+    extends ReadablePretrainedCoHereTransformerModel
+    with ReadCoHereTransformerDLModel
diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTestSpec.scala
new file mode 100644
index 00000000000000..d3df41d1b31ef4
--- /dev/null
+++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTestSpec.scala
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-2023 John Snow Labs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.johnsnowlabs.nlp.annotators.seq2seq
+
+import com.johnsnowlabs.nlp.base.DocumentAssembler
+import com.johnsnowlabs.nlp.util.io.ResourceHelper
+import com.johnsnowlabs.tags.{SlowTest, FastTest}
+import org.apache.spark.ml.Pipeline
+import org.scalatest.flatspec.AnyFlatSpec
+
+class CoHereTestSpec extends AnyFlatSpec {
+
+  "CoHere" should "should handle temperature=0 correctly and not crash when predicting more than 1 element with doSample=True" taggedAs SlowTest in {
+    // Even tough the Paper states temperature in interval [0,1), using temperature=0 will result in division by 0 error.
+    // Also DoSample=True may result in infinities being generated and distFiltered.length==0 which results in exception if we don't return 0 instead internally.
+    val testData = ResourceHelper.spark
+      .createDataFrame(
+        Seq((
+          1,
+          """<|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hello, how are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>
+            """.stripMargin)))
+      .toDF("id", "text")
+      .repartition(1)
+    val documentAssembler = new DocumentAssembler()
+      .setInputCol("text")
+      .setOutputCol("documents")
+
+    val CoHere = CoHereTransformer
+      .pretrained()
+      .setInputCols(Array("documents"))
+      .setDoSample(false)
+      .setMaxOutputLength(50)
+      .setOutputCol("generation")
+      .setBeamSize(1)
+      .setStopTokenIds(Array(255001))
+      .setTemperature(0.6)
+      .setTopP(0.9)
+      .setTopK(-1)
+    val pipeline = new Pipeline()
+      .setStages(Array(documentAssembler, CoHere))
+
+    val pipelineModel = pipeline.fit(testData)
+
+    pipelineModel
+      .transform(testData)
+      .show(truncate = false)
+
+    pipelineModel
+      .transform(testData)
+      .show(truncate = false)
+
+    pipelineModel.stages.last
+      .asInstanceOf[CoHereTransformer]
+      .write
+      .overwrite()
+      .save("/tmp/CoHere-7b-4bit-model")
+
+    val loadedCoHere = CoHereTransformer.load("/tmp/CoHere-7b-4bit-model")
+
+    val loadedPipeline = new Pipeline().setStages(Array(documentAssembler, loadedCoHere))
+
+    loadedPipeline
+      .fit(testData)
+      .transform(testData)
+      .show(truncate = false)
+
+  }
+}

From 133b326cf222d45798536b3924df62558bc313a6 Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Thu, 14 Nov 2024 04:14:24 +0000
Subject: [PATCH 051/108] Cohere Notebook

Signed-off-by: Prabod Rathnayaka 
---
 ...ingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb | 2521 +++++++++++++++++
 1 file changed, 2521 insertions(+)
 create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb

diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb
new file mode 100644
index 00000000000000..7d13d50b8b40d9
--- /dev/null
+++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb
@@ -0,0 +1,2521 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "id": "FvX_yCcI4W7D"
+   },
+   "source": [
+    "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n",
+    "\n",
+    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "id": "8J48sFcb4W7G"
+   },
+   "source": [
+    "# Import OpenVINO CoHere models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n",
+    "\n",
+    "This notebook provides a detailed walkthrough on optimizing and importing CoHere models from HuggingFace  for use in Spark NLP, with [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html). The focus is on converting the model to the OpenVINO format and applying precision optimizations (INT8 and INT4), to enhance the performance and efficiency on CPU platforms using [Optimum Intel](https://huggingface.co/docs/optimum/main/en/intel/inference).\n",
+    "\n",
+    "Let's keep in mind a few things before we start ๐Ÿ˜Š\n",
+    "\n",
+    "- OpenVINO support was introduced in  `Spark NLP 5.4.0`, enabling high performance CPU inference for models. So please make sure you have upgraded to the latest Spark NLP release.\n",
+    "- Model quantization is a computationally expensive process, so it is recommended to use a runtime with more than 32GB memory for exporting the quantized model from HuggingFace.\n",
+    "- You can import LLama models via `LlamaModel`. These models are usually under `Text Generation` category and have `CoHere` in their labels.\n",
+    "- Reference: [LlamaModel](https://huggingface.co/docs/transformers/model_doc/llama#transformers.LlamaModel)\n",
+    "- Some [example models](https://huggingface.co/models?search=CoHere)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "id": "Ko24PkTd4W7H"
+   },
+   "source": [
+    "## 1. Export and Save the HuggingFace model\n",
+    "\n",
+    "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n",
+    "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future release, but we wanted you to know which versions have been tested successfully."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "colab": {
+     "base_uri": "https://localhost:8080/"
+    },
+    "id": "2rOdslOi4W7H",
+    "outputId": "0fe0d124-f09d-4fc0-b822-655d7b616125"
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\u001b[2K     \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m43.8/43.8 kB\u001b[0m \u001b[31m722.3 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m9.1/9.1 MB\u001b[0m \u001b[31m21.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m38.7/38.7 MB\u001b[0m \u001b[31m12.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m223.4/223.4 kB\u001b[0m \u001b[31m12.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m527.3/527.3 kB\u001b[0m \u001b[31m26.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m421.5/421.5 kB\u001b[0m \u001b[31m23.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m15.9/15.9 MB\u001b[0m \u001b[31m14.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m8.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m39.9/39.9 MB\u001b[0m \u001b[31m28.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m1.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m10.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m15.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m7.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n",
+      "cudf-cu12 24.4.1 requires pyarrow<15.0.0a0,>=14.0.1, but you have pyarrow 17.0.0 which is incompatible.\n",
+      "ibis-framework 8.0.0 requires pyarrow<16,>=2, but you have pyarrow 17.0.0 which is incompatible.\u001b[0m\u001b[31m\n",
+      "\u001b[0m  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+      "\u001b[2K     \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m68.4/68.4 kB\u001b[0m \u001b[31m3.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K     \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m207.3/207.3 kB\u001b[0m \u001b[31m15.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[?25h  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.2/1.2 MB\u001b[0m \u001b[31m32.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m307.2/307.2 kB\u001b[0m \u001b[31m12.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m4.2/4.2 MB\u001b[0m \u001b[31m34.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m249.1/249.1 kB\u001b[0m \u001b[31m8.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m76.0/76.0 kB\u001b[0m \u001b[31m4.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[?25h  Building wheel for jstyleson (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+      "  Building wheel for grapheme (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m417.5/417.5 kB\u001b[0m \u001b[31m12.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m15.7/15.7 MB\u001b[0m \u001b[31m58.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m755.5/755.5 MB\u001b[0m \u001b[31m2.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m166.0/166.0 MB\u001b[0m \u001b[31m5.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[2K   \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m167.9/167.9 MB\u001b[0m \u001b[31m6.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+      "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n",
+      "torchaudio 2.3.1+cu121 requires torch==2.3.1, but you have torch 2.2.1 which is incompatible.\n",
+      "torchtext 0.18.0 requires torch>=2.3.0, but you have torch 2.2.1 which is incompatible.\n",
+      "torchvision 0.18.1+cu121 requires torch==2.3.1, but you have torch 2.2.1 which is incompatible.\u001b[0m\u001b[31m\n",
+      "\u001b[0m"
+     ]
+    }
+   ],
+   "source": [
+    "!pip install -q --upgrade transformers==4.41.2\n",
+    "!pip install -q --upgrade openvino==2024.1\n",
+    "!pip install -q --upgrade optimum-intel\n",
+    "!pip install -q --upgrade nncf\n",
+    "!pip install -q --upgrade huggingface_hub\n",
+    "!pip install -q --upgrade onnx==1.15.0\n",
+    "!pip install -q --upgrade torch==2.2.1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "colab": {
+     "base_uri": "https://localhost:8080/",
+     "height": 145,
+     "referenced_widgets": [
+      "8420c288f5e44084af6589d767899664",
+      "a03258e8bcb241b2be89ac5c03fba9fe",
+      "0540ea7b02994fa1a8318a7d2f38c12c",
+      "4f57921b6c234eabae3f424afe3c04b5",
+      "97bca2fe9b06436ab7174a8e0b921fcf",
+      "529731f33fb242d9a1d283931beaa70f",
+      "1b76dafe2da64c1fa55e52a5f83715c9",
+      "8087b4ffd55b450ca453fd4c5ffd21f9",
+      "ee6313eca4be4f6b9d386b2c27624452",
+      "e23e8b6170294d4999b90a293da45b19",
+      "452dbb332660410ca9b94d11017075c0",
+      "f15d2dd70cee40899a34443cd1589e21",
+      "b20f5c394c9b4c7e9a7d68c1c1dd89ba",
+      "374c8537fa7443d4aa6f6b8047fc090b",
+      "8cecf94197a040e88791faddd5df7698",
+      "d52ee940ddd64d44aa8d08ad032f4225",
+      "65686043fcb4475baa17734312cc7f7d",
+      "6d4a762cf1f847a59c5e2acf27d3780b",
+      "cb0cf954d70d4a20b45b6a7a5508d05d",
+      "174693aa52194cae9bde419572ac117e",
+      "ec830e5068ef40a7b596fef9908e9c0b",
+      "9f994a6df3b94907a6da46c63209dac2",
+      "1ca22e25121b4d36a7a8bd88c6d39efe",
+      "3154cd7ba0b841bf909030a40dba671a",
+      "68b4590ad1bf4eebb05be97c3445bf11",
+      "90ac8ccbb2c447b79064050316b4fa1e",
+      "446c4a71c2574673b4f54d06ff24a4ba",
+      "12e23151dcc74313be8c7e02b0f4ea05",
+      "613ffc0f9ac74c0fab8f3cb05f9deb43",
+      "8cf69353a540492a8f81795d635e9069",
+      "9802c5078cb245a793c8ab8a97e370ca",
+      "4fed2ab467c94954b8b463b96c751715"
+     ]
+    },
+    "id": "bYxXi0Gr4W7J",
+    "outputId": "a421b770-6287-439a-c892-816448fc23f5"
+   },
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "8420c288f5e44084af6589d767899664",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "VBox(children=(HTML(value='
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/markdown": [ + "`optimum-cli export openvino --model CohereForAI/c4ai-command-r-v01 c4ai-command-r-v01/INT4 --weight-format int4 --task text-generation-with-past --group-size 128 --ratio 1 --all-layers`" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Loading checkpoint shards: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 15/15 [00:03<00:00, 4.41it/s]\n", + "We detected that you are passing `past_key_values` as a tuple of tuples. This is deprecated and will be removed in v4.47. Please convert your cache or use an appropriate `Cache` class (https://huggingface.co/docs/transformers/kv_cache#legacy-cache-format)\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/cache_utils.py:447: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n", + " or len(self.key_cache[layer_idx]) == 0 # the layer has no cache\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/optimum/exporters/openvino/model_patcher.py:496: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + " if sequence_length != 1:\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/cache_utils.py:432: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n", + " elif len(self.key_cache[layer_idx]) == 0: # fills previously skipped layers; checking for tensor causes errors\n", + "Starting from v4.46, the `logits` model output will have the same type as the model (except at train time, where it will always be FP32)\n", + "Exporting tokenizers to OpenVINO is not supported for tokenizers version > 0.19. Please downgrade to tokenizers version <= 0.19 to export tokenizers to OpenVINO.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:nncf:Statistics of the bitwidth distribution:\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n", + "โ”‚ Num bits (N) โ”‚ % all parameters (layers) โ”‚ % ratio-defining parameters (layers) โ”‚\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n", + "โ”‚ 4 โ”‚ 100% (281 / 281) โ”‚ 100% (281 / 281) โ”‚\n", + "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”๏ฟฝ๏ฟฝ๏ฟฝโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n", + "\u001b[2KApplying Weight Compression \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[35m100%\u001b[0m โ€ข \u001b[36m0:28:04\u001b[0m โ€ข \u001b[36m0:00:00\u001b[0m00:04\u001b[0m01:04\u001b[0m\n", + "\u001b[?25h" + ] + } + ], + "source": [ + "from cmd_helper import optimum_cli\n", + "\n", + "model_id = \"CohereForAI/c4ai-command-r-v01\"\n", + "model_path = Path(model_id.split(\"/\")[-1]) / \"INT4\"\n", + "\n", + "\n", + "if not model_path.exists():\n", + " optimum_cli(\n", + " model_id,\n", + " model_path,\n", + " additional_args={\"weight-format\": \"int4\", \"task\": \"text-generation-with-past\",\"group-size\": \"128\", \"ratio\": \"1\", \"all-layers\": \"\"},\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "n4_STbc7kJji" + }, + "source": [ + "Once the model export and quantization is complete, move the model assets needed for tokenization in Spark NLP to the `assets` directory." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PP6xDXDC4W7K" + }, + "source": [ + "Let's have a look inside these two directories and see what we are dealing with:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "EXPORT_PATH = model_path" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "EOLmL1S14W7K", + "outputId": "32f9bf09-3b78-43b8-e250-9bc24aa4d4ad" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 17771924\n", + "-rw-rw-r-- 1 prabod prabod 770 Nov 14 02:21 config.json\n", + "-rw-rw-r-- 1 prabod prabod 137 Nov 14 02:21 generation_config.json\n", + "-rw-rw-r-- 1 prabod prabod 18174804540 Nov 14 02:53 openvino_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 3473013 Nov 14 02:53 openvino_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 439 Nov 14 02:22 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 20719 Nov 14 02:22 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 20124090 Nov 14 02:22 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -l {EXPORT_PATH}" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "assets_dir = EXPORT_PATH / \"assets\"\n", + "assets_dir.mkdir(exist_ok=True)\n", + "\n", + "# copy all the assets to the assets directory (json files, vocab files, etc.)\n", + "\n", + "import shutil\n", + "\n", + "# copy all json files\n", + "\n", + "for file in EXPORT_PATH.glob(\"*.json\"):\n", + " shutil.copy(file, assets_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "zQ1SbNAc4W7K", + "outputId": "bbb93961-3dbf-459f-d3c0-bdca7965bf53" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 19692\n", + "-rw-rw-r-- 1 prabod prabod 770 Nov 14 03:45 config.json\n", + "-rw-rw-r-- 1 prabod prabod 137 Nov 14 03:45 generation_config.json\n", + "-rw-rw-r-- 1 prabod prabod 439 Nov 14 03:45 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 20719 Nov 14 03:45 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 20124090 Nov 14 03:45 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -l {EXPORT_PATH}/assets" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "svbT3OG24W7L" + }, + "source": [ + "## 2. Import and Save CoHere in Spark NLP\n", + "\n", + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "z6TWf2r14W7L" + }, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OYI03iqp4W7L" + }, + "source": [ + "Let's start Spark with Spark NLP included via our simple `start()` function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7_Oy0zMi4W7L" + }, + "outputs": [], + "source": [ + "import sparknlp\n", + "\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aXCJqb9i4W7M" + }, + "source": [ + "- Let's use `loadSavedModel` functon in `CoHereTransformer` which allows us to load the OpenVINO model.\n", + "- Most params will be set automatically. They can also be set later after loading the model in `CoHereTransformer` during runtime, so don't worry about setting them now.\n", + "- `loadSavedModel` accepts two params, first is the path to the exported model. The second is the SparkSession that is `spark` variable we previously started via `sparknlp.start()`\n", + "- NOTE: `loadSavedModel` accepts local paths in addition to distributed file systems such as `HDFS`, `S3`, `DBFS`, etc. This feature was introduced in Spark NLP 4.2.2 release. Keep in mind the best and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively.st and recommended way to move/share/reuse Spark NLP models is to use `write.save` so you can use `.load()` from any file systems natively." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "T3591W9R4W7M" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "24/11/14 04:03:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native17996866745707494714/libtbb.so.2\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING: An illegal reflective access operation has occurred\n", + "WARNING: Illegal reflective access by org.apache.spark.util.SizeEstimator$ (file:/home/prabod/spark/jars/spark-core_2.12-3.3.2.jar) to field java.util.regex.Pattern.pattern\n", + "WARNING: Please consider reporting this to the maintainers of org.apache.spark.util.SizeEstimator$\n", + "WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n", + "WARNING: All illegal access operations will be denied in a future release\n" + ] + } + ], + "source": [ + "from sparknlp.annotator import *\n", + "\n", + "CoHere = CoHereTransformer \\\n", + " .loadSavedModel(EXPORT_PATH, spark) \\\n", + " .setMaxOutputLength(500) \\\n", + " .setDoSample(False) \\\n", + " .setBeamSize(1) \\\n", + " .setInputCols([\"documents\"]) \\\n", + " .setOutputCol(\"generation\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9X3RphM-4W7M" + }, + "source": [ + "Let's save it on disk so it is easier to be moved around and also be used later via `.load` function" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "MODEL_NAME = \"CohereForAI/c4ai-command-r-v01\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "T6GaugQa4W7M" + }, + "outputs": [], + "source": [ + "CoHere.write().overwrite().save(f\"{MODEL_NAME}_spark_nlp\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "o0kroa6u4W7M" + }, + "source": [ + "Let's clean up stuff we don't need anymore" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "BHvWriCn4W7M" + }, + "outputs": [], + "source": [ + "!rm -rf {EXPORT_PATH}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Gz4cU4Q54W7N" + }, + "source": [ + "Awesome ๐Ÿ˜Ž !\n", + "\n", + "This is your OpenVINO CoHere model from HuggingFace ๐Ÿค— loaded and saved by Spark NLP ๐Ÿš€" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "17klLp1M4W7N", + "outputId": "eccfaaba-5b98-4914-dcfc-aedb8de3d285" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 4141828\n", + "drwxr-xr-x 3 root root 4096 Jun 6 16:35 fields\n", + "-rw-r--r-- 1 root root 4240712291 Jun 6 16:36 llama2_openvino\n", + "-rw-r--r-- 1 root root 499723 Jun 6 16:36 llama2_spp\n", + "drwxr-xr-x 2 root root 4096 Jun 6 16:35 metadata\n" + ] + } + ], + "source": [ + "! ls -l {MODEL_NAME}_spark_nlp" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3R_rS8Fj4W7N" + }, + "source": [ + "Now let's see how we can use it on other machines, clusters, or any place you wish to use your new and shiny CoHere model ๐Ÿ˜Š" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "uxSo5-b24W7N", + "outputId": "c4c91a3a-de46-41d7-98c7-e301fbe9419a" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Stage 3:=======================================================> (30 + 1) / 31]\r" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|result |\n", + "+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[ Hello, how are you?Hello! I'm doing well, thank you for asking! I'm excited to help you with whatever questions you have today. How can I assist you?]|\n", + "+--------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "import sparknlp\n", + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.ml import Pipeline\n", + "\n", + "test_data = spark.createDataFrame([\n", + " (\n", + " 1,\n", + " \"<|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hello, how are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>\"\n", + " )\n", + " ]).toDF(\"id\", \"text\")\n", + "\n", + "\n", + "document_assembler = DocumentAssembler() \\\n", + " .setInputCol(\"text\") \\\n", + " .setOutputCol(\"documents\")\n", + "\n", + "CoHere = CoHereTransformer \\\n", + " .load(f\"{MODEL_NAME}_spark_nlp\") \\\n", + " .setMaxOutputLength(50) \\\n", + " .setDoSample(False) \\\n", + " .setBeamSize(1) \\\n", + " .setInputCols([\"documents\"]) \\\n", + " .setOutputCol(\"generation\")\n", + "\n", + "pipeline = Pipeline().setStages([document_assembler, CoHere])\n", + "results = pipeline.fit(test_data).transform(test_data)\n", + "\n", + "results.select(\"generation.result\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PdvQAAfo4W7N" + }, + "source": [ + "That's it! You can now go wild and use hundreds of CoHere models from HuggingFace ๐Ÿค— in Spark NLP ๐Ÿš€\n" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "tempspark", + "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.8.16" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "0340296c8770497d84982352c35708ea": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_13f85f42ec2941998d4e4f241e15d88a", + "placeholder": "โ€‹", + "style": "IPY_MODEL_7cac4aa1adf84d338e7de9f3ac91bd47", + "value": "โ€‡2/2โ€‡[00:39<00:00,โ€‡18.11s/it]" + } + }, + "0540ea7b02994fa1a8318a7d2f38c12c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "PasswordModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "PasswordModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "PasswordView", + "continuous_update": true, + "description": "Token:", + "description_tooltip": null, + "disabled": false, + "layout": "IPY_MODEL_e23e8b6170294d4999b90a293da45b19", + "placeholder": "โ€‹", + "style": "IPY_MODEL_452dbb332660410ca9b94d11017075c0", + "value": "" + } + }, + "09dd4d814d1a43719a4dfd145ffe5d1d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_dccf73bd549b4e25ae1da68d0f3931fc", + "placeholder": "โ€‹", + "style": "IPY_MODEL_a0f81e62e3f74e418fae0c9e08830f2a", + "value": "Loadingโ€‡checkpointโ€‡shards:โ€‡100%" + } + }, + "12e23151dcc74313be8c7e02b0f4ea05": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "13f85f42ec2941998d4e4f241e15d88a": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "174693aa52194cae9bde419572ac117e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "1b76dafe2da64c1fa55e52a5f83715c9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": "center", + "align_self": null, + "border": null, + "bottom": null, + "display": "flex", + "flex": null, + "flex_flow": "column", + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": "50%" + } + }, + "1ca22e25121b4d36a7a8bd88c6d39efe": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "LabelModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "LabelView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_613ffc0f9ac74c0fab8f3cb05f9deb43", + "placeholder": "โ€‹", + "style": "IPY_MODEL_8cf69353a540492a8f81795d635e9069", + "value": "Your token has been saved to /root/.cache/huggingface/token" + } + }, + "3154cd7ba0b841bf909030a40dba671a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "LabelModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "LabelView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_9802c5078cb245a793c8ab8a97e370ca", + "placeholder": "โ€‹", + "style": "IPY_MODEL_4fed2ab467c94954b8b463b96c751715", + "value": "Login successful" + } + }, + "36adb757251e475b9d854456b6a59a60": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_8291ca2579ee4c3bbaf3bf34614e865f", + "placeholder": "โ€‹", + "style": "IPY_MODEL_5ae825fa761a4cdab40831ec71624dfa", + "value": "Loadingโ€‡checkpointโ€‡shards:โ€‡โ€‡25%" + } + }, + "374c8537fa7443d4aa6f6b8047fc090b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "446c4a71c2574673b4f54d06ff24a4ba": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "452dbb332660410ca9b94d11017075c0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "48199a26cd8047acbe897c6600919b67": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "4f57921b6c234eabae3f424afe3c04b5": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "CheckboxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "CheckboxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "CheckboxView", + "description": "Add token as git credential?", + "description_tooltip": null, + "disabled": false, + "indent": true, + "layout": "IPY_MODEL_f15d2dd70cee40899a34443cd1589e21", + "style": "IPY_MODEL_b20f5c394c9b4c7e9a7d68c1c1dd89ba", + "value": true + } + }, + "4fed2ab467c94954b8b463b96c751715": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "529731f33fb242d9a1d283931beaa70f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_d52ee940ddd64d44aa8d08ad032f4225", + "placeholder": "โ€‹", + "style": "IPY_MODEL_65686043fcb4475baa17734312cc7f7d", + "value": "\nPro Tip: If you don't already have one, you can create a dedicated\n'notebooks' token with 'write' access, that you can then easily reuse for all\nnotebooks.
" + } + }, + "5ae825fa761a4cdab40831ec71624dfa": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "613ffc0f9ac74c0fab8f3cb05f9deb43": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "65686043fcb4475baa17734312cc7f7d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "68b4590ad1bf4eebb05be97c3445bf11": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6d4a762cf1f847a59c5e2acf27d3780b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "LabelModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "LabelView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_cb0cf954d70d4a20b45b6a7a5508d05d", + "placeholder": "โ€‹", + "style": "IPY_MODEL_174693aa52194cae9bde419572ac117e", + "value": "Connecting..." + } + }, + "6e7be2d51b3b4bd4967a0d0193078629": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7cac4aa1adf84d338e7de9f3ac91bd47": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "8087b4ffd55b450ca453fd4c5ffd21f9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "80a1163781ca4b76952de9b2dc3b6fb1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_36adb757251e475b9d854456b6a59a60", + "IPY_MODEL_cb7635efbf78425e82caafc51e05588d", + "IPY_MODEL_fadeed4224c44883942b67fee7691241" + ], + "layout": "IPY_MODEL_a959a396bdeb4d51b5819ba8ee12be03" + } + }, + "8291ca2579ee4c3bbaf3bf34614e865f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "830590bf17d7419c915bfd27aff3b9c3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8420c288f5e44084af6589d767899664": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "VBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "VBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "VBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_ec830e5068ef40a7b596fef9908e9c0b", + "IPY_MODEL_9f994a6df3b94907a6da46c63209dac2", + "IPY_MODEL_1ca22e25121b4d36a7a8bd88c6d39efe", + "IPY_MODEL_3154cd7ba0b841bf909030a40dba671a" + ], + "layout": "IPY_MODEL_1b76dafe2da64c1fa55e52a5f83715c9" + } + }, + "84fd5763774748eeb36f33dcb4bbe83f": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "89582782ab634fd1a59994272f817d00": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "8cecf94197a040e88791faddd5df7698": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ButtonStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "button_color": null, + "font_weight": "" + } + }, + "8cf69353a540492a8f81795d635e9069": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "90ac8ccbb2c447b79064050316b4fa1e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "95828bd4ddd54be4bf441952d22ae080": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "97aba788d25a48bb9aed50f0802d76f0": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_84fd5763774748eeb36f33dcb4bbe83f", + "max": 2, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_89582782ab634fd1a59994272f817d00", + "value": 2 + } + }, + "97bca2fe9b06436ab7174a8e0b921fcf": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ButtonModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ButtonView", + "button_style": "", + "description": "Login", + "disabled": false, + "icon": "", + "layout": "IPY_MODEL_374c8537fa7443d4aa6f6b8047fc090b", + "style": "IPY_MODEL_8cecf94197a040e88791faddd5df7698", + "tooltip": "" + } + }, + "9802c5078cb245a793c8ab8a97e370ca": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "9a504923ea28417897157cc07065fe26": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_09dd4d814d1a43719a4dfd145ffe5d1d", + "IPY_MODEL_97aba788d25a48bb9aed50f0802d76f0", + "IPY_MODEL_0340296c8770497d84982352c35708ea" + ], + "layout": "IPY_MODEL_830590bf17d7419c915bfd27aff3b9c3" + } + }, + "9b2f731950544dc1be7a5671fc1efe63": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_95828bd4ddd54be4bf441952d22ae080", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
Applying Weight Compression โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 100% 226/226 โ€ข 0:03:46 โ€ข 0:00:00\n
\n", + "text/plain": "Applying Weight Compression \u001b[38;2;114;156;31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[35m100%\u001b[0m \u001b[38;2;0;104;181m226/226\u001b[0m โ€ข \u001b[38;2;0;104;181m0:03:46\u001b[0m โ€ข \u001b[38;2;0;104;181m0:00:00\u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "9e5afb290c1b4320a95d328c74566123": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_df213f2f24d347a1aedf121c8d071345", + "msg_id": "", + "outputs": [ + { + "data": { + "text/html": "
Mixed-Precision assignment โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” 100% 224/224 โ€ข 0:04:06 โ€ข 0:00:00\n
\n", + "text/plain": "Mixed-Precision assignment \u001b[38;2;114;156;31mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[35m100%\u001b[0m \u001b[38;2;0;104;181m224/224\u001b[0m โ€ข \u001b[38;2;0;104;181m0:04:06\u001b[0m โ€ข \u001b[38;2;0;104;181m0:00:00\u001b[0m\n" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "9f994a6df3b94907a6da46c63209dac2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "LabelModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "LabelView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_446c4a71c2574673b4f54d06ff24a4ba", + "placeholder": "โ€‹", + "style": "IPY_MODEL_12e23151dcc74313be8c7e02b0f4ea05", + "value": "Your token has been saved in your configured git credential helpers (store)." + } + }, + "a03258e8bcb241b2be89ac5c03fba9fe": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_8087b4ffd55b450ca453fd4c5ffd21f9", + "placeholder": "โ€‹", + "style": "IPY_MODEL_ee6313eca4be4f6b9d386b2c27624452", + "value": "
" + } + }, + "a0f81e62e3f74e418fae0c9e08830f2a": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "a959a396bdeb4d51b5819ba8ee12be03": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b20f5c394c9b4c7e9a7d68c1c1dd89ba": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "c0c8f56586684c95a71f5926b1ecc4fb": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "c354400f56d84c19ab16fd9533bc4abf": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "cb0cf954d70d4a20b45b6a7a5508d05d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "cb7635efbf78425e82caafc51e05588d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ProgressView", + "bar_style": "", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_48199a26cd8047acbe897c6600919b67", + "max": 4, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_c354400f56d84c19ab16fd9533bc4abf", + "value": 1 + } + }, + "d52ee940ddd64d44aa8d08ad032f4225": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dccf73bd549b4e25ae1da68d0f3931fc": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "df213f2f24d347a1aedf121c8d071345": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e23e8b6170294d4999b90a293da45b19": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ec830e5068ef40a7b596fef9908e9c0b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "LabelModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "LabelModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "LabelView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_68b4590ad1bf4eebb05be97c3445bf11", + "placeholder": "โ€‹", + "style": "IPY_MODEL_90ac8ccbb2c447b79064050316b4fa1e", + "value": "Token is valid (permission: write)." + } + }, + "ee6313eca4be4f6b9d386b2c27624452": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "f15d2dd70cee40899a34443cd1589e21": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fadeed4224c44883942b67fee7691241": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_6e7be2d51b3b4bd4967a0d0193078629", + "placeholder": "โ€‹", + "style": "IPY_MODEL_c0c8f56586684c95a71f5926b1ecc4fb", + "value": "โ€‡1/4โ€‡[00:27<01:22,โ€‡27.51s/it]" + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From 028ca67c873938acc4e04171172cd07ccc69c2ff Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 13 Feb 2025 09:52:40 +0000 Subject: [PATCH 052/108] update documentation and resource downloader entry Signed-off-by: Prabod Rathnayaka --- .../transformer_entries/CoHereTransformer.md | 110 +++++++++++ ...ingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb | 177 ++++++++---------- .../annotator/seq2seq/cohere_transformer.py | 8 +- .../seq2seq/CoHereTransformer.scala | 8 +- .../nlp/pretrained/ResourceDownloader.scala | 3 +- 5 files changed, 197 insertions(+), 109 deletions(-) create mode 100644 docs/en/transformer_entries/CoHereTransformer.md diff --git a/docs/en/transformer_entries/CoHereTransformer.md b/docs/en/transformer_entries/CoHereTransformer.md new file mode 100644 index 00000000000000..3fe8f81ff62c01 --- /dev/null +++ b/docs/en/transformer_entries/CoHereTransformer.md @@ -0,0 +1,110 @@ + + +{%- capture title -%} +CoHereTransformer +{%- endcapture -%} + +{%- capture description -%} +Text Generation using Cohere Command-R. + +C4AI Command-R is a research release of a 35 billion parameter highly performant generative model. +Command-R is a large language model with open weights optimized for a variety of use cases including reasoning, +summarization, and question answering. Command-R has the capability for multilingual generation evaluated +in 10 languages and highly performant RAG capabilities. + +Pretrained models can be loaded with `pretrained` of the companion object: + +```scala +val CoHere = CoHereTransformer.pretrained() + .setInputCols("document") + .setOutputCol("generation") +``` +{%- capture input_anno -%} +DOCUMENT +{%- endcapture -%} + +{%- capture output_anno -%} +DOCUMENT +{%- endcapture -%} + +{%- capture python_example -%} +import sparknlp +from sparknlp.base import * +from sparknlp.annotator import * +from pyspark.ml import Pipeline + +documentAssembler = DocumentAssembler() + .setInputCol("text") + .setOutputCol("documents") +CoHere = CoHereTransformer.pretrained("c4ai_command_r_v01_int4","en") + .setInputCols(["documents"]) + .setMaxOutputLength(60) + .setOutputCol("generation") +pipeline = Pipeline().setStages([documentAssembler, CoHere]) +data = spark.createDataFrame([ + ( + 1, + "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hello, how are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>" + ) + ]).toDF("id", "text") +result = pipeline.fit(data).transform(data) +result.select("generation.result").show(truncate=False) +{%- endcapture -%} + +{%- capture scala_example -%} +import spark.implicits._ +import com.johnsnowlabs.nlp.base.DocumentAssembler +import com.johnsnowlabs.nlp.annotators.seq2seq.CoHereTransformer +import org.apache.spark.ml.Pipeline + +val documentAssembler = new DocumentAssembler() + .setInputCol("text") + .setOutputCol("documents") + +val CoHere = CoHereTransformer.pretrained("c4ai_command_r_v01_int4") + .setInputCols(Array("documents")) + .setMinOutputLength(15) + .setMaxOutputLength(60) + .setDoSample(false) + .setTopK(40) + .setNoRepeatNgramSize(3) + .setOutputCol("generation") + +val pipeline = new Pipeline().setStages(Array(documentAssembler, CoHere)) + +val data = Seq( + ( + 1, + """ + <|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hello, how are you?<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|> + """.stripMargin) +).toDF("id", "text") + +val result = pipeline.fit(data).transform(data) + +result.select("generation.result").show(truncate = false) +{%- endcapture -%} + +{%- capture api_link -%} +[CoHereTransformer](https://www.google.com/url?sa=E&source=gmail&q=https://www.google.com/url?sa=E%26source=gmail%26q=/api/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer) +{%- endcapture -%} + +{%- capture python_api_link -%} +[CoHereTransformer](https://www.google.com/url?sa=E&source=gmail&q=https://www.google.com/url?sa=E%26source=gmail%26q=/api/python/reference/autosummary/sparknlp/annotator/seq2seq/cohere/index.html#sparknlp.annotator.seq2seq.cohere.CoHereTransformer) +{%- endcapture -%} + +{%- capture source_link -%} +[CoHereTransformer](https://www.google.com/url?sa=E&source=gmail&q=https://www.google.com/url?sa=E%26source=gmail%26q=https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala) +{%- endcapture -%} + +{% include templates/anno_template.md +title=title +description=description +input_anno=input_anno +output_anno=output_anno +python_example=python_example +scala_example=scala_example +api_link=api_link +python_api_link=python_api_link +source_link=source_link +%} \ No newline at end of file diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb index 7d13d50b8b40d9..2d4f3efba97653 100644 --- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_CoHere.ipynb @@ -25,8 +25,8 @@ "\n", "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance CPU inference for models. So please make sure you have upgraded to the latest Spark NLP release.\n", "- Model quantization is a computationally expensive process, so it is recommended to use a runtime with more than 32GB memory for exporting the quantized model from HuggingFace.\n", - "- You can import LLama models via `LlamaModel`. These models are usually under `Text Generation` category and have `CoHere` in their labels.\n", - "- Reference: [LlamaModel](https://huggingface.co/docs/transformers/model_doc/llama#transformers.LlamaModel)\n", + "- You can import CoHere models via `CoHereModel`. These models are usually under `Text Generation` category and have `CoHere` in their labels.\n", + "- Reference: [CoHereModel](https://huggingface.co/docs/transformers/model_doc/CoHereTransformer#transformers.CoHereModel)\n", "- Some [example models](https://huggingface.co/models?search=CoHere)" ] }, @@ -57,59 +57,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m43.8/43.8 kB\u001b[0m \u001b[31m722.3 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m9.1/9.1 MB\u001b[0m \u001b[31m21.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m38.7/38.7 MB\u001b[0m \u001b[31m12.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m223.4/223.4 kB\u001b[0m \u001b[31m12.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m527.3/527.3 kB\u001b[0m \u001b[31m26.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m421.5/421.5 kB\u001b[0m \u001b[31m23.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m15.9/15.9 MB\u001b[0m \u001b[31m14.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m116.3/116.3 kB\u001b[0m \u001b[31m8.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m39.9/39.9 MB\u001b[0m \u001b[31m28.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m1.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m134.8/134.8 kB\u001b[0m \u001b[31m10.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m194.1/194.1 kB\u001b[0m \u001b[31m15.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m7.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "cudf-cu12 24.4.1 requires pyarrow<15.0.0a0,>=14.0.1, but you have pyarrow 17.0.0 which is incompatible.\n", - "ibis-framework 8.0.0 requires pyarrow<16,>=2, but you have pyarrow 17.0.0 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0m Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m68.4/68.4 kB\u001b[0m \u001b[31m3.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m207.3/207.3 kB\u001b[0m \u001b[31m15.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m1.2/1.2 MB\u001b[0m \u001b[31m32.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m307.2/307.2 kB\u001b[0m \u001b[31m12.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m4.2/4.2 MB\u001b[0m \u001b[31m34.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m249.1/249.1 kB\u001b[0m \u001b[31m8.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m76.0/76.0 kB\u001b[0m \u001b[31m4.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h Building wheel for jstyleson (setup.py) ... \u001b[?25l\u001b[?25hdone\n", - " Building wheel for grapheme (setup.py) ... \u001b[?25l\u001b[?25hdone\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m417.5/417.5 kB\u001b[0m \u001b[31m12.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m15.7/15.7 MB\u001b[0m \u001b[31m58.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m755.5/755.5 MB\u001b[0m \u001b[31m2.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m166.0/166.0 MB\u001b[0m \u001b[31m5.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[2K \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[32m167.9/167.9 MB\u001b[0m \u001b[31m6.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n", - "torchaudio 2.3.1+cu121 requires torch==2.3.1, but you have torch 2.2.1 which is incompatible.\n", - "torchtext 0.18.0 requires torch>=2.3.0, but you have torch 2.2.1 which is incompatible.\n", - "torchvision 0.18.1+cu121 requires torch==2.3.1, but you have torch 2.2.1 which is incompatible.\u001b[0m\u001b[31m\n", - "\u001b[0m" + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ - "!pip install -q --upgrade transformers==4.41.2\n", - "!pip install -q --upgrade openvino==2024.1\n", - "!pip install -q --upgrade optimum-intel\n", - "!pip install -q --upgrade nncf\n", - "!pip install -q --upgrade huggingface_hub\n", - "!pip install -q --upgrade onnx==1.15.0\n", - "!pip install -q --upgrade torch==2.2.1" + "%pip install -q \"nncf>=2.14.0\" \"torch>=2.3\" \"transformers>=4.39.1\" \"accelerate\" \"pillow\" \"gradio>=4.26\" \"datasets>=2.14.6\" \"tqdm\" --extra-index-url https://download.pytorch.org/whl/cpu\n", + "%pip install -q -U \"openvino>=2024.5.0\" \"openvino-tokenizers>=2024.5.0\" \"openvino-genai>=2024.5\"\n", + "%pip install -q \"git+https://github.com/huggingface/optimum-intel.git\" --extra-index-url https://download.pytorch.org/whl/cpu\n", + "%pip install -q ipywidgets" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -156,7 +120,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8420c288f5e44084af6589d767899664", + "model_id": "6237118d2c1e42a687289d6dc49e3389", "version_major": 2, "version_minor": 0 }, @@ -183,7 +147,7 @@ "- To load a HuggingFace model directly for inference/export, just replace the `AutoModelForXxx` class with the corresponding `OVModelForXxx` class. We can use this to import and export OpenVINO models with `from_pretrained` and `save_pretrained`.\n", "- By setting `export=True`, the source model is converted to OpenVINO IR format on the fly.\n", "- We'll use [meta-llama/Meta-Llama-3-8B-Instruct](https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct) model from HuggingFace as an example.\n", - "- In addition to `LlamaModel` we also need to save the tokenizer. This is the same for every model, these are assets needed for tokenization inside Spark NLP." + "- In addition to `CoHereModel` we also need to save the tokenizer. This is the same for every model, these are assets needed for tokenization inside Spark NLP." ] }, { @@ -197,7 +161,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -239,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -257,7 +221,7 @@ { "data": { "text/markdown": [ - "`optimum-cli export openvino --model CohereForAI/c4ai-command-r-v01 c4ai-command-r-v01/INT4 --weight-format int4 --task text-generation-with-past --group-size 128 --ratio 1 --all-layers`" + "`optimum-cli export openvino --model CohereForAI/c4ai-command-r-v01 /mnt/research/c4ai-command-r-v01/INT4 --weight-format int4 --task text-generation-with-past --group-size 128 --ratio 1 --all-layers`" ], "text/plain": [ "" @@ -270,16 +234,16 @@ "name": "stderr", "output_type": "stream", "text": [ - "Loading checkpoint shards: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 15/15 [00:03<00:00, 4.41it/s]\n", - "We detected that you are passing `past_key_values` as a tuple of tuples. This is deprecated and will be removed in v4.47. Please convert your cache or use an appropriate `Cache` class (https://huggingface.co/docs/transformers/kv_cache#legacy-cache-format)\n", - "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/cache_utils.py:447: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n", + "/home/prabod/anaconda3/envs/cohere/lib/python3.9/importlib/util.py:245: DeprecationWarning: The `openvino.runtime` module is deprecated and will be removed in the 2026.0 release. Please replace `openvino.runtime` with `openvino`.\n", + " self.__spec__.loader.exec_module(self)\n", + "Loading checkpoint shards: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 15/15 [00:03<00:00, 4.13it/s]\n", + "`loss_type=None` was set in the config but it is unrecognised.Using the default loss: `ForCausalLMLoss`.\n", + "/home/prabod/anaconda3/envs/cohere/lib/python3.9/site-packages/transformers/cache_utils.py:460: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n", " or len(self.key_cache[layer_idx]) == 0 # the layer has no cache\n", - "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/optimum/exporters/openvino/model_patcher.py:496: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", + "/home/prabod/anaconda3/envs/cohere/lib/python3.9/site-packages/optimum/exporters/openvino/model_patcher.py:515: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", " if sequence_length != 1:\n", - "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/cache_utils.py:432: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n", - " elif len(self.key_cache[layer_idx]) == 0: # fills previously skipped layers; checking for tensor causes errors\n", - "Starting from v4.46, the `logits` model output will have the same type as the model (except at train time, where it will always be FP32)\n", - "Exporting tokenizers to OpenVINO is not supported for tokenizers version > 0.19. Please downgrade to tokenizers version <= 0.19 to export tokenizers to OpenVINO.\n" + "/home/prabod/anaconda3/envs/cohere/lib/python3.9/site-packages/transformers/cache_utils.py:444: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n", + " len(self.key_cache[layer_idx]) == 0\n" ] }, { @@ -287,12 +251,12 @@ "output_type": "stream", "text": [ "INFO:nncf:Statistics of the bitwidth distribution:\n", - "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n", - "โ”‚ Num bits (N) โ”‚ % all parameters (layers) โ”‚ % ratio-defining parameters (layers) โ”‚\n", - "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n", - "โ”‚ 4 โ”‚ 100% (281 / 281) โ”‚ 100% (281 / 281) โ”‚\n", - "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”๏ฟฝ๏ฟฝ๏ฟฝโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n", - "\u001b[2KApplying Weight Compression \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[35m100%\u001b[0m โ€ข \u001b[36m0:28:04\u001b[0m โ€ข \u001b[36m0:00:00\u001b[0m00:04\u001b[0m01:04\u001b[0m\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n", + "โ”‚ Weight compression mode โ”‚ % all parameters (layers) โ”‚ % ratio-defining parameters (layers) โ”‚\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n", + "โ”‚ int4_asym โ”‚ 100% (281 / 281) โ”‚ 100% (281 / 281) โ”‚\n", + "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n", + "\u001b[2KApplying Weight Compression \u001b[90mโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\u001b[0m \u001b[35m100%\u001b[0m โ€ข \u001b[36m0:04:08\u001b[0m โ€ข \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:08\u001b[0m\n", "\u001b[?25h" ] } @@ -303,7 +267,7 @@ "model_id = \"CohereForAI/c4ai-command-r-v01\"\n", "model_path = Path(model_id.split(\"/\")[-1]) / \"INT4\"\n", "\n", - "\n", + "model_path = \"/mnt/research\" / model_path\n", "if not model_path.exists():\n", " optimum_cli(\n", " model_id,\n", @@ -332,7 +296,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -341,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -354,24 +318,31 @@ "name": "stdout", "output_type": "stream", "text": [ - "total 17771924\n", - "-rw-rw-r-- 1 prabod prabod 770 Nov 14 02:21 config.json\n", - "-rw-rw-r-- 1 prabod prabod 137 Nov 14 02:21 generation_config.json\n", - "-rw-rw-r-- 1 prabod prabod 18174804540 Nov 14 02:53 openvino_model.bin\n", - "-rw-rw-r-- 1 prabod prabod 3473013 Nov 14 02:53 openvino_model.xml\n", - "-rw-rw-r-- 1 prabod prabod 439 Nov 14 02:22 special_tokens_map.json\n", - "-rw-rw-r-- 1 prabod prabod 20719 Nov 14 02:22 tokenizer_config.json\n", - "-rw-rw-r-- 1 prabod prabod 20124090 Nov 14 02:22 tokenizer.json\n" + "total 17G\n", + "drwxrwxr-x 3 prabod prabod 4.0K Feb 13 09:13 .\n", + "drwxrwxr-x 3 prabod prabod 4.0K Feb 13 09:02 ..\n", + "drwxrwxr-x 2 prabod prabod 4.0K Feb 13 09:13 assets\n", + "-rw-rw-r-- 1 prabod prabod 810 Feb 13 09:02 config.json\n", + "-rw-rw-r-- 1 prabod prabod 137 Feb 13 09:02 generation_config.json\n", + "-rw-rw-r-- 1 prabod prabod 2.8M Feb 13 09:06 openvino_detokenizer.bin\n", + "-rw-rw-r-- 1 prabod prabod 23K Feb 13 09:06 openvino_detokenizer.xml\n", + "-rw-rw-r-- 1 prabod prabod 17G Feb 13 09:11 openvino_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 3.4M Feb 13 09:11 openvino_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 6.6M Feb 13 09:06 openvino_tokenizer.bin\n", + "-rw-rw-r-- 1 prabod prabod 40K Feb 13 09:06 openvino_tokenizer.xml\n", + "-rw-rw-r-- 1 prabod prabod 439 Feb 13 09:02 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 21K Feb 13 09:02 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 20M Feb 13 09:02 tokenizer.json\n" ] } ], "source": [ - "!ls -l {EXPORT_PATH}" + "!ls -lah {EXPORT_PATH}" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -390,7 +361,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -404,11 +375,11 @@ "output_type": "stream", "text": [ "total 19692\n", - "-rw-rw-r-- 1 prabod prabod 770 Nov 14 03:45 config.json\n", - "-rw-rw-r-- 1 prabod prabod 137 Nov 14 03:45 generation_config.json\n", - "-rw-rw-r-- 1 prabod prabod 439 Nov 14 03:45 special_tokens_map.json\n", - "-rw-rw-r-- 1 prabod prabod 20719 Nov 14 03:45 tokenizer_config.json\n", - "-rw-rw-r-- 1 prabod prabod 20124090 Nov 14 03:45 tokenizer.json\n" + "-rw-rw-r-- 1 prabod prabod 810 Feb 13 09:13 config.json\n", + "-rw-rw-r-- 1 prabod prabod 137 Feb 13 09:13 generation_config.json\n", + "-rw-rw-r-- 1 prabod prabod 439 Feb 13 09:13 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 20749 Feb 13 09:13 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 20124090 Feb 13 09:13 tokenizer.json\n" ] } ], @@ -476,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 14, "metadata": { "id": "T3591W9R4W7M" }, @@ -485,7 +456,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "24/11/14 04:03:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native17996866745707494714/libtbb.so.2\n" + "25/02/13 09:19:52 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native14220754060683836653/libtbb.so.2\n" ] }, { @@ -504,10 +475,9 @@ "from sparknlp.annotator import *\n", "\n", "CoHere = CoHereTransformer \\\n", - " .loadSavedModel(EXPORT_PATH, spark) \\\n", - " .setMaxOutputLength(500) \\\n", + " .loadSavedModel(str(EXPORT_PATH), spark) \\\n", + " .setMaxOutputLength(50) \\\n", " .setDoSample(False) \\\n", - " .setBeamSize(1) \\\n", " .setInputCols([\"documents\"]) \\\n", " .setOutputCol(\"generation\")" ] @@ -523,7 +493,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -536,7 +506,15 @@ "metadata": { "id": "T6GaugQa4W7M" }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], "source": [ "CoHere.write().overwrite().save(f\"{MODEL_NAME}_spark_nlp\")" ] @@ -587,11 +565,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "total 4141828\n", - "drwxr-xr-x 3 root root 4096 Jun 6 16:35 fields\n", - "-rw-r--r-- 1 root root 4240712291 Jun 6 16:36 llama2_openvino\n", - "-rw-r--r-- 1 root root 499723 Jun 6 16:36 llama2_spp\n", - "drwxr-xr-x 2 root root 4096 Jun 6 16:35 metadata\n" + "total 17754944\n", + "-rw-r--r-- 1 prabod prabod 18181049933 Feb 13 09:34 CoHere_openvino\n", + "drwxr-xr-x 6 prabod prabod 4096 Feb 13 09:32 fields\n", + "drwxr-xr-x 2 prabod prabod 4096 Feb 13 09:32 metadata\n" ] } ], @@ -623,7 +600,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "[Stage 3:=======================================================> (30 + 1) / 31]\r" + "[Stage 21:======================================================> (30 + 1) / 31]\r" ] }, { @@ -693,7 +670,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "tempspark", + "display_name": "cohere", "language": "python", "name": "python3" }, @@ -707,7 +684,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.16" + "version": "3.9.21" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/python/sparknlp/annotator/seq2seq/cohere_transformer.py b/python/sparknlp/annotator/seq2seq/cohere_transformer.py index 87419d8edc0a27..f72994860171a4 100644 --- a/python/sparknlp/annotator/seq2seq/cohere_transformer.py +++ b/python/sparknlp/annotator/seq2seq/cohere_transformer.py @@ -32,7 +32,7 @@ class CoHereTransformer(AnnotatorModel, HasBatchedAnnotate, HasEngine): ... .setOutputCol("generation") - The default model is ``"CoHere-7b"``, if no name is provided. For available + The default model is ``"c4ai_command_r_v01_int4"``, if no name is provided. For available pretrained models please see the `Models Hub `__. @@ -91,7 +91,7 @@ class CoHereTransformer(AnnotatorModel, HasBatchedAnnotate, HasEngine): >>> documentAssembler = DocumentAssembler() \\ ... .setInputCol("text") \\ ... .setOutputCol("documents") - >>> CoHere = CoHereTransformer.pretrained() \\ + >>> CoHere = CoHereTransformer.pretrained("c4ai_command_r_v01_int4","en") \\ ... .setInputCols(["documents"]) \\ ... .setMaxOutputLength(60) \\ ... .setOutputCol("generation") @@ -335,13 +335,13 @@ def loadSavedModel(folder, spark_session, use_openvino=False): return CoHereTransformer(java_model=jModel) @staticmethod - def pretrained(name="cohere_35b_int4", lang="en", remote_loc=None): + def pretrained(name="c4ai_command_r_v01_int4", lang="en", remote_loc=None): """Downloads and loads a pretrained model. Parameters ---------- name : str, optional - Name of the pretrained model, by default "llama_2_7b_chat_hf_int4" + Name of the pretrained model, by default "c4ai_command_r_v01_int4" lang : str, optional Language of the pretrained model, by default "en" remote_loc : str, optional diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala index 486c22ff5ec9d2..4e755139c05379 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala @@ -58,8 +58,8 @@ import org.json4s.jackson.JsonMethods._ * .setInputCols("document") * .setOutputCol("generation") * }}} - * The default model is `"cohere_35b_int4"`, if no name is provided. For available pretrained - * models please see the [[https://sparknlp.org/models?q=CoHere Models Hub]]. + * The default model is `"c4ai_command_r_v01_int4"`, if no name is provided. For available + * pretrained models please see the [[https://sparknlp.org/models?q=CoHere Models Hub]]. * * For extended examples of usage, see * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTestSpec.scala CoHereTestSpec]]. @@ -83,7 +83,7 @@ import org.json4s.jackson.JsonMethods._ * .setInputCol("text") * .setOutputCol("documents") * - * val CoHere = CoHereTransformer.pretrained("CoHere_3_7b_chat_hf_int8") + * val CoHere = CoHereTransformer.pretrained("c4ai_command_r_v01_int4","en") * .setInputCols(Array("documents")) * .setMinOutputLength(15) * .setMaxOutputLength(60) @@ -334,7 +334,7 @@ class CoHereTransformer(override val uid: String) trait ReadablePretrainedCoHereTransformerModel extends ParamsAndFeaturesReadable[CoHereTransformer] with HasPretrained[CoHereTransformer] { - override val defaultModelName: Some[String] = Some("cohere_35b_int4") + override val defaultModelName: Some[String] = Some("c4ai_command_r_v01_int4") /** Java compliant-overrides */ override def pretrained(): CoHereTransformer = super.pretrained() diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala index 0e457d4d6e20df..73162fda54c5df 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala @@ -697,7 +697,8 @@ object PythonResourceDownloader { "NLLBTransformer" -> NLLBTransformer, "Phi3Transformer" -> Phi3Transformer, "QwenTransformer" -> QwenTransformer, - "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings) + "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings, + "CoHereTransformer" -> CoHereTransformer) // List pairs of types such as the one with key type can load a pretrained model from the value type val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering") From db48372891ff6259a73e6f0cb78e558e55bc80f5 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 13 Feb 2025 09:55:02 +0000 Subject: [PATCH 053/108] update documentation and resource downloader entry Signed-off-by: Prabod Rathnayaka --- docs/en/transformer_entries/CoHereTransformer.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/transformer_entries/CoHereTransformer.md b/docs/en/transformer_entries/CoHereTransformer.md index 3fe8f81ff62c01..23ad849e7c829a 100644 --- a/docs/en/transformer_entries/CoHereTransformer.md +++ b/docs/en/transformer_entries/CoHereTransformer.md @@ -86,15 +86,15 @@ result.select("generation.result").show(truncate = false) {%- endcapture -%} {%- capture api_link -%} -[CoHereTransformer](https://www.google.com/url?sa=E&source=gmail&q=https://www.google.com/url?sa=E%26source=gmail%26q=/api/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer) +[CoHereTransformer](/api/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer) {%- endcapture -%} {%- capture python_api_link -%} -[CoHereTransformer](https://www.google.com/url?sa=E&source=gmail&q=https://www.google.com/url?sa=E%26source=gmail%26q=/api/python/reference/autosummary/sparknlp/annotator/seq2seq/cohere/index.html#sparknlp.annotator.seq2seq.cohere.CoHereTransformer) +[CoHereTransformer](/api/python/reference/autosummary/sparknlp/annotator/seq2seq/cohere/index.html#sparknlp.annotator.seq2seq.cohere.CoHereTransformer) {%- endcapture -%} {%- capture source_link -%} -[CoHereTransformer](https://www.google.com/url?sa=E&source=gmail&q=https://www.google.com/url?sa=E%26source=gmail%26q=https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala) +[CoHereTransformer](https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/CoHereTransformer.scala) {%- endcapture -%} {% include templates/anno_template.md From b9676827043112155568035c9d8b8410437d6d74 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Mon, 9 Dec 2024 07:22:06 +0000 Subject: [PATCH 054/108] Qwen2VL scala API --- .../com/johnsnowlabs/ml/ai/Qwen2VL.scala | 644 +++++++++++++++++ .../ml/openvino/OpenvinoWrapper.scala | 15 + .../ml/util/LoadExternalModel.scala | 59 +- .../annotators/cv/Qwen2VLTransformer.scala | 675 ++++++++++++++++++ .../cv/feature_extractor/Preprocessor.scala | 3 + .../cv/util/transform/Qwen2VLUtils.scala | 63 ++ .../tokenizer/bpe/BpeTokenizer.scala | 16 +- .../tokenizer/bpe/Qwen2VLTokenizer.scala | 102 +++ .../cv/Qwen2VLTransformerTestSpec.scala | 189 +++++ 9 files changed, 1744 insertions(+), 22 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/ml/ai/Qwen2VL.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Qwen2VLUtils.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Qwen2VLTokenizer.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformerTestSpec.scala diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/Qwen2VL.scala b/src/main/scala/com/johnsnowlabs/ml/ai/Qwen2VL.scala new file mode 100644 index 00000000000000..91ac3d6c2f858b --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/ml/ai/Qwen2VL.scala @@ -0,0 +1,644 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.ml.ai + +import breeze.optimize.BatchSize +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.Qwen2VLWrappers +import com.johnsnowlabs.nlp.annotators.common.Sentence +import com.johnsnowlabs.ml.util.{ONNX, Openvino} +import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT +import com.johnsnowlabs.nlp._ +import com.johnsnowlabs.nlp.annotators.common.SentenceSplit +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils +import com.johnsnowlabs.nlp.annotators.cv.util.transform.ImageResizeUtils +import com.johnsnowlabs.nlp.annotators.cv.util.transform.Qwen2VLUtils.{ + IMAGE_FACTOR, + MAX_PIXELS, + MAX_RATIO, + MIN_PIXELS, + imageBufferToArray, + smartResize +} +import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{ + BpeTokenizer, + LLAMA3Tokenizer, + Qwen2VLTokenizer, + SpecialTokens +} +import org.intel.openvino.InferRequest + +import scala.collection.JavaConverters._ + +private[johnsnowlabs] class Qwen2VL( + val onnxWrappers: Option[DecoderWrappers], + val openvinoWrapper: Option[Qwen2VLWrappers], + merges: Map[(String, String), Int], + vocabulary: Map[String, Int], + addedTokens: Map[String, Int], + preprocessor: Preprocessor, + generationConfig: GenerationConfig, + minPixels: Int = MIN_PIXELS, + maxPixels: Int = MAX_PIXELS, + imageToken: Int = 151655) + extends Serializable { + + val detectedEngine: String = + if (onnxWrappers.isDefined) ONNX.name + else if (openvinoWrapper.isDefined) Openvino.name + else Openvino.name + + private val GenerationConfig( + bosTokenId: Int, + paddingTokenId: Int, + eosTokenId: Int, + vocabSize: Int, + beginSuppressTokens, + suppressTokenIds, + forcedDecoderIds) = + generationConfig + val reversedVocabulary: Map[Int, String] = vocabulary.map(_.swap) + + val specialTokens: SpecialTokens = SpecialTokens( + vocabulary, + startTokenString = reversedVocabulary(bosTokenId), + endTokenString = reversedVocabulary(eosTokenId), + unkTokenString = reversedVocabulary(eosTokenId), + maskTokenString = reversedVocabulary(eosTokenId), + padTokenString = reversedVocabulary(paddingTokenId), + additionalStrings = addedTokens.keys.toArray) + + val bpeTokenizer: Qwen2VLTokenizer = BpeTokenizer + .forModel( + "qwen2vl", + merges = merges, + vocab = vocabulary, + specialTokens = Some(specialTokens), + addPrefixSpaceToSentence = false, + alwaysAddPrefix = false, + prependString = "") + .asInstanceOf[Qwen2VLTokenizer] + + /** Decode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of decoded sentences + */ + def decode(sentences: Array[Array[Int]]): Seq[String] = { + sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt))) + } + + /** Encode a sequence of sentences + * @param sentences + * Sequence of sentences + * @return + * Sequence of encoded sentences + */ + def encodeText(sentences: Seq[Annotation], imgTokenLen: List[Int]): Seq[Array[Int]] = { + +// val pattern = raw"<\|image_\d+\|>".r +// <|vision_start|><|image_pad|><|vision_end|> + + val pattern = raw"<\|image_pad\|>".r + // raise an error if the pattern is not found in the text + if (pattern.findFirstIn(sentences.head.result).isEmpty) { + throw new IllegalArgumentException("The pattern <\\|image_pad\\|> is not found in the text") + } + + // split the sentences into chunks based on the pattern and tokenize them + // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)] + val promptChunks = sentences + .map(s => { + val sentWithTask = s.result + var offsetLength = 0 + pattern + .split(sentWithTask) + .zipWithIndex + .map(s => { + val sentenceWithTask = Sentence( + content = s._1, + start = offsetLength, + end = offsetLength + s._1.length, + index = s._2) + offsetLength += s._1.length + bpeTokenizer + .tokenize(sentenceWithTask) + .map(bpeTokenizer.encode) + .flatMap(_.map(_.pieceId)) + }) + }) + + // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]] + val tokens = promptChunks + .zip(imgTokenLen) + .map(s => { + val (promptChunk, imgTokenLen) = s + val imgPaddingTokens = Array.fill(imgTokenLen)(imageToken) + val combinedChunks = promptChunk + .map(_.toArray) + .reduce(_ ++ imgPaddingTokens ++ _) + Array(bosTokenId) ++ combinedChunks + }) + + // val tokens = SentenceSplit + // .unpack(sentences) + // .map(s => { + // val sentWithTask = s + // bpeTokenizer + // .tokenize(sentWithTask) + // .map(bpeTokenizer.encode) + // .flatMap(_.map(_.pieceId)) + // }) + tokens + } + def encode( + imageAnnotations: Seq[AnnotationImage], + sentences: Seq[Annotation], + preprocessor: Preprocessor) + : (Seq[Array[Int]], (org.intel.openvino.Tensor, (Int, Int, Int))) = { + val preprocessedImages = preprocessImage( + imageAnnotations, + preprocessor, + minPixels = minPixels, + maxPixels = maxPixels) + val imageTokenLength = preprocessedImages._2._2 * preprocessedImages._2._3 / 4 + val encodedText = encodeText(sentences, List(imageTokenLength)).toArray + + (encodedText, preprocessedImages) + } + + def tag( + batch: Seq[Array[Int]], + images: (org.intel.openvino.Tensor, (Int, Int, Int)), + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long], + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int, + stopTokenIds: Array[Int], + numOfCrops: Int = 16): Array[Array[Int]] = { + + val (pixelValues, (grid_t, grid_h, grid_w)) = images + val imageGridTHW: Array[Array[Int]] = Array(Array(grid_t, grid_h, grid_w)) + val ignoreTokenIdsInt = ignoreTokenIds + val expandedDecoderInputsVals = batch + val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray + val maxSentenceLength = sequencesLength.max // - curLen + // val pixelValues = images._1 + // val imageSizes = images._2 + val numReturn_sequences = 1 + // from config + + var effectiveBatch_size = 1 + var effectiveBatch_mult = 1 + + if (doSample) { + effectiveBatch_size = expandedDecoderInputsVals.length * numReturn_sequences + effectiveBatch_mult = numReturn_sequences + } else { + effectiveBatch_size = expandedDecoderInputsVals.length + effectiveBatch_mult = 1 + } + + val inferRequestImageEmbed = + openvinoWrapper.get.imageEmbedding.getCompiledModel().create_infer_request() + val inferRequestImageEmbedMerger = + openvinoWrapper.get.imageEmbeddingMerger.getCompiledModel().create_infer_request() + val inferRequestRotaryEmbedding = + openvinoWrapper.get.rotaryEmbedding.getCompiledModel().create_infer_request() + val inferRequestTextEmbedding = + openvinoWrapper.get.textEmbedding.getCompiledModel().create_infer_request() + val inferRequestMultimodalModelMerge = + openvinoWrapper.get.multimodalMergeModel.getCompiledModel().create_infer_request() + val inferRequestLanguageModel = + openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request() + + val generatedIds = generateGreedy( + batch.toArray, + batch.toArray, + pixelValues, + imageGridTHW, + maxOutputLength, + inferRequestImageEmbed, + inferRequestImageEmbedMerger, + inferRequestRotaryEmbedding, + inferRequestTextEmbedding, + inferRequestMultimodalModelMerge, + inferRequestLanguageModel) + generatedIds + } + + def generateGreedy( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: org.intel.openvino.Tensor, + imageGridTHW: Array[Array[Int]], + maxOutputLength: Int, + inferRequestImageEmbed: InferRequest, + inferRequestImageEmbedMerger: InferRequest, + inferRequestRotaryEmbedding: InferRequest, + inferRequestTextEmbedding: InferRequest, + inferRequestMultimodalModelMerge: InferRequest, + inferRequestLanguageModel: InferRequest): Array[Array[Int]] = { + + var generatedIds: Array[Array[Int]] = Array() + var decoderInputIdsCopied = decoderInputIds + while (!greedyGenerationFinished(generatedIds, eosTokenId, maxOutputLength)) { + val decoderOutputs = getModelOutputs( + encoderInputIds, + decoderInputIdsCopied, + pixelValues, + imageGridTHW, + inferRequestImageEmbed, + inferRequestImageEmbedMerger, + inferRequestRotaryEmbedding, + inferRequestTextEmbedding, + inferRequestMultimodalModelMerge, + inferRequestLanguageModel) + + val nextTokenIds = decoderOutputs.map { scores => + argmax(scores) + } + + if (generatedIds.isEmpty) { + generatedIds = nextTokenIds.map(Array(_)) + } else { + generatedIds = + generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => + currentIds ++ Array(nextId) + } + } + + // extend decoder input ids + decoderInputIdsCopied = + decoderInputIdsCopied.zip(nextTokenIds).map { case (currentIds, nextId) => + currentIds ++ Array(nextId) + } + } + generatedIds + } + + def predict( + sentences: Seq[Annotation], + imageAnnotations: Seq[AnnotationImage], + batchSize: Int, + minOutputLength: Int, + maxOutputLength: Int, + doSample: Boolean, + temperature: Double, + topK: Int, + topP: Double, + repetitionPenalty: Double, + noRepeatNgramSize: Int, + randomSeed: Option[Long] = None, + ignoreTokenIds: Array[Int] = Array(), + beamSize: Int, + maxInputLength: Int): Seq[Annotation] = { + + val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences, preprocessor) +// val (pixelValues, imageSizes, imgTokens) = preprocessedImages + val tagged = tag( + encodedText, + preprocessedImages, + minOutputLength, + maxOutputLength, + doSample, + temperature, + topK, + topP, + repetitionPenalty, + noRepeatNgramSize, + randomSeed, + ignoreTokenIds, + beamSize, + maxInputLength, + Array(eosTokenId)) + val decoded = decode(tagged) + + var sentBegin, nextSentEnd = 0 + val annotations = decoded.map { content => + nextSentEnd += content.length - 1 + val annots = new Annotation( + annotatorType = DOCUMENT, + begin = sentBegin, + end = nextSentEnd, + result = content, + metadata = Map()) + sentBegin += nextSentEnd + 1 + annots + } + annotations + } + + def getModelOutputs( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: org.intel.openvino.Tensor, + imageGridTHW: Array[Array[Int]], + inferRequestImageEmbed: InferRequest, + inferRequestImageEmbedMerger: InferRequest, + inferRequestRotaryEmbedding: InferRequest, + inferRequestTextEmbedding: InferRequest, + inferRequestMultimodalModelMerge: InferRequest, + inferRequestLanguageModel: InferRequest): Array[Array[Float]] = { + + val imageEmbeddings = getImageEmbeddings( + encoderInputIds, + decoderInputIds, + pixelValues, + imageGridTHW, + inferRequestImageEmbed, + inferRequestImageEmbedMerger, + inferRequestRotaryEmbedding, + inferRequestTextEmbedding, + inferRequestMultimodalModelMerge) + + val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + val posIdsLong = decoderInputIds.flatMap { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + } + } + (inpIdsLong, posIdsLong) + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + val posIdsLong = decoderInputIds.map { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + }.last + } + (inpIdsLong, posIdsLong) + } + val attentionMask: Array[Long] = + decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) } + + val batchSize: Int = decoderInputIds.length + val beamIdx: Array[Int] = new Array[Int](batchSize) + val shape: Array[Int] = Array(3, 1, inputIdsLong.length / batchSize) + + val reshapedArray = Array(Array(inputPositionIDsLong)) + + // Expand the array by replicating the first dimension + val inputPositionIDsLongX3 = + reshapedArray.map(x => Array(x, x, x)).flatten.flatten.flatten + + val decoderAttentionMask: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask) + val decoderPositionIDs: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputPositionIDsLongX3) + val beamIdxTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize), beamIdx) + + val imgEmbeddingTensor = + new org.intel.openvino.Tensor(imageEmbeddings.get_shape(), imageEmbeddings.data()) + + inferRequestLanguageModel.set_tensor("inputs_embeds", imgEmbeddingTensor) + inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask) + inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs) + inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor) + + inferRequestLanguageModel.infer() + + val result = inferRequestLanguageModel.get_tensor("logits") + val logitsRaw = result.data() + + val sequenceLength = inputIdsLong.length / batchSize + val decoderOutputs = (0 until batchSize).map(i => { + logitsRaw + .slice( + i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize, + i * sequenceLength * vocabSize + sequenceLength * vocabSize) + }) + decoderOutputs.toArray + } + + private def argmax(scores: Array[Float]): Int = + scores.zipWithIndex.maxBy { case (score, _) => + score + }._2 + + private def greedyGenerationFinished( + decoderIds: Seq[Array[Int]], + eosTokenId: Int, + maxOutputLength: Int): Boolean = { + if (decoderIds.isEmpty) { + false + } else { + decoderIds.forall { ids => + ids.length >= maxOutputLength || ids.last == eosTokenId + } + } + } + + def preprocessImage( + imageAnnotations: Seq[AnnotationImage], + preprocessor: Preprocessor, + sizeFactor: Int = IMAGE_FACTOR, + minPixels: Int = MIN_PIXELS, + maxPixels: Int = MAX_PIXELS): (org.intel.openvino.Tensor, (Int, Int, Int)) = { + + val rescaledImage = imageAnnotations + .map(annotations => { + + val (width, height) = smartResize( + annotations.height, + annotations.width, + factor = sizeFactor, + minPixels = MIN_PIXELS, + maxPixels = MAX_PIXELS) + + val bufferedImage = ImageIOUtils.byteToBufferedImage( + bytes = annotations.result, + w = annotations.width, + h = annotations.height, + nChannels = annotations.nChannels) + + val resizedImage = + ImageResizeUtils.resizeBufferedImage(height = height, width = width, resample = 3)( + bufferedImage) + + val resizedDimensions = smartResize( + resizedImage.getHeight, + resizedImage.getWidth, + factor = sizeFactor, + minPixels = minPixels, + maxPixels = maxPixels) + + val (resizedWidth, resizedHeight) = resizedDimensions + + val resizedImageArray = ImageResizeUtils.resizeBufferedImage( + width = resizedWidth, + height = resizedHeight, + resample = 3)(resizedImage) + + val normalizedImage = + ImageResizeUtils.normalizeAndConvertBufferedImage( + img = resizedImageArray, + mean = preprocessor.image_mean, + std = preprocessor.image_std, + doNormalize = preprocessor.do_normalize, + doRescale = preprocessor.do_rescale, + rescaleFactor = preprocessor.rescale_factor) + + normalizedImage + }) + .toArray + + val inferRequestPatchReshape = + openvinoWrapper.get.patchReshapeModel.getCompiledModel().create_infer_request() + + val patchTensor = new org.intel.openvino.Tensor( + Array( + rescaledImage.length, + rescaledImage.head.length, + rescaledImage.head.head.length, + rescaledImage.head.head.head.length), + rescaledImage.flatten.flatten.flatten.map(_.toFloat)) + + // 2.0f if rescaledImage.length == 1 else 1.0f + val factor: Long = if (rescaledImage.length == 1) 2L else 1L + val repetitionFactorTensor = new org.intel.openvino.Tensor(Array[Int](), Array(factor)) + inferRequestPatchReshape.set_tensor("patches", patchTensor) + inferRequestPatchReshape.set_tensor("repetition_factor", repetitionFactorTensor) + + inferRequestPatchReshape.infer() + + val pixel_values = inferRequestPatchReshape.get_output_tensor() + val grid_t = if (rescaledImage.length == 1) 1 else Math.ceil(rescaledImage.length / 2).toInt + val grid_h = (rescaledImage.head.head.length / 14).toInt + val grid_w = (rescaledImage.head.head.head.length / 14).toInt + (pixel_values, (grid_t, grid_h, grid_w)) + } + + def getImageEmbeddings( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + pixelValues: org.intel.openvino.Tensor, + imageGridTHW: Array[Array[Int]], + inferRequestImageEmbed: InferRequest, + inferRequestImageEmbedMerger: InferRequest, + inferRequestRotaryEmbedding: InferRequest, + inferRequestTextEmbedding: InferRequest, + inferRequestMultimodalModelMerge: InferRequest): org.intel.openvino.Tensor = { + val inputIdsLong: Array[Long] = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + + inpIdsLong + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + inpIdsLong + } + val batchSize: Int = decoderInputIds.length + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + val inputIdsLongTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputIdsLong) + + val imageEmbeddings: org.intel.openvino.Tensor = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + val pixelValuesTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(pixelValues.get_shape(), pixelValues.data()) +// +// val pixelValuesTensor = pixelValues + inferRequestImageEmbed.set_input_tensor(pixelValuesTensor) + + inferRequestImageEmbed.infer() + + val hiddenStates = inferRequestImageEmbed.get_output_tensor() + + val rotaryEmbeds = imageGridTHW.map(imageTHW => { + val imageTHWTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array[Int](3), imageTHW.map(_.toLong)) + inferRequestRotaryEmbedding.set_input_tensor(imageTHWTensor) + inferRequestRotaryEmbedding.infer() + + val rotary = inferRequestRotaryEmbedding.get_output_tensor() + val rotaryData = rotary.data() + (rotaryData, rotary.get_shape()) + }) + + // rotary_pos_emb = torch.cat([torch.from_numpy(rotary_embedding(x)[0]) for x in image_grid_thw], dim=0) + + val rotaryPosEmb = rotaryEmbeds.flatMap(_._1) + // shape should be batch_size x seq_len, hidden_size + val rotaryShape = + Array(rotaryEmbeds.length * rotaryEmbeds.head._2(0), rotaryEmbeds.head._2(1)) +// println("Rotary Shape: " + rotaryShape.mkString(",")) +// println("Rotary Pos Emb: " + rotaryPosEmb.length) + val rotaryPosEmbTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(rotaryShape, rotaryPosEmb) + + // attention_mask = torch.zeros((1, hidden_states.shape[0], hidden_states.shape[0]), dtype=torch.bool) + + val attentionMask: Array[Float] = + Array.fill(hiddenStates.get_shape()(0) * hiddenStates.get_shape()(0))(1f) + +// println("Hidden States Shape: " + hiddenStates.get_shape().mkString(",")) +// println("attentionMask Shape: " + attentionMask.length) + + val attentionMaskTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor( + Array(1, hiddenStates.get_shape()(0), hiddenStates.get_shape()(0)), + attentionMask) + + inferRequestImageEmbedMerger.set_tensor("hidden_states", hiddenStates) + inferRequestImageEmbedMerger.set_tensor("rotary_pos_emb", rotaryPosEmbTensor) + inferRequestImageEmbedMerger.set_tensor("attention_mask", attentionMaskTensor) + + inferRequestImageEmbedMerger.infer() + + val imageEmbedMerged = inferRequestImageEmbedMerger.get_output_tensor() + + inferRequestTextEmbedding.set_input_tensor(inputIdsLongTensor) + inferRequestTextEmbedding.infer() + + val textEmbeddings = inferRequestTextEmbedding.get_output_tensor() + + inferRequestMultimodalModelMerge.set_tensor("inputs_embeds", textEmbeddings) + inferRequestMultimodalModelMerge.set_tensor("vision_embeds", imageEmbedMerged) + inferRequestMultimodalModelMerge.set_tensor("input_ids", inputIdsLongTensor) + + inferRequestMultimodalModelMerge.infer() + + inferRequestMultimodalModelMerge.get_output_tensor() + + } else { + inferRequestTextEmbedding.set_input_tensor(inputIdsLongTensor) + inferRequestTextEmbedding.infer() + + inferRequestTextEmbedding.get_output_tensor() + } + imageEmbeddings + } + +} diff --git a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala index 0c2f65d4315e4e..7abda7732bd7a6 100644 --- a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala +++ b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala @@ -218,4 +218,19 @@ object OpenvinoWrapper { decoderWithPast: OpenvinoWrapper) case class DecoderWrappers(decoder: OpenvinoWrapper) case class EncoderDecoderWithoutPastWrappers(encoder: OpenvinoWrapper, decoder: OpenvinoWrapper) + +// LANGUAGE_MODEL_NAME = "openvino_language_model.xml" +//IMAGE_EMBEDDING_NAME = "openvino_vision_embeddings_model.xml" +//IMAGE_EMBEDDING_MERGER_NAME = "openvino_vision_embeddings_merger_model.xml" +//TEXT_EMBEDDING_NAME = "openvino_text_embeddings_model.xml" + // ROTARY_EMBEDDING_NAME = "openvino_rotary_embeddings_model.xml" + // PATCH_RESHAPE_NAME = "openvino_patch_reshape_model.xml" + case class Qwen2VLWrappers( + languageModel: OpenvinoWrapper, + imageEmbedding: OpenvinoWrapper, + imageEmbeddingMerger: OpenvinoWrapper, + textEmbedding: OpenvinoWrapper, + rotaryEmbedding: OpenvinoWrapper, + patchReshapeModel: OpenvinoWrapper, + multimodalMergeModel: OpenvinoWrapper) } diff --git a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala index cd0761f0f9daa3..403613a13c0901 100644 --- a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala +++ b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala @@ -18,6 +18,7 @@ package com.johnsnowlabs.ml.util import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper import com.johnsnowlabs.nlp.util.io.{ExternalResource, ReadAs, ResourceHelper} +import org.glassfish.jersey.internal.inject.Custom import java.io.File import java.nio.file.Paths @@ -103,22 +104,40 @@ object LoadExternalModel { } - def isOpenvinoModel(modelPath: String, isEncoderDecoder: Boolean): Boolean = { - if (isEncoderDecoder) { - val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml") - val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin") - val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml") - val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin") - val ovDecoderModelWithPastXml = new File(modelPath, s"${Openvino.decoderModelWithPast}.xml") - val ovDecoderModelWithPastBin = new File(modelPath, s"${Openvino.decoderModelWithPast}.bin") - - ovEncoderModelXml.exists() && ovEncoderModelBin.exists() && - ovDecoderModelXml.exists() && ovDecoderModelBin.exists() && - ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists() + def isOpenvinoModel( + modelPath: String, + isEncoderDecoder: Boolean, + custom: Option[List[String]] = None): Boolean = { + + if (custom.isDefined) { + for (model <- custom.get) { + val ovModelXml = new File(modelPath, s"${model}.xml") + val ovModelBin = new File(modelPath, s"${model}.bin") + if (!ovModelXml.exists() || !ovModelBin.exists()) { + println(s"Model $model not found in $modelPath") + return false + } + } + true } else { - val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml") - val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin") - modelXml.exists() && modelBin.exists() + if (isEncoderDecoder) { + val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml") + val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin") + val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml") + val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin") + val ovDecoderModelWithPastXml = + new File(modelPath, s"${Openvino.decoderModelWithPast}.xml") + val ovDecoderModelWithPastBin = + new File(modelPath, s"${Openvino.decoderModelWithPast}.bin") + + ovEncoderModelXml.exists() && ovEncoderModelBin.exists() && + ovDecoderModelXml.exists() && ovDecoderModelBin.exists() && + ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists() + } else { + val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml") + val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin") + modelXml.exists() && modelBin.exists() + } } } @@ -126,7 +145,8 @@ object LoadExternalModel { modelPath: String, isEncoderDecoder: Boolean = false, withPast: Boolean = false, - isDecoder: Boolean = false): String = { + isDecoder: Boolean = false, + custom: Option[List[String]] = None): String = { /** Check if the path is correct */ val f = new File(modelPath) @@ -146,7 +166,7 @@ object LoadExternalModel { val onnxModelExist = isOnnxModel(modelPath, isEncoderDecoder, withPast, isDecoder) /*Openvino required model files*/ - val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder) + val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder, custom) if (tfSavedModelExist) { TensorFlow.name @@ -176,10 +196,11 @@ object LoadExternalModel { path: String, isEncoderDecoder: Boolean = false, withPast: Boolean = false, - isDecoder: Boolean = false): (String, String) = { + isDecoder: Boolean = false, + custom: Option[List[String]] = None): (String, String) = { val localPath: String = ResourceHelper.copyToLocal(path) - (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder)) + (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder, custom)) } def loadTextAsset(assetPath: String, assetName: String): Array[String] = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala new file mode 100644 index 00000000000000..714615ac7ceadf --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala @@ -0,0 +1,675 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig +import com.johnsnowlabs.ml.ai.Qwen2VL +import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers +import com.johnsnowlabs.ml.util.LoadExternalModel.{ + loadJsonStringAsset, + loadTextAsset, + modelSanityCheck, + notSupportedEngineError +} +import com.johnsnowlabs.ml.util.Openvino +import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor +import com.johnsnowlabs.nlp.AnnotatorType.{DOCUMENT, IMAGE} +import com.johnsnowlabs.nlp._ +import org.json4s.{DefaultFormats, JValue} +import org.json4s.jackson.JsonMethods.parse +import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel} +import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.Qwen2VLWrappers +import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} +import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.{IntArrayParam, IntParam} +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.SparkSession + +/** Qwen2VLTransformer can load Qwen2 Vision-Language models for visual question answering and + * multimodal instruction following. The model consists of a vision encoder, a text encoder, and + * a text decoder. The vision encoder processes the input image, the text encoder integrates the + * encoding of the image with the input text, and the text decoder outputs the response to the + * query or instruction. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val visualQA = Qwen2VLTransformer.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * }}} + * The default model is `"Qwen/Qwen2-VL-@B-Instruct"`, if no name is provided. + * + * For available pretrained models, please see the + * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them, see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]]. To explore more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformerTest.scala]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val imageDF: DataFrame = ResourceHelper.spark.read + * .format("image") + * .option("dropInvalid", value = true) + * .load(imageFolder) + * + * val testDF: DataFrame = imageDF.withColumn("text", lit("<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n")) + * + * val imageAssembler: ImageAssembler = new ImageAssembler() + * .setInputCol("image") + * .setOutputCol("image_assembler") + * + * val visualQAClassifier = Qwen2VLTransformer.pretrained() + * .setInputCols("image_assembler") + * .setOutputCol("answer") + * + * val pipeline = new Pipeline().setStages(Array( + * imageAssembler, + * visualQAClassifier + * )) + * + * val result = pipeline.fit(testDF).transform(testDF) + * + * result.select("image_assembler.origin", "answer.result").show(false) + * +--------------------------------------+------+ + * |origin |result| + * +--------------------------------------+------+ + * |[file:///content/images/cat_image.jpg]|[This image is unusual because it features two cats lying on a pink couch.]| + * +--------------------------------------+------+ + * }}} + * + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer- + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ +class Qwen2VLTransformer(override val uid: String) + extends AnnotatorModel[Qwen2VLTransformer] + with HasBatchedAnnotateImage[Qwen2VLTransformer] + with HasImageFeatureProperties + with WriteOpenvinoModel + with HasGeneratorProperties + with HasEngine { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("Qwen2VLTransformer")) + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(IMAGE) + override val outputAnnotatorType: AnnotatorType = DOCUMENT + + /** @group setParam */ + def setRandomSeed(value: Int): Qwen2VLTransformer.this.type = { + if (randomSeed.isEmpty) { + this.randomSeed = Some(value) + } + this + } + + /** A list of token ids which are ignored in the decoder's output (Default: `Array()`) + * + * @group param + */ + var ignoreTokenIds = new IntArrayParam( + this, + "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output") + + /** @group setParam */ + def setIgnoreTokenIds(tokenIds: Array[Int]): Qwen2VLTransformer.this.type = { + set(ignoreTokenIds, tokenIds) + } + + /** @group getParam */ + def getIgnoreTokenIds: Array[Int] = $(ignoreTokenIds) + + /** Vocabulary used to encode the words to ids with bpeTokenizer.encode + * + * @group param + */ + val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() + + /** @group setParam */ + def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value) + + /** Holding merges.txt coming from RoBERTa model + * + * @group param + */ + val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected() + + /** @group setParam */ + def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value) + + /** Additional tokens to be added to the vocabulary + * + * @group param + */ + val addedTokens: MapFeature[String, Int] = new MapFeature(this, "addedTokens").setProtected() + + /** @group setParam */ + def setAddedTokens(value: Map[String, Int]): this.type = set(addedTokens, value) + + /** Stop tokens to terminate the generation + * + * @group param + */ + override val stopTokenIds = + new IntArrayParam(this, "stopTokenIds", "Stop tokens to terminate the generation") + + /** @group setParam */ + override def setStopTokenIds(value: Array[Int]): this.type = { + set(stopTokenIds, value) + } + + /** @group getParam */ + override def getStopTokenIds: Array[Int] = $(stopTokenIds) + + /** max pixel values for image normalization + * + * @group param + */ + val maxPixelValue = + new IntParam(this, "maxPixelValue", "max pixel values for image normalization") + + /** @group setParam */ + def setMaxPixelValue(value: Int): this.type = { + set(maxPixelValue, value) + } + + /** @group getParam */ + def getMaxPixelValue: Int = $(maxPixelValue) + + /** min pixel values for image normalization + * + * @group param + */ + val minPixelValue = + new IntParam(this, "minPixelValue", "min pixel values for image normalization") + + /** @group setParam */ + def setMinPixelValue(value: Int): this.type = { + set(minPixelValue, value) + } + + /** @group getParam */ + def getMinPixelValue: Int = $(minPixelValue) + + private var _model: Option[Broadcast[Qwen2VL]] = None + val generationConfig: StructFeature[GenerationConfig] = + new StructFeature(this, "generationConfig").setProtected() + + def setGenerationConfig(value: GenerationConfig): this.type = + set(generationConfig, value) + + def getGenerationConfig: GenerationConfig = $$(generationConfig) + + /** @group setParam */ + def setModelIfNotSet( + spark: SparkSession, + preprocessor: Preprocessor, + onnxWrappers: Option[DecoderWrappers], + openvinoWrapper: Option[Qwen2VLWrappers]): this.type = { + if (_model.isEmpty) { + _model = Some( + spark.sparkContext.broadcast( + new Qwen2VL( + onnxWrappers, + openvinoWrapper, + $$(merges), + $$(vocabulary), + $$(addedTokens), + preprocessor = preprocessor, + generationConfig = getGenerationConfig, + minPixels = $(minPixelValue), + maxPixels = $(maxPixelValue)))) + } + this + } + + /** @group getParam */ + def getModelIfNotSet: Qwen2VL = _model.get.value + + setDefault( + minOutputLength -> 0, + maxOutputLength -> 20, + doSample -> false, + temperature -> 0.6, + topK -> -1, + topP -> 0.9, + repetitionPenalty -> 1.0, + noRepeatNgramSize -> 3, + ignoreTokenIds -> Array(), + batchSize -> 1, + beamSize -> 1, + maxInputLength -> 4096, + stopTokenIds -> Array(128001), + maxPixelValue -> 16384 * 28 * 28, + minPixelValue -> 256 * 28 * 28) + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + */ + override def batchAnnotate( + batchedAnnotations: Seq[Array[AnnotationImage]]): Seq[Seq[Annotation]] = { + + batchedAnnotations +// .filter { annotationImages => +// annotationImages.exists(_.text.nonEmpty) +// } + .map { cleanAnnotationImages => + val validImages = cleanAnnotationImages.filter(_.result.nonEmpty) + val questionAnnotations = extractInputAnnotation(validImages) + + getModelIfNotSet.predict( + questionAnnotations, + validImages.toSeq, + batchSize = $(batchSize), + minOutputLength = $(minOutputLength), + maxOutputLength = $(maxOutputLength), + doSample = $(doSample), + temperature = $(temperature), + topK = $(topK), + topP = $(topP), + repetitionPenalty = $(repetitionPenalty), + noRepeatNgramSize = $(noRepeatNgramSize), + randomSeed = this.randomSeed, + ignoreTokenIds = $(ignoreTokenIds), + beamSize = $(beamSize), + maxInputLength = $(maxInputLength)) + } + } + + private def extractInputAnnotation( + annotationImages: Array[AnnotationImage]): Seq[Annotation] = { + val questions = annotationImages.map(annotationImage => { + val imageText = + if (annotationImage.text.nonEmpty) annotationImage.text + else + "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n" // default question + Annotation(imageText) + }) + + questions + } + + override def onWrite(path: String, spark: SparkSession): Unit = { + super.onWrite(path, spark) + getEngine match { + case Openvino.name => + val wrappers = getModelIfNotSet.openvinoWrapper + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.patchReshapeModel, "openvino_patch_reshape_model.xml")), + Qwen2VLTransformer.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.languageModel, "openvino_language_model.xml")), + Qwen2VLTransformer.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.imageEmbedding, "openvino_vision_embeddings_model.xml")), + Qwen2VLTransformer.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.imageEmbeddingMerger, "openvino_vision_embeddings_merger_model.xml")), + Qwen2VLTransformer.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.textEmbedding, "openvino_text_embeddings_model.xml")), + Qwen2VLTransformer.suffix) + + writeOpenvinoModels( + path, + spark, + Seq((wrappers.get.multimodalMergeModel, "openvino_multimodal_merge_model.xml")), + Qwen2VLTransformer.suffix) + case _ => + throw new Exception(notSupportedEngineError) + } + } + +} + +trait ReadablePretrainedQwen2VLTransformer + extends ParamsAndFeaturesReadable[Qwen2VLTransformer] + with HasPretrained[Qwen2VLTransformer] { + + override val defaultModelName: Some[String] = Some("phi3v") + + /** Java compliant-overrides */ + override def pretrained(): Qwen2VLTransformer = super.pretrained() + + override def pretrained(name: String): Qwen2VLTransformer = + super.pretrained(name) + + override def pretrained(name: String, lang: String): Qwen2VLTransformer = + super.pretrained(name, lang) + + override def pretrained(name: String, lang: String, remoteLoc: String): Qwen2VLTransformer = + super.pretrained(name, lang, remoteLoc) + +} + +trait ReadQwen2VLTransformerDLModel extends ReadOpenvinoModel { + this: ParamsAndFeaturesReadable[Qwen2VLTransformer] => + val suffix: String = "_phi3v" + override val openvinoFile: String = "phi3v_openvino" + def readModel(instance: Qwen2VLTransformer, path: String, spark: SparkSession): Unit = { + instance.getEngine match { + // LANGUAGE_MODEL_NAME = "openvino_language_model.xml" + // IMAGE_EMBEDDING_NAME = "openvino_vision_embeddings_model.xml" + // IMAGE_EMBEDDING_MERGER_NAME = "openvino_vision_embeddings_merger_model.xml" + // TEXT_EMBEDDING_NAME = "openvino_text_embeddings_model.xml" + // ROTARY_EMBEDDING_NAME = "openvino_rotary_embeddings_model.xml" + // PATCH_RESHAPE_NAME = "openvino_patch_reshape_model.xml" + case Openvino.name => + val languageModelWrappers = + readOpenvinoModels(path, spark, Seq("openvino_language_model.xml"), suffix) + val imageEmbeddingWrappers = + readOpenvinoModels(path, spark, Seq("openvino_vision_embeddings_model.xml"), suffix) + val imageEmbeddingMergerWrappers = + readOpenvinoModels( + path, + spark, + Seq("openvino_vision_embeddings_merger_model.xml"), + suffix) + val textEmbeddingWrappers = + readOpenvinoModels(path, spark, Seq("openvino_text_embeddings_model.xml"), suffix) + val rotaryEmbeddingWrappers = + readOpenvinoModels(path, spark, Seq("openvino_rotary_embeddings_model.xml"), suffix) + val patchReshapeWrappers = + readOpenvinoModels(path, spark, Seq("openvino_patch_reshape_model.xml"), suffix) + val multiModalMergeWrappers = + readOpenvinoModels(path, spark, Seq("openvino_multimodal_merge_model.xml"), suffix) + val ovWrapper = Qwen2VLWrappers( + imageEmbedding = imageEmbeddingWrappers("openvino_vision_embeddings_model.xml"), + imageEmbeddingMerger = + imageEmbeddingMergerWrappers("openvino_vision_embeddings_merger_model.xml"), + languageModel = languageModelWrappers("openvino_language_model.xml"), + textEmbedding = textEmbeddingWrappers("openvino_text_embeddings_model.xml"), + rotaryEmbedding = rotaryEmbeddingWrappers("openvino_rotary_embeddings_model.xml"), + patchReshapeModel = patchReshapeWrappers("openvino_patch_reshape_model.xml"), + multimodalMergeModel = multiModalMergeWrappers("openvino_multimodal_merge_model.xml")) + val preprocessor = Preprocessor( + do_normalize = true, + do_resize = true, + "Qwen2VLFeatureExtractor", + instance.getImageMean, + instance.getImageStd, + instance.getResample, + instance.getSize) + instance.setModelIfNotSet(spark, preprocessor, None, Some(ovWrapper)) + case _ => { + throw new Exception(notSupportedEngineError) + } + } + } + + addReader(readModel) + + def loadSavedModel( + modelPath: String, + spark: SparkSession, + useOpenvino: Boolean = false): Qwen2VLTransformer = { + implicit val formats: DefaultFormats.type = DefaultFormats // for json4 + val (localModelPath, detectedEngine) = + modelSanityCheck( + modelPath, + isDecoder = false, + custom = Some( + List( + "openvino_text_embeddings_model", + "openvino_language_model", + "openvino_vision_embeddings_model", + "openvino_vision_embeddings_merger_model", + "openvino_rotary_embeddings_model", + "openvino_patch_reshape_model", + "openvino_multimodal_merge_model"))) + val modelConfig: JValue = + parse(loadJsonStringAsset(localModelPath, "config.json")) + val preprocessorConfigJsonContent = + loadJsonStringAsset(localModelPath, "preprocessor_config.json") + val preprocessorConfig = Preprocessor.loadPreprocessorConfig(preprocessorConfigJsonContent) + val beginSuppressTokens: Array[Int] = + (modelConfig \ "begin_suppress_tokens").extract[Array[Int]] + + val suppressTokenIds: Array[Int] = + (modelConfig \ "suppress_tokens").extract[Array[Int]] + + val forcedDecoderIds: Array[(Int, Int)] = + (modelConfig \ "forced_decoder_ids").extract[Array[Array[Int]]].map { + case idxWithTokenId: Array[Int] if idxWithTokenId.length == 2 => + (idxWithTokenId(0), idxWithTokenId(1)) + case _ => + throw new Exception( + "Could not extract forced_decoder_ids. Should be a list of tuples with 2 entries.") + } + + def arrayOrNone[T](array: Array[T]): Option[Array[T]] = + if (array.nonEmpty) Some(array) else None + + val bosTokenId = (modelConfig \ "bos_token_id").extract[Int] + val eosTokenId = (modelConfig \ "eos_token_id").extract[Int] + val padTokenId = (modelConfig \ "eos_token_id").extract[Int] + val vocabSize = (modelConfig \ "vocab_size").extract[Int] + + // Check if tokenizer.json exists + val tokenizerPath = s"$localModelPath/assets/tokenizer.json" + val tokenizerExists = new java.io.File(tokenizerPath).exists() + val (vocabs, addedTokens, bytePairs) = if (tokenizerExists) { + val tokenizerConfig: JValue = parse(loadJsonStringAsset(localModelPath, "tokenizer.json")) + // extract vocab from tokenizer.json ( model -> vocab) + var vocabs: Map[String, Int] = + (tokenizerConfig \ "model" \ "vocab").extract[Map[String, Int]] + + // extract merges from tokenizer.json ( model -> merges) + val bytePairs = (tokenizerConfig \ "model" \ "merges") + .extract[List[Array[String]]] + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + + // extract added_tokens from tokenizer.json (added_tokens) + // "added_tokens": [ + // { + // "id": 128000, + // "content": "<|begin_of_text|>", + // "single_word": false, + // "lstrip": false, + // "rstrip": false, + // "normalized": false, + // "special": true + // }, ... + // ] + val addedTokens = (tokenizerConfig \ "added_tokens") + .extract[List[Map[String, Any]]] + .map { token => + val id = token("id").asInstanceOf[BigInt].intValue() + val content = token("content").asInstanceOf[String] + (content, id) + } + .toMap + + // update vocab with added tokens + addedTokens.foreach { case (content, id) => + vocabs += (content -> id) + } + (vocabs, addedTokens, bytePairs) + } else { + val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap + val addedTokens = loadTextAsset(localModelPath, "added_tokens.txt").zipWithIndex.toMap + val bytePairs = loadTextAsset(localModelPath, "merges.txt") + .map(_.split(" ")) + .filter(w => w.length == 2) + .map { case Array(c1, c2) => (c1, c2) } + .zipWithIndex + .toMap + (vocabs, addedTokens, bytePairs) + } + + val annotatorModel = new Qwen2VLTransformer() + .setGenerationConfig( + GenerationConfig( + bosTokenId, + padTokenId, + eosTokenId, + vocabSize, + arrayOrNone(beginSuppressTokens), + arrayOrNone(suppressTokenIds), + arrayOrNone(forcedDecoderIds))) + .setVocabulary(vocabs) + .setMerges(bytePairs) + .setAddedTokens(addedTokens) + + val modelEngine = + if (useOpenvino) + Openvino.name + else + detectedEngine + annotatorModel.set(annotatorModel.engine, modelEngine) + + detectedEngine match { + case Openvino.name => + val patchReshapeWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_patch_reshape_model") + + val languageModelWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_language_model") + + val imageEmbeddingWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_vision_embeddings_model") + + val imageEmbeddingMergerWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_vision_embeddings_merger_model") + + val textEmbeddingWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_text_embeddings_model") + + val rotaryEmbeddingWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_rotary_embeddings_model") + + val multimodalMergerWrappers = + OpenvinoWrapper.read( + spark, + localModelPath, + zipped = false, + useBundle = true, + detectedEngine = detectedEngine, + modelName = "openvino_multimodal_merge_model") + + val openvinoWrapper = Qwen2VLWrappers( + languageModel = languageModelWrappers, + imageEmbedding = imageEmbeddingWrappers, + imageEmbeddingMerger = imageEmbeddingMergerWrappers, + textEmbedding = textEmbeddingWrappers, + rotaryEmbedding = rotaryEmbeddingWrappers, + patchReshapeModel = patchReshapeWrappers, + multimodalMergeModel = multimodalMergerWrappers) + annotatorModel.setModelIfNotSet(spark, preprocessorConfig, None, Some(openvinoWrapper)) + case _ => + throw new Exception(notSupportedEngineError) + } + + annotatorModel + } +} + +object Qwen2VLTransformer + extends ReadablePretrainedQwen2VLTransformer + with ReadQwen2VLTransformerDLModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/feature_extractor/Preprocessor.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/feature_extractor/Preprocessor.scala index f043f8450d1e69..41b448632d7807 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/feature_extractor/Preprocessor.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/feature_extractor/Preprocessor.scala @@ -135,6 +135,9 @@ private[johnsnowlabs] object Preprocessor { // ConvNext case: Size of the output image after `resize` has been applied sizeMap("shortest_edge").toInt case sizeInt: BigInt => sizeInt.toInt + case sizeMap: Map[String, BigInt] if sizeMap.contains("max_pixels") => + val max_pixels = sizeMap("max_pixels") + max_pixels.toInt case _ => throw new IllegalArgumentException( "Unsupported format for size. Should either be int or dict with entries \'width\' and \'height\' or \'shortest_edge\'") diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Qwen2VLUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Qwen2VLUtils.scala new file mode 100644 index 00000000000000..a20b1a3ef032cf --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/Qwen2VLUtils.scala @@ -0,0 +1,63 @@ +package com.johnsnowlabs.nlp.annotators.cv.util.transform +import java.awt.image.BufferedImage + +private[johnsnowlabs] object Qwen2VLUtils { + + val IMAGE_FACTOR: Int = 28 + val MIN_PIXELS: Int = 4 * 28 * 28 + val MAX_PIXELS: Int = 16384 * 28 * 28 + val MAX_RATIO: Int = 200 + + def roundByFactor(number: Int, factor: Int): Int = + Math.round(number.toDouble / factor).toInt * factor + + def ceilByFactor(number: Int, factor: Int): Int = + Math.ceil(number.toDouble / factor).toInt * factor + + def floorByFactor(number: Int, factor: Int): Int = + Math.floor(number.toDouble / factor).toInt * factor + + def smartResize( + height: Int, + width: Int, + factor: Int = IMAGE_FACTOR, + minPixels: Int = MIN_PIXELS, + maxPixels: Int = MAX_PIXELS): (Int, Int) = { + if (Math.max(height, width).toDouble / Math.min(height, width) > MAX_RATIO) { + throw new IllegalArgumentException(s"absolute aspect ratio must be smaller than $MAX_RATIO") + } + + var hBar = Math.max(factor, roundByFactor(height, factor)) + var wBar = Math.max(factor, roundByFactor(width, factor)) + + if (hBar * wBar > maxPixels) { + val beta = Math.sqrt(height.toDouble * width / maxPixels) + hBar = floorByFactor((height / beta).toInt, factor) + wBar = floorByFactor((width / beta).toInt, factor) + } else if (hBar * wBar < minPixels) { + val beta = Math.sqrt(minPixels.toDouble / (height * width)) + hBar = ceilByFactor((height * beta).toInt, factor) + wBar = ceilByFactor((width * beta).toInt, factor) + } + + (hBar, wBar) + } + + def imageBufferToArray(imgCrop: BufferedImage): Array[Array[Array[Int]]] = { + val height = imgCrop.getHeight + val width = imgCrop.getWidth + + // Create a 3D array for RGB channels + val channels = 3 + val cropArray = Array.ofDim[Int](channels, height, width) + + for (y <- 0 until height; x <- 0 until width) { + val color = new java.awt.Color(imgCrop.getRGB(x, y)) + cropArray(0)(y)(x) = color.getRed // Red channel + cropArray(1)(y)(x) = color.getGreen // Green channel + cropArray(2)(y)(x) = color.getBlue // Blue channel + } + + cropArray + } +} diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala index 8c72a8f99d6685..18b00791527ed7 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala @@ -297,15 +297,16 @@ private[nlp] abstract class BpeTokenizer( def encode(indToken: IndexedToken): Array[TokenPiece] = { if (!specialTokens.contains(indToken.token)) bpe(indToken) - else + else { Array( TokenPiece( indToken.token, indToken.token, vocab(indToken.token), - isWordStart = true, + isWordStart = false, indToken.begin, indToken.end)) + } } def encode(indTokens: Array[IndexedToken]): Array[TokenPiece] = indTokens.flatMap(encode(_)) @@ -319,7 +320,8 @@ object BpeTokenizer { padWithSequenceTokens: Boolean = false, addPrefixSpaceToSentence: Boolean = false, specialTokens: Option[SpecialTokens] = None, - alwaysAddPrefix: Boolean = true): BpeTokenizer = { + alwaysAddPrefix: Boolean = true, + prependString: String = ""): BpeTokenizer = { def modelSpecialTokens() = specialTokens match { case Some(specialTok) => specialTok @@ -382,6 +384,14 @@ object BpeTokenizer { modelSpecialTokens(), padWithSequenceTokens, addPrefixSpaceToSentence = addPrefixSpaceToSentence) + case "qwen2vl" => + new Qwen2VLTokenizer( + merges, + vocab, + modelSpecialTokens(), + padWithSequenceTokens, + addPrefixSpaceToSentence = addPrefixSpaceToSentence, + prependString = prependString) case _ => throw new IllegalArgumentException("Model type \"" + modelType + "\" not supported yet.") } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Qwen2VLTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Qwen2VLTokenizer.scala new file mode 100644 index 00000000000000..98ca09b2d28118 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/Qwen2VLTokenizer.scala @@ -0,0 +1,102 @@ +/* + * Copyright 2017-2022 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.tokenizer.bpe + +import com.johnsnowlabs.nlp.annotators.common.IndexedToken + +import java.nio.charset.Charset +import scala.collection.mutable.ListBuffer +import scala.util.matching.Regex +import scala.collection.mutable + +class Qwen2VLTokenizer( + merges: Map[(String, String), Int], + vocab: Map[String, Int], + specialTokens: SpecialTokens, + padWithSequenceTokens: Boolean = true, + prependString: String = "", + addPrefixSpaceToSentence: Boolean = false, + alwaysAddPrefix: Boolean = true, + splitPatternRegex: Regex = + raw"""(?i)(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".r) + extends BpeTokenizer( + merges, + vocab, + specialTokens, + padWithSequenceTokens, + addPrefixSpaceToSentence, + alwaysAddPrefix) { + + /** Mapping for bytes to a different set of unicode characters (especially white spaces). This + * improved model performance for gpt-2 + */ + protected val bytesToUnicodeMapping: Map[Int, String] = { + val bytes: ListBuffer[Int] = + ListBuffer.range('!', '~' + 1) ++ ListBuffer.range('ยก', 'ยฌ' + 1) ++ ListBuffer + .range('ยฎ', 'รฟ' + 1) + val characters: ListBuffer[Int] = bytes.clone + var n = 0 + for (b <- 0 to 256) { + if (!bytes.contains(b)) { + bytes += b + characters += (256 + n) + n += 1 + } + } + (bytes zip characters.map(_.toChar.toString)).toMap + } + + // Differs from Transformers, space is always prepended. + // FIX: Space should not be prepended to all tokens, but to the beginning of the text only. Otherwise token + // such as '.' get space prepended and they should not. + override val prefixForPieceId: Option[String] = + if (prependString.nonEmpty) Some(prependString) else None + + protected val decoderVocab: Map[Int, String] = vocab.map(x => (x._2, x._1)) + + protected val unicodeToByteMapping: Map[String, Int] = + bytesToUnicodeMapping.map(x => (x._2, x._1)) + + override def preProcessTokenForBpe(token: String): String = { + token + .getBytes("UTF-8") + .map { b => if (b < 0) 256 + b else b } + .foldLeft("")(_ + bytesToUnicodeMapping(_)) + } + + val splitPattern: Regex = splitPatternRegex + + override def tokenizeSubText(text: String, indexOffset: Int): Array[IndexedToken] = { + // split pattern based on gpt2's bpe tokenizer + splitPattern + .findAllMatchIn(if (prefixForPieceId.isDefined || text.startsWith(" ")) text + else " " + text) // Prepend space to the beginning of text + .map(tok => IndexedToken(tok.matched, tok.start + indexOffset, tok.end + indexOffset - 1)) + .toArray + } + + def decodeTokens(tokens: Array[Int]): String = { + val text = tokens + .map(token => decoderVocab(token)) + .filter(x => !specialTokens.contains(x)) + .mkString("") + + val bytes = + text.map(x => unicodeToByteMapping(x.toString)).map(x => x.toByte).toArray + new String(bytes, Charset.forName("UTF-8")) + } +} diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformerTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformerTestSpec.scala new file mode 100644 index 00000000000000..9c7128239d2569 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformerTestSpec.scala @@ -0,0 +1,189 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, ImageAssembler} +import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit +import org.scalatest.flatspec.AnyFlatSpec + +class Qwen2VLTransformerTestSpec extends AnyFlatSpec { + + lazy val model = getQwen2VLTransformerPipelineModel + + "Qwen2VLTransformer" should "answer a question for a given image" taggedAs SlowTest in { + + val testDF = getTestDF + val result = model.transform(testDF) + + val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") + + answerAnnotation.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + + answerAnnotation.foreach { annotation => + annotation.foreach(a => println(a.result)) + } + + } + + it should "work with light pipeline annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/image/egyptian_cat.jpeg" + val resultAnnotate = + lightPipeline.annotate( + imagePath, + "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.contains("cat")) + } + + it should "work with light pipeline full annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/image/bluetick.jpg" + val resultFullAnnotate = + lightPipeline.fullAnnotateImage( + imagePath, + "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + println(s"imageName.result: ${answerAnnotation.result}") + assert(answerAnnotation.result.nonEmpty) + } + + it should "fullAnnotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n" + val questions = Array(question, "", question) + + val resultFullAnnotate = lightPipeline.fullAnnotateImages(imagesPath, questions) + + resultFullAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { iAnnotation => + val annotation = iAnnotation.asInstanceOf[Annotation] + assert( + annotation.result.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + } + + it should "annotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n" + val questions = Array(question, "", question) + + val resultAnnotate = lightPipeline.annotate(imagesPath, questions) + + resultAnnotate.foreach { annotate => + println(s"annotate: $annotate") + } + + resultAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { annotation => + assert( + annotation.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + + } + + private def getQwen2VLTransformerPipelineModel = { + val testDF = getTestDF + + val imageAssembler: ImageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + + val loadModel = Qwen2VLTransformer + .pretrained() + .setInputCols("image_assembler") + .setOutputCol("answer") + .setMaxOutputLength(200) + + val newPipeline: Pipeline = + new Pipeline().setStages(Array(imageAssembler, loadModel)) + + newPipeline.fit(testDF) + } + + private def getTestDF: DataFrame = { + val imageFolder = "src/test/resources/image/" + val imageDF: DataFrame = ResourceHelper.spark.read + .format("image") + .option("dropInvalid", value = true) + .load(imageFolder) + + val testDF: DataFrame = imageDF.withColumn( + "text", + lit( + "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n")) + + testDF + } + +} From e5017e2a6cb4cb15708e6212a911545e4aed9c77 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 10 Dec 2024 04:12:51 +0000 Subject: [PATCH 055/108] QWEN2VL python api Signed-off-by: Prabod Rathnayaka --- python/sparknlp/annotator/cv/__init__.py | 3 +- .../annotator/cv/qwen2vl_transformer.py | 332 ++++++++++++++++++ python/sparknlp/internal/__init__.py | 9 + .../annotator/cv/qwen2vl_transformer_test.py | 83 +++++ 4 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 python/sparknlp/annotator/cv/qwen2vl_transformer.py create mode 100644 python/test/annotator/cv/qwen2vl_transformer_test.py diff --git a/python/sparknlp/annotator/cv/__init__.py b/python/sparknlp/annotator/cv/__init__.py index 37eeaf696bb2a8..06910fc7f638d1 100644 --- a/python/sparknlp/annotator/cv/__init__.py +++ b/python/sparknlp/annotator/cv/__init__.py @@ -16,4 +16,5 @@ from sparknlp.annotator.cv.convnext_for_image_classification import * from sparknlp.annotator.cv.vision_encoder_decoder_for_image_captioning import * from sparknlp.annotator.cv.clip_for_zero_shot_classification import * -from sparknlp.annotator.cv.blip_for_question_answering import * \ No newline at end of file +from sparknlp.annotator.cv.blip_for_question_answering import * +from sparknlp.annotator.cv.qwen2vl_transformer import * \ No newline at end of file diff --git a/python/sparknlp/annotator/cv/qwen2vl_transformer.py b/python/sparknlp/annotator/cv/qwen2vl_transformer.py new file mode 100644 index 00000000000000..06d4a5644c5678 --- /dev/null +++ b/python/sparknlp/annotator/cv/qwen2vl_transformer.py @@ -0,0 +1,332 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class Qwen2VLTransformer(AnnotatorModel, + HasBatchedAnnotateImage, + HasImageFeatureProperties, + HasEngine, + HasCandidateLabelsProperties, + HasRescaleFactor): + """ + Qwen2VLTransformer can load Qwen2 Vision-Language models for visual question answering + and multimodal instruction following. The model consists of a vision encoder, a text encoder, + and a text decoder. The vision encoder processes the input image, the text encoder integrates + the encoding of the image with the input text, and the text decoder outputs the response to + the query or instruction. + + Pretrained models can be loaded with :meth:`.pretrained` of the companion object: + + >>> visualQAClassifier = Qwen2VLTransformer.pretrained() \\ + ... .setInputCols(["image_assembler"]) \\ + ... .setOutputCol("answer") + + The default model is ``"Qwen/Qwen2-VL-7B-Instruct"``, if no name is provided. + + For available pretrained models, please see the `Models Hub + `__. + + Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + see which models are compatible and how to import them, see + `Import Transformers into Spark NLP ๐Ÿš€ + `__. For more extended examples, see + `Spark NLP Test Suite for Qwen2VLTransformer + `__. + + ====================== ====================== + Input Annotation types Output Annotation type + ====================== ====================== + ``IMAGE`` ``DOCUMENT`` + ====================== ====================== + + Parameters + ---------- + batchSize + Batch size. Large values allow faster processing but require more memory, + by default 2 + configProtoBytes + ConfigProto from TensorFlow, serialized into byte array. + maxSentenceLength + Max sentence length to process, by default 50 + + Examples + -------- + >>> import sparknlp + >>> from sparknlp.base import * + >>> from sparknlp.annotator import * + >>> from pyspark.ml import Pipeline + >>> image_df = SparkSessionForTest.spark.read.format("image").load(path=images_path) + >>> test_df = image_df.withColumn("text", lit("<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n")) + >>> imageAssembler = ImageAssembler() \\ + ... .setInputCol("image") \\ + ... .setOutputCol("image_assembler") + >>> visualQAClassifier = Qwen2VLTransformer.pretrained() \\ + ... .setInputCols("image_assembler") \\ + ... .setOutputCol("answer") + >>> pipeline = Pipeline().setStages([ + ... imageAssembler, + ... visualQAClassifier + ... ]) + >>> result = pipeline.fit(test_df).transform(test_df) + >>> result.select("image_assembler.origin", "answer.result").show(false) + +--------------------------------------+------+ + |origin |result| + +--------------------------------------+------+ + |[file:///content/images/cat_image.jpg]|[This image is unusual because it features two cats lying on a pink couch.]| + +--------------------------------------+------+ + """ + + + name = "Qwen2VLTransformer" + + inputAnnotatorTypes = [AnnotatorType.IMAGE] + + outputAnnotatorType = AnnotatorType.DOCUMENT + + configProtoBytes = Param(Params._dummy(), + "configProtoBytes", + "ConfigProto from tensorflow, serialized into byte array. Get with " + "config_proto.SerializeToString()", + TypeConverters.toListInt) + + minOutputLength = Param(Params._dummy(), "minOutputLength", "Minimum length of the sequence to be generated", + typeConverter=TypeConverters.toInt) + + maxOutputLength = Param(Params._dummy(), "maxOutputLength", "Maximum length of output text", + typeConverter=TypeConverters.toInt) + + doSample = Param(Params._dummy(), "doSample", "Whether or not to use sampling; use greedy decoding otherwise", + typeConverter=TypeConverters.toBoolean) + + temperature = Param(Params._dummy(), "temperature", "The value used to module the next token probabilities", + typeConverter=TypeConverters.toFloat) + + topK = Param(Params._dummy(), "topK", + "The number of highest probability vocabulary tokens to keep for top-k-filtering", + typeConverter=TypeConverters.toInt) + + topP = Param(Params._dummy(), "topP", + "If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or higher are kept for generation", + typeConverter=TypeConverters.toFloat) + + repetitionPenalty = Param(Params._dummy(), "repetitionPenalty", + "The parameter for repetition penalty. 1.0 means no penalty. See `this paper `__ for more details", + typeConverter=TypeConverters.toFloat) + + noRepeatNgramSize = Param(Params._dummy(), "noRepeatNgramSize", + "If set to int > 0, all ngrams of that size can only occur once", + typeConverter=TypeConverters.toInt) + + ignoreTokenIds = Param(Params._dummy(), "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output", + typeConverter=TypeConverters.toListInt) + beamSize = Param(Params._dummy(), "beamSize", + "The Number of beams for beam search.", + typeConverter=TypeConverters.toInt) + + def setMaxSentenceSize(self, value): + """Sets Maximum sentence length that the annotator will process, by + default 50. + + Parameters + ---------- + value : int + Maximum sentence length that the annotator will process + """ + return self._set(maxSentenceLength=value) + + def setIgnoreTokenIds(self, value): + """A list of token ids which are ignored in the decoder's output. + + Parameters + ---------- + value : List[int] + The words to be filtered out + """ + return self._set(ignoreTokenIds=value) + + def setConfigProtoBytes(self, b): + """Sets configProto from tensorflow, serialized into byte array. + + Parameters + ---------- + b : List[int] + ConfigProto from tensorflow, serialized into byte array + """ + return self._set(configProtoBytes=b) + + def setMinOutputLength(self, value): + """Sets minimum length of the sequence to be generated. + + Parameters + ---------- + value : int + Minimum length of the sequence to be generated + """ + return self._set(minOutputLength=value) + + def setMaxOutputLength(self, value): + """Sets maximum length of output text. + + Parameters + ---------- + value : int + Maximum length of output text + """ + return self._set(maxOutputLength=value) + + def setDoSample(self, value): + """Sets whether or not to use sampling, use greedy decoding otherwise. + + Parameters + ---------- + value : bool + Whether or not to use sampling; use greedy decoding otherwise + """ + return self._set(doSample=value) + + def setTemperature(self, value): + """Sets the value used to module the next token probabilities. + + Parameters + ---------- + value : float + The value used to module the next token probabilities + """ + return self._set(temperature=value) + + def setTopK(self, value): + """Sets the number of highest probability vocabulary tokens to keep for + top-k-filtering. + + Parameters + ---------- + value : int + Number of highest probability vocabulary tokens to keep + """ + return self._set(topK=value) + + def setTopP(self, value): + """Sets the top cumulative probability for vocabulary tokens. + + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + + Parameters + ---------- + value : float + Cumulative probability for vocabulary tokens + """ + return self._set(topP=value) + + def setRepetitionPenalty(self, value): + """Sets the parameter for repetition penalty. 1.0 means no penalty. + + Parameters + ---------- + value : float + The repetition penalty + + References + ---------- + See `Ctrl: A Conditional Transformer Language Model For Controllable + Generation `__ for more details. + """ + return self._set(repetitionPenalty=value) + + def setNoRepeatNgramSize(self, value): + """Sets size of n-grams that can only occur once. + + If set to int > 0, all ngrams of that size can only occur once. + + Parameters + ---------- + value : int + N-gram size can only occur once + """ + return self._set(noRepeatNgramSize=value) + + def setBeamSize(self, value): + """Sets the number of beam size for beam search, by default `4`. + + Parameters + ---------- + value : int + Number of beam size for beam search + """ + return self._set(beamSize=value) + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cv.Qwen2VLTransformer", + java_model=None): + super(Qwen2VLTransformer, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=2, + minOutputLength=0, + maxOutputLength=200, + doSample=False, + temperature=1, + topK=50, + topP=1, + repetitionPenalty=1.0, + noRepeatNgramSize=0, + ignoreTokenIds=[], + beamSize=1, + ) + + @staticmethod + def loadSavedModel(folder, spark_session, use_openvino=False): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.internal import _Qwen2VLTransformerLoader + jModel = _Qwen2VLTransformerLoader(folder, spark_session._jsparkSession, use_openvino)._java_obj + return Qwen2VLTransformer(java_model=jModel) + + @staticmethod + def pretrained(name="phi3v", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "phi3v" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(Qwen2VLTransformer, name, lang, remote_loc) \ No newline at end of file diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..ab694bbeb8a206 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -1021,3 +1021,12 @@ def __init__(self, path, jspark): path, jspark, ) + +class _Qwen2VLTransformerLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark, use_openvino=False): + super(_Qwen2VLTransformerLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.cv.Qwen2VLTransformer.loadSavedModel", + path, + jspark, + use_openvino, + ) diff --git a/python/test/annotator/cv/qwen2vl_transformer_test.py b/python/test/annotator/cv/qwen2vl_transformer_test.py new file mode 100644 index 00000000000000..e2ca01d68d9521 --- /dev/null +++ b/python/test/annotator/cv/qwen2vl_transformer_test.py @@ -0,0 +1,83 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +import pytest +import os + +from sparknlp.annotator import * +from sparknlp.base import * +from pyspark.sql.functions import lit +from test.util import SparkSessionForTest +from test.util import SparkContextForTest + + +class Qwen2VLTransformerTestSetup(unittest.TestCase): + + def setUp(self): + self.images_path = os.getcwd() + "/../src/test/resources/image/" + image_df = SparkSessionForTest.spark.read.format("image").load( + path=self.images_path + ) + self.spark = SparkContextForTest.spark + self.test_df = image_df.withColumn("text", lit("<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n")) + + image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") + + imageClassifier = Qwen2VLTransformer.pretrained() \ + .setInputCols("image_assembler") \ + .setOutputCol("answer") + + self.pipeline = Pipeline( + stages=[ + image_assembler, + imageClassifier, + ] + ) + + self.model = self.pipeline.fit(self.test_df) + + + +@pytest.mark.slow +class Qwen2VLTransformerTest(Qwen2VLTransformerTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + result = self.model.transform(self.test_df).collect() + + for row in result: + self.assertTrue(row["answer"] != "") + print(row["answer"]) + + +@pytest.mark.slow +class LightQwen2VLTransformerTest(Qwen2VLTransformerTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.model) + image_path = self.images_path + "bluetick.jpg" + annotations_result = light_pipeline.fullAnnotateImage( + image_path, + "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n" + ) + + for result in annotations_result: + self.assertTrue(len(result["image_assembler"]) > 0) + self.assertTrue(len(result["answer"]) > 0) + print(result["answer"]) \ No newline at end of file From 16c97162626c3b5dd13c10e0f5030aaa9bddf293 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 10 Dec 2024 07:30:53 +0000 Subject: [PATCH 056/108] QWEN2VL Notebook Signed-off-by: Prabod Rathnayaka --- ...ngFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb | 1142 +++++++++++++++++ 1 file changed, 1142 insertions(+) create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb new file mode 100644 index 00000000000000..8a5aa6277b11b2 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb @@ -0,0 +1,1142 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import OpenVINO Qwen2VL models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and importing Qwen2VL models from HuggingFace for use in Spark NLP, with [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html). The focus is on converting the model to the OpenVINO format and applying precision optimizations (INT8 and INT4), to enhance the performance and efficiency on CPU platforms using [Optimum Intel](https://huggingface.co/docs/optimum/main/en/intel/inference).\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance CPU inference for models. So please make sure you have upgraded to the latest Spark NLP release.\n", + "- Model quantization is a computationally expensive process, so it is recommended to use a runtime with more than 32GB memory for exporting the quantized model from HuggingFace.\n", + "- You can import Qwen2VL models via `Qwen2VL`. These models are usually under `Text Generation` category and have `Qwen2VL` in their labels.\n", + "- Reference: [Qwen2VL](https://huggingface.co/docs/transformers/model_doc/llama#transformers.Qwen2VL)\n", + "- Some [example models](https://huggingface.co/models?search=Qwen2VL)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Export and Save the HuggingFace model\n", + "\n", + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future release, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import requests" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "%pip install -qU \"openvino>=2024.4.0\" \"nncf>=2.13.0\"\n", + "%pip install -q \"sentencepiece\" \"tokenizers>=0.12.1\" \"transformers>=4.45.0\" \"gradio>=4.36\"\n", + "%pip install -q -U --pre --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly openvino-tokenizers openvino openvino-genai\n", + "%pip install -q --upgrade huggingface_hub\n", + "%pip install -q --upgrade torch>=2.2.1\n", + "%pip install -q --upgrade qwen-vl-utils\n", + "\n", + "utility_files = [\"notebook_utils.py\", \"cmd_helper.py\"]\n", + "\n", + "from pathlib import Path\n", + "import requests\n", + "\n", + "if not Path(\"ov_qwen2_vl.py\").exists():\n", + " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/notebooks/qwen2-vl/ov_qwen2_vl.py\")\n", + " open(\"ov_qwen2_vl.py\", \"w\").write(r.text)\n", + "\n", + "if not Path(\"notebook_utils.py\").exists():\n", + " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\")\n", + " open(\"notebook_utils.py\", \"w\").write(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Convert the model to OpenVino" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:nncf:NNCF initialized successfully. Supported frameworks detected: torch, onnx, openvino\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4659f1c77b1b4fc28b2869cbed0ea309", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Dropdown(description='Model:', options=('Qwen/Qwen2-VL-2B-Instruct', 'Qwen/Qwen2-VL-7B-Instruct'), value='Qwenโ€ฆ" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from ov_qwen2_vl import model_selector\n", + "\n", + "model_id = model_selector()\n", + "\n", + "model_id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Selected Qwen/Qwen2-VL-2B-Instruct\n" + ] + } + ], + "source": [ + "print(f\"Selected {model_id.value}\")\n", + "pt_model_id = model_id.value\n", + "model_dir = Path(pt_model_id.split(\"/\")[-1])" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('test/Qwen2-VL-2B-Instruct')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_dir" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โŒ› Qwen/Qwen2-VL-2B-Instruct conversion started. Be patient, it may takes some time.\n", + "โŒ› Load Original model\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "`Qwen2VLRotaryEmbedding` can now be fully parameterized by passing the model config through the `config` argument. All other arguments will be removed in v4.46\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "afc3407b5faa4b8ea14e18fef35f69bb", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading checkpoint shards: 0%| | 0/2 [00:00 target_length:\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/cache_utils.py:444: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n", + " len(self.key_cache[layer_idx]) == 0\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Language model successfully converted\n", + "โŒ› Weights compression with int4_asym mode started\n", + "INFO:nncf:Statistics of the bitwidth distribution:\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n", + "โ”‚ Weight compression mode โ”‚ % all parameters (layers) โ”‚ % ratio-defining parameters (layers) โ”‚\n", + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n", + "โ”‚ int8_asym โ”‚ 15% (1 / 197) โ”‚ 0% (0 / 196) โ”‚\n", + "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n", + "โ”‚ int4_asym โ”‚ 85% (196 / 197) โ”‚ 100% (196 / 196) โ”‚\n", + "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b4a917cc8701479f8a37567a90cb7f54", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "โœ… Weights compression finished\n",
+      "โŒ› Convert Image embedding model\n",
+      "โŒ› Weights compression with int4_asym mode started\n",
+      "INFO:nncf:Statistics of the bitwidth distribution:\n",
+      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฏโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”‘\n",
+      "โ”‚ Weight compression mode   โ”‚ % all parameters (layers)   โ”‚ % ratio-defining parameters (layers)   โ”‚\n",
+      "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n",
+      "โ”‚ int8_asym                 โ”‚ 1% (1 / 130)                โ”‚ 0% (0 / 129)                           โ”‚\n",
+      "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n",
+      "โ”‚ int4_asym                 โ”‚ 99% (129 / 130)             โ”‚ 100% (129 / 129)                       โ”‚\n",
+      "โ”•โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ทโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”™\n"
+     ]
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "041352b5ffce4e2886add167de6ca1ad",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "โœ… Weights compression finished\n",
+      "โœ… Image embedding model successfully converted\n",
+      "โœ… Qwen/Qwen2-VL-2B-Instruct model conversion finished. You can find results in test/Qwen2-VL-2B-Instruct\n"
+     ]
+    }
+   ],
+   "source": [
+    "from ov_qwen2_vl import convert_qwen2vl_model\n",
+    "import nncf\n",
+    "\n",
+    "compression_configuration = {\n",
+    "    \"mode\": nncf.CompressWeightsMode.INT4_ASYM,\n",
+    "    \"group_size\": 128,\n",
+    "    \"ratio\": 1.0,\n",
+    "}\n",
+    "\n",
+    "convert_qwen2vl_model(pt_model_id, model_dir, compression_configuration)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import torch\n",
+    "import torch.nn as nn\n",
+    "\n",
+    "class Qwen2ReshapePatches(nn.Module):\n",
+    "    def __init__(self,\n",
+    "                 temporal_patch_size: int = 2,\n",
+    "                 merge_size: int = 2,\n",
+    "                 patch_size: int = 14\n",
+    "                 ):\n",
+    "        super().__init__()\n",
+    "        self.temporal_patch_size = temporal_patch_size\n",
+    "        self.merge_size = merge_size\n",
+    "        self.patch_size = patch_size\n",
+    "\n",
+    "    def forward(self, patches, repetition_factor=1):\n",
+    "        # Repeat the patches along the first dimension\n",
+    "        patches = patches.repeat(repetition_factor, 1, 1, 1)\n",
+    "        channel = patches.shape[1]\n",
+    "        grid_t = patches.shape[0] // self.temporal_patch_size\n",
+    "        resized_height = patches.shape[2]\n",
+    "        resized_width = patches.shape[3]\n",
+    "        grid_h, grid_w = resized_height // self.patch_size, resized_width // self.patch_size\n",
+    "        patches = patches.reshape(\n",
+    "            grid_t,\n",
+    "            self.temporal_patch_size,\n",
+    "            channel,\n",
+    "            grid_h // self.merge_size,\n",
+    "            self.merge_size,\n",
+    "            self.patch_size,\n",
+    "            grid_w // self.merge_size,\n",
+    "            self.merge_size,\n",
+    "            self.patch_size,\n",
+    "        )\n",
+    "        patches = patches.permute(0, 3, 6, 4, 7, 2, 1, 5, 8)\n",
+    "        flatten_patches = patches.reshape(\n",
+    "            grid_t * grid_h * grid_w, channel * self.temporal_patch_size * self.patch_size * self.patch_size\n",
+    "        )\n",
+    "\n",
+    "        return flatten_patches\n",
+    "\n",
+    "\n",
+    "patch_reshape_model = Qwen2ReshapePatches()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import openvino as ov\n",
+    "\n",
+    "\n",
+    "ov_model = ov.convert_model(\n",
+    "            patch_reshape_model,\n",
+    "            example_input={\n",
+    "                \"patches\": torch.ones((1, 3, 1372, 2044), dtype=torch.float32),\n",
+    "                \"repetition_factor\": torch.tensor(2),\n",
+    "            }\n",
+    "        )\n",
+    "\n",
+    "# Save the OpenVINO model\n",
+    "ov.save_model(ov_model, model_dir/\"openvino_patch_reshape_model.xml\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from transformers.models.qwen2_vl.modeling_qwen2_vl import VisionRotaryEmbedding\n",
+    "from transformers import Qwen2VLForConditionalGeneration, AutoProcessor, AutoConfig\n",
+    "\n",
+    "config = AutoConfig.from_pretrained(\"Qwen/Qwen2-VL-2B-Instruct\")\n",
+    "\n",
+    "\n",
+    "class RotaryEmbedding(nn.Module):\n",
+    "\n",
+    "    def __init__(self, embed_dim, spatial_merge_size):\n",
+    "        super().__init__()\n",
+    "        self._rotary_pos_emb = VisionRotaryEmbedding(embed_dim)\n",
+    "        self.spatial_merge_size = spatial_merge_size\n",
+    "    \n",
+    "    def forward(self, grid_thw):\n",
+    "        t, h, w = grid_thw\n",
+    "        pos_ids = []\n",
+    "        # for t, h, w in grid_thw:\n",
+    "\n",
+    "        hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)\n",
+    "        hpos_ids = hpos_ids.reshape(\n",
+    "            h // self.spatial_merge_size,\n",
+    "            self.spatial_merge_size,\n",
+    "            w // self.spatial_merge_size,\n",
+    "            self.spatial_merge_size,\n",
+    "        )\n",
+    "        hpos_ids = hpos_ids.permute(0, 2, 1, 3)\n",
+    "        hpos_ids = hpos_ids.flatten()\n",
+    "\n",
+    "        wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)\n",
+    "        wpos_ids = wpos_ids.reshape(\n",
+    "            h // self.spatial_merge_size,\n",
+    "            self.spatial_merge_size,\n",
+    "            w // self.spatial_merge_size,\n",
+    "            self.spatial_merge_size,\n",
+    "        )\n",
+    "        wpos_ids = wpos_ids.permute(0, 2, 1, 3)\n",
+    "        wpos_ids = wpos_ids.flatten()\n",
+    "        pos_ids.append(torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))\n",
+    "        pos_ids = torch.cat(pos_ids, dim=0)\n",
+    "        max_grid_size = grid_thw.max()\n",
+    "        rotary_pos_emb_full = self._rotary_pos_emb(max_grid_size)\n",
+    "        rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)\n",
+    "        return rotary_pos_emb\n",
+    "\n",
+    "\n",
+    "\n",
+    "vision_rotary_embedding = RotaryEmbedding(config.vision_config.embed_dim // config.vision_config.num_heads // 2, config.vision_config.spatial_merge_size)\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/tmp/ipykernel_1830471/1989675311.py:15: TracerWarning: Iterating over a tensor might cause the trace to be incorrect. Passing a tensor of different shape won't change the number of iterations executed (and might lead to errors or silently give incorrect results).\n",
+      "  t, h, w = grid_thw\n"
+     ]
+    }
+   ],
+   "source": [
+    "import openvino as ov\n",
+    "\n",
+    "vision_embedding_ov = ov.convert_model(\n",
+    "    vision_rotary_embedding,\n",
+    "    example_input={\n",
+    "        \"grid_thw\": torch.tensor([1, 98, 146]),\n",
+    "    }\n",
+    ")\n",
+    "\n",
+    "# Save the OpenVINO model\n",
+    "ov.save_model(vision_embedding_ov, model_dir/\"openvino_rotary_embeddings_model.xml\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class MergeMultiModalInputs(torch.nn.Module):\n",
+    "    def __init__(self,image_token_index=151655):\n",
+    "        super().__init__()\n",
+    "        self.image_token_index = image_token_index\n",
+    "\n",
+    "    def forward(\n",
+    "        self,\n",
+    "        vision_embeds,\n",
+    "        inputs_embeds,\n",
+    "        input_ids,\n",
+    "    ):\n",
+    "        image_features = vision_embeds\n",
+    "        inputs_embeds = inputs_embeds\n",
+    "        special_image_mask = (input_ids == self.image_token_index).unsqueeze(-1).expand_as(inputs_embeds)\n",
+    "        # image_features = image_features.to(inputs_embeds.dtype)\n",
+    "        final_embedding = inputs_embeds.masked_scatter(special_image_mask, image_features)\n",
+    "\n",
+    "        return {\n",
+    "            \"inputs_embeds\": final_embedding\n",
+    "        }\n",
+    "\n",
+    "torch_model_merge = MergeMultiModalInputs()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import openvino as ov\n",
+    "\n",
+    "# convert MergeMultiModalInputs to OpenVINO IR\n",
+    "ov_model_merge = ov.convert_model(\n",
+    "    torch_model_merge,\n",
+    "    example_input={\n",
+    "        \"vision_embeds\": torch.randn((3577, 1536), dtype=torch.float32),\n",
+    "        \"inputs_embeds\": torch.randn((1, 3602, 1536), dtype=torch.float32),\n",
+    "        \"input_ids\": torch.randint(0, 151656, (1, 3602), dtype=torch.long),\n",
+    "    }\n",
+    ")\n",
+    "ov.save_model(ov_model_merge, model_dir/\"openvino_multimodal_merge_model.xml\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2 Load openvino models"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "LANGUAGE_MODEL_NAME = \"openvino_language_model.xml\"\n",
+    "IMAGE_EMBEDDING_NAME = \"openvino_vision_embeddings_model.xml\"\n",
+    "IMAGE_EMBEDDING_MERGER_NAME = \"openvino_vision_embeddings_merger_model.xml\"\n",
+    "TEXT_EMBEDDING_NAME = \"openvino_text_embeddings_model.xml\"\n",
+    "ROTARY_EMBEDDING_NAME = \"openvino_rotary_embeddings_model.xml\"\n",
+    "PATCH_RESHAPE_NAME = \"openvino_patch_reshape_model.xml\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import openvino as ov\n",
+    "import gc\n",
+    "\n",
+    "core = ov.Core()\n",
+    "model_path = model_dir\n",
+    "\n",
+    "language_model = core.read_model(model_path / LANGUAGE_MODEL_NAME)\n",
+    "compiled_language_model = core.compile_model(language_model, \"CPU\")\n",
+    "request = compiled_language_model.create_infer_request()\n",
+    "\n",
+    "image_embedding = core.compile_model(model_path / IMAGE_EMBEDDING_NAME, \"CPU\")\n",
+    "image_embedding_merger = core.compile_model(model_path / IMAGE_EMBEDDING_MERGER_NAME, \"CPU\")\n",
+    "text_embedding = core.compile_model(model_path / TEXT_EMBEDDING_NAME, \"CPU\")\n",
+    "rotary_embedding = core.compile_model(model_path / ROTARY_EMBEDDING_NAME, \"CPU\")\n",
+    "patch_reshape = core.compile_model(model_path / PATCH_RESHAPE_NAME, \"CPU\")\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "โŒ› Check if all models are converted\n",
+      "โœ… All models are converted. You can find results in test/Qwen2-VL-2B-Instruct\n"
+     ]
+    }
+   ],
+   "source": [
+    "# check if all the models are converted\n",
+    "\n",
+    "print(\"โŒ› Check if all models are converted\")\n",
+    "language_model_path = model_dir / LANGUAGE_MODEL_NAME\n",
+    "image_embed_path = model_dir / IMAGE_EMBEDDING_NAME\n",
+    "image_merger_path = model_dir / IMAGE_EMBEDDING_MERGER_NAME\n",
+    "text_embed_path = model_dir / TEXT_EMBEDDING_NAME\n",
+    "rotary_embed_path = model_dir / ROTARY_EMBEDDING_NAME\n",
+    "patch_reshape_path = model_dir / PATCH_RESHAPE_NAME\n",
+    "\n",
+    "\n",
+    "\n",
+    "\n",
+    "if all(\n",
+    "    [\n",
+    "        language_model_path.exists(),\n",
+    "        image_embed_path.exists(),\n",
+    "        image_merger_path.exists(),\n",
+    "        text_embed_path.exists(),\n",
+    "        rotary_embed_path.exists(),\n",
+    "        patch_reshape_path.exists(),\n",
+    "    ]\n",
+    "):\n",
+    "    print(f\"โœ… All models are converted. You can find results in {model_dir}\")\n",
+    "else:\n",
+    "    print(\"โŒ Not all models are converted. Please check the conversion process\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.2 Copy assets to the assets folder"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "assets_dir = model_dir / \"assets\"\n",
+    "assets_dir.mkdir(exist_ok=True)\n",
+    "\n",
+    "# copy all the assets to the assets directory (json files, vocab files, etc.)\n",
+    "\n",
+    "import shutil\n",
+    "\n",
+    "# copy all json files\n",
+    "\n",
+    "for file in model_dir.glob(\"*.json\"):\n",
+    "    shutil.copy(file, assets_dir)\n",
+    "\n",
+    "    \n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "total 1.7G\n",
+      "-rw-rw-r-- 1 prabod prabod  392 Dec 10 06:55 added_tokens.json\n",
+      "drwxrwxr-x 2 prabod prabod 4.0K Dec 10 06:59 assets\n",
+      "-rw-rw-r-- 1 prabod prabod 1.1K Dec 10 06:55 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.2K Dec 10 06:55 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.6M Dec 10 06:55 merges.txt\n",
+      "-rw-rw-r-- 1 prabod prabod 873M Dec 10 06:57 openvino_language_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 3.5M Dec 10 06:57 openvino_language_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod   40 Dec 10 06:58 openvino_multimodal_merge_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 9.8K Dec 10 06:58 openvino_multimodal_merge_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  132 Dec 10 06:58 openvino_patch_reshape_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod  24K Dec 10 06:58 openvino_patch_reshape_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  132 Dec 10 06:58 openvino_rotary_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod  30K Dec 10 06:58 openvino_rotary_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 446M Dec 10 06:55 openvino_text_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9K Dec 10 06:55 openvino_text_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 334M Dec 10 06:58 openvino_vision_embeddings_merger_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.1M Dec 10 06:58 openvino_vision_embeddings_merger_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9M Dec 10 06:57 openvino_vision_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 4.4K Dec 10 06:57 openvino_vision_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  567 Dec 10 06:55 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  613 Dec 10 06:55 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 4.3K Dec 10 06:55 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  11M Dec 10 06:55 tokenizer.json\n",
+      "-rw-rw-r-- 1 prabod prabod 2.7M Dec 10 06:55 vocab.json\n"
+     ]
+    }
+   ],
+   "source": [
+    "!ls -lh {model_dir}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "total 14M\n",
+      "-rw-rw-r-- 1 prabod prabod  392 Dec 10 07:05 added_tokens.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.1K Dec 10 07:05 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.2K Dec 10 07:05 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  567 Dec 10 07:05 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  613 Dec 10 07:05 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 4.3K Dec 10 07:05 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  11M Dec 10 07:05 tokenizer.json\n",
+      "-rw-rw-r-- 1 prabod prabod 2.7M Dec 10 07:05 vocab.json\n"
+     ]
+    }
+   ],
+   "source": [
+    "!ls -lh {assets_dir}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### 1.3 Test the openvino model"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import openvino as ov\n",
+    "import torch\n",
+    "from pathlib import Path\n",
+    "core = ov.Core()\n",
+    "device = \"CPU\"\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\n",
+    "model_path = Path(\"/mnt/research/Projects/ModelZoo/QWEN2-VL/test/Qwen2-VL-2B-Instruct\")\n",
+    "\n",
+    "language_model = core.read_model(model_path / LANGUAGE_MODEL_NAME)\n",
+    "compiled_language_model = core.compile_model(language_model, \"CPU\")\n",
+    "request = compiled_language_model.create_infer_request()\n",
+    "\n",
+    "image_embedding = core.compile_model(model_path / IMAGE_EMBEDDING_NAME, \"CPU\")\n",
+    "image_embedding_merger = core.compile_model(model_path / IMAGE_EMBEDDING_MERGER_NAME, \"CPU\")\n",
+    "text_embedding = core.compile_model(model_path / TEXT_EMBEDDING_NAME, \"CPU\")\n",
+    "rotary_embedding = core.compile_model(model_path / ROTARY_EMBEDDING_NAME, \"CPU\")\n",
+    "patch_reshape = core.compile_model(model_path / PATCH_RESHAPE_NAME, \"CPU\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "The argument `trust_remote_code` is to be used with Auto classes. It has no effect here and is ignored.\n"
+     ]
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "56e7f99e52234dbc9d2f7cb0306cd668",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Loading checkpoint shards:   0%|          | 0/2 [00:00 1:\n",
+    "        hidden_states = torch.from_numpy(image_embedding(pixel_values)[0])\n",
+    "        rotary_pos_emb = torch.cat([torch.from_numpy(rotary_embedding(x)[0]) for x in image_grid_thw], dim=0)\n",
+    "        grid_thw = image_grid_thw\n",
+    "        cu_seqlens = torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2], grid_thw[:, 0]).cumsum(dim=0, dtype=torch.int32)\n",
+    "        cu_seqlens = torch.nn.functional.pad(cu_seqlens, (1, 0), value=0)\n",
+    "        attention_mask = torch.zeros((1, hidden_states.shape[0], hidden_states.shape[0]), dtype=torch.bool)\n",
+    "        causal_mask = torch.zeros_like(attention_mask, dtype=torch.float32)\n",
+    "        for i in range(1, len(cu_seqlens)):\n",
+    "            attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = True\n",
+    "\n",
+    "        causal_mask.masked_fill_(torch.logical_not(attention_mask), float(\"-inf\"))\n",
+    "\n",
+    "        image_embeds = torch.from_numpy(image_embedding_merger(\n",
+    "            {\n",
+    "                \"hidden_states\": hidden_states,\n",
+    "                \"rotary_pos_emb\": rotary_pos_emb,\n",
+    "                \"attention_mask\": attention_mask,\n",
+    "            }\n",
+    "        )[0])\n",
+    "        image_mask = input_ids == config.image_token_id\n",
+    "        inputs_embeds[image_mask] = image_embeds\n",
+    "    # break\n",
+    "    if i>0:\n",
+    "        inputs = {}\n",
+    "\n",
+    "    if current_input_ids.shape[-1] > 1:\n",
+    "        attention_mask = inputs_new[\"attention_mask\"]\n",
+    "        position_ids = torch.arange(current_input_ids.shape[1], device=current_input_ids.device).view(1, 1, -1).expand(3, current_input_ids.shape[0], -1)\n",
+    "    \n",
+    "    # Prepare inputs for the model\n",
+    "    inputs[\"inputs_embeds\"] = inputs_embeds\n",
+    "    inputs[\"attention_mask\"] = attention_mask\n",
+    "    inputs[\"position_ids\"] = position_ids\n",
+    "    if \"beam_idx\" in input_names:\n",
+    "        inputs[\"beam_idx\"] = np.arange(inputs_embeds.shape[0], dtype=int)\n",
+    "    \n",
+    "    # Start inference\n",
+    "    request.start_async(inputs, share_inputs=True)\n",
+    "    request.wait()\n",
+    "    \n",
+    "    # Get the logits and find the next token\n",
+    "    logits = torch.from_numpy(request.get_tensor(\"logits\").data)\n",
+    "    next_token = logits.argmax(-1)[0][-1]\n",
+    "\n",
+    "    # Append the generated token\n",
+    "    generated_tokens.append(next_token)\n",
+    "    \n",
+    "    # Update input_ids with the new token\n",
+    "    current_input_ids = torch.cat([next_token.unsqueeze(0).unsqueeze(0)], dim=-1)\n",
+    "    \n",
+    "    position_ids = torch.tensor(inputs_new[\"input_ids\"].shape[-1] + i).view(1, 1, -1).expand(3, current_input_ids.shape[0], -1)\n",
+    "\n",
+    "    inputs[\"position_ids\"] = position_ids\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Question:\n",
+      " Describe this Image\n",
+      "Answer:\n",
+      "The Bennett is sitting on the beach, wearing a plaid shirt and black pants. She is smiling and appears to be enjoying her time outdoors. A dog is sitting next to her, wearing a harness and leash. The beach is sandy and the sky\n"
+     ]
+    }
+   ],
+   "source": [
+    "output_text = processor.batch_decode(\n",
+    "    generated_tokens, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
+    ")\n",
+    "\n",
+    "print(\"Question:\\n Describe this Image\")\n",
+    "print(\"Answer:\")\n",
+    "print(\"\".join(output_text))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## 2. Import and Save Qwen2VL in Spark NLP\n",
+    "\n",
+    "- Let's install and setup Spark NLP in Google Colab\n",
+    "- This part is pretty easy via our simple script"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's start Spark with Spark NLP included via our simple `start()` function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "24/11/07 09:56:55 WARN Utils: Your hostname, minotaur resolves to a loopback address: 127.0.1.1; using 192.168.1.4 instead (on interface eno1)\n",
+      "24/11/07 09:56:55 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n",
+      "24/11/07 09:56:55 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "Setting default log level to \"WARN\".\n",
+      "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n"
+     ]
+    }
+   ],
+   "source": [
+    "import sparknlp\n",
+    "\n",
+    "# let's start Spark with Spark NLP\n",
+    "spark = sparknlp.start()\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "24/11/07 09:57:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native15331424460843812197/libtbb.so.2\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "WARNING: An illegal reflective access operation has occurred\n",
+      "WARNING: Illegal reflective access by org.apache.spark.util.SizeEstimator$ (file:/home/prabod/spark/jars/spark-core_2.12-3.3.2.jar) to field java.util.regex.Pattern.pattern\n",
+      "WARNING: Please consider reporting this to the maintainers of org.apache.spark.util.SizeEstimator$\n",
+      "WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n",
+      "WARNING: All illegal access operations will be denied in a future release\n"
+     ]
+    }
+   ],
+   "source": [
+    "imageClassifier = Qwen2VLForMultiModal.pretrained() \\\n",
+    "            .setInputCols(\"image_assembler\") \\\n",
+    "            .setOutputCol(\"answer\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "imageClassifier.write().overwrite().save(\"Qwen2VL_spark_nlp\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sparknlp\n",
+    "from sparknlp.base import *\n",
+    "from sparknlp.annotator import *\n",
+    "from pyspark.sql.functions import lit\n",
+    "from pyspark.ml import Pipeline\n",
+    "from pathlib import Path\n",
+    "import os\n",
+    "\n",
+    "# download two images to test into ./images folder\n",
+    "\n",
+    "url1 = \"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\"\n",
+    "url2 = \"http://images.cocodataset.org/val2017/000000039769.jpg\"\n",
+    "\n",
+    "Path(\"images\").mkdir(exist_ok=True)\n",
+    "\n",
+    "!wget -q -O images/image1.jpg {url1}\n",
+    "!wget -q -O images/image2.jpg {url2}\n",
+    "\n",
+    "\n",
+    "\n",
+    "images_path = \"file://\" + os.getcwd() + \"/images/\"\n",
+    "image_df = spark.read.format(\"image\").load(\n",
+    "    path=images_path\n",
+    ")\n",
+    "\n",
+    "test_df = image_df.withColumn(\"text\", lit(\"<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n<|im_start|>user\\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\\n<|im_start|>assistant\\n\"))\n",
+    "\n",
+    "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n",
+    "\n",
+    "imageClassifier = Qwen2VLForMultiModal.load(\"Qwen2VL_spark_nlp\")\\\n",
+    "            .setMaxOutputLength(50) \\\n",
+    "            .setInputCols(\"image_assembler\") \\\n",
+    "            .setOutputCol(\"answer\")\n",
+    "\n",
+    "pipeline = Pipeline(\n",
+    "            stages=[\n",
+    "                image_assembler,\n",
+    "                imageClassifier,\n",
+    "            ]\n",
+    "        )\n",
+    "\n",
+    "model = pipeline.fit(test_df)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "image_path: /mnt/research/Projects/ModelZoo/LLAVA/images/image1.jpg\n",
+      "[Annotation(document, 0, 363, This image features a cat comfortably laying inside a cardboard box. The cat appears to be relaxed and enjoying its cozy spot. The scene takes place on a carpeted floor, which adds to the overall warm and inviting atmosphere of the image. The cat's position inside the box creates a sense of security and contentment, making it an endearing and heartwarming scene., Map(), [])]\n"
+     ]
+    }
+   ],
+   "source": [
+    "light_pipeline = LightPipeline(model)\n",
+    "image_path = os.getcwd() + \"/images/\" + \"image1.jpg\"\n",
+    "print(\"image_path: \" + image_path)\n",
+    "annotations_result = light_pipeline.fullAnnotateImage(\n",
+    "    image_path,\n",
+    "    \"<|im_start|>system\\nYou are a helpful assistant.<|im_end|>\\n<|im_start|>user\\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\\n<|im_start|>assistant\\n\"\n",
+    ")\n",
+    "\n",
+    "for result in annotations_result:\n",
+    "    print(result[\"answer\"])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "pth23",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.19"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

From 934af9098b96666c51bef76f1d2bb17bc103f515 Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Fri, 14 Feb 2025 01:01:53 +0000
Subject: [PATCH 057/108] update default_model and resource downloader entry

Signed-off-by: Prabod Rathnayaka 
---
 ...ngFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb | 475 ++++++------------
 .../annotator/cv/qwen2vl_transformer.py       |   8 +-
 .../annotators/cv/Qwen2VLTransformer.scala    |  15 +-
 .../nlp/pretrained/ResourceDownloader.scala   |   3 +-
 4 files changed, 163 insertions(+), 338 deletions(-)

diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb
index 8a5aa6277b11b2..38e005dbd7d78e 100644
--- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb
+++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Qwen2VL.ipynb
@@ -38,27 +38,32 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 24,
    "metadata": {},
-   "outputs": [],
-   "source": [
-    "from pathlib import Path\n",
-    "import requests"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n",
+      "Note: you may need to restart the kernel to use updated packages.\n"
+     ]
+    }
+   ],
    "source": [
     "\n",
     "%pip install -qU \"openvino>=2024.4.0\" \"nncf>=2.13.0\"\n",
-    "%pip install -q  \"sentencepiece\" \"tokenizers>=0.12.1\" \"transformers>=4.45.0\" \"gradio>=4.36\"\n",
+    "%pip install -q  \"sentencepiece\" \"tokenizers>=0.12.1\" \"transformers>=4.45.0\" \"gradio>=4.36\" \"accelerate>=0.26.0\"\n",
     "%pip install -q -U --pre --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly openvino-tokenizers openvino openvino-genai\n",
     "%pip install -q --upgrade huggingface_hub\n",
-    "%pip install -q --upgrade torch>=2.2.1\n",
+    "%pip install -q --upgrade torch>=2.2.1 torchvision>=0.10.2\n",
     "%pip install -q --upgrade qwen-vl-utils\n",
+    "%pip install -q --upgrade ipywidgets\n",
     "\n",
     "utility_files = [\"notebook_utils.py\", \"cmd_helper.py\"]\n",
     "\n",
@@ -83,20 +88,13 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "INFO:nncf:NNCF initialized successfully. Supported frameworks detected: torch, onnx, openvino\n"
-     ]
-    },
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "4659f1c77b1b4fc28b2869cbed0ea309",
+       "model_id": "b6dd00586e2b4cc1bf3fd2e7cd80f072",
        "version_major": 2,
        "version_minor": 0
       },
@@ -104,13 +102,15 @@
        "Dropdown(description='Model:', options=('Qwen/Qwen2-VL-2B-Instruct', 'Qwen/Qwen2-VL-7B-Instruct'), value='Qwenโ€ฆ"
       ]
      },
-     "execution_count": 2,
+     "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
     "from ov_qwen2_vl import model_selector\n",
+    "from pathlib import Path\n",
+    "import requests\n",
     "\n",
     "model_id = model_selector()\n",
     "\n",
@@ -119,7 +119,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
@@ -138,16 +138,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "PosixPath('test/Qwen2-VL-2B-Instruct')"
+       "PosixPath('Qwen2-VL-2B-Instruct')"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -158,7 +158,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
@@ -169,6 +169,20 @@
       "โŒ› Load Original model\n"
      ]
     },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "5c1440417023424ebcdac61adf7a04bb",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Downloading shards:   0%|          | 0/2 [00:00 target_length:\n",
-      "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/cache_utils.py:444: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n",
+      "/home/prabod/anaconda3/envs/qwen2vl/lib/python3.9/site-packages/transformers/cache_utils.py:444: TracerWarning: Using len to get tensor shape might cause the trace to be incorrect. Recommended usage would be tensor.shape[0]. Passing a tensor of different shape might lead to errors or silently give incorrect results.\n",
       "  len(self.key_cache[layer_idx]) == 0\n"
      ]
     },
@@ -249,7 +249,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "b4a917cc8701479f8a37567a90cb7f54",
+       "model_id": "7bf356fb03094dea88c213baa5f17ce1",
        "version_major": 2,
        "version_minor": 0
       },
@@ -290,7 +290,7 @@
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "041352b5ffce4e2886add167de6ca1ad",
+       "model_id": "eed1fe0109374336afee2590bd8ee7be",
        "version_major": 2,
        "version_minor": 0
       },
@@ -317,7 +317,7 @@
      "text": [
       "โœ… Weights compression finished\n",
       "โœ… Image embedding model successfully converted\n",
-      "โœ… Qwen/Qwen2-VL-2B-Instruct model conversion finished. You can find results in test/Qwen2-VL-2B-Instruct\n"
+      "โœ… Qwen/Qwen2-VL-2B-Instruct model conversion finished. You can find results in Qwen2-VL-2B-Instruct\n"
      ]
     }
    ],
@@ -336,7 +336,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -386,7 +386,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 8,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -407,7 +407,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 9,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -462,14 +462,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 10,
    "metadata": {},
    "outputs": [
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "/tmp/ipykernel_1830471/1989675311.py:15: TracerWarning: Iterating over a tensor might cause the trace to be incorrect. Passing a tensor of different shape won't change the number of iterations executed (and might lead to errors or silently give incorrect results).\n",
+      "/tmp/ipykernel_33347/1989675311.py:15: TracerWarning: Iterating over a tensor might cause the trace to be incorrect. Passing a tensor of different shape won't change the number of iterations executed (and might lead to errors or silently give incorrect results).\n",
       "  t, h, w = grid_thw\n"
      ]
     }
@@ -490,7 +490,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 11,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -520,7 +520,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 12,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -547,7 +547,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -561,7 +561,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 14,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -592,7 +592,7 @@
      "output_type": "stream",
      "text": [
       "โŒ› Check if all models are converted\n",
-      "โœ… All models are converted. You can find results in test/Qwen2-VL-2B-Instruct\n"
+      "โœ… All models are converted. You can find results in Qwen2-VL-2B-Instruct\n"
      ]
     }
    ],
@@ -663,30 +663,30 @@
      "output_type": "stream",
      "text": [
       "total 1.7G\n",
-      "-rw-rw-r-- 1 prabod prabod  392 Dec 10 06:55 added_tokens.json\n",
-      "drwxrwxr-x 2 prabod prabod 4.0K Dec 10 06:59 assets\n",
-      "-rw-rw-r-- 1 prabod prabod 1.1K Dec 10 06:55 chat_template.json\n",
-      "-rw-rw-r-- 1 prabod prabod 1.2K Dec 10 06:55 config.json\n",
-      "-rw-rw-r-- 1 prabod prabod 1.6M Dec 10 06:55 merges.txt\n",
-      "-rw-rw-r-- 1 prabod prabod 873M Dec 10 06:57 openvino_language_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 3.5M Dec 10 06:57 openvino_language_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod   40 Dec 10 06:58 openvino_multimodal_merge_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 9.8K Dec 10 06:58 openvino_multimodal_merge_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod  132 Dec 10 06:58 openvino_patch_reshape_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod  24K Dec 10 06:58 openvino_patch_reshape_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod  132 Dec 10 06:58 openvino_rotary_embeddings_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod  30K Dec 10 06:58 openvino_rotary_embeddings_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod 446M Dec 10 06:55 openvino_text_embeddings_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 2.9K Dec 10 06:55 openvino_text_embeddings_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod 334M Dec 10 06:58 openvino_vision_embeddings_merger_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 2.1M Dec 10 06:58 openvino_vision_embeddings_merger_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod 2.9M Dec 10 06:57 openvino_vision_embeddings_model.bin\n",
-      "-rw-rw-r-- 1 prabod prabod 4.4K Dec 10 06:57 openvino_vision_embeddings_model.xml\n",
-      "-rw-rw-r-- 1 prabod prabod  567 Dec 10 06:55 preprocessor_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  613 Dec 10 06:55 special_tokens_map.json\n",
-      "-rw-rw-r-- 1 prabod prabod 4.3K Dec 10 06:55 tokenizer_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  11M Dec 10 06:55 tokenizer.json\n",
-      "-rw-rw-r-- 1 prabod prabod 2.7M Dec 10 06:55 vocab.json\n"
+      "-rw-rw-r-- 1 prabod prabod  392 Feb 13 22:58 added_tokens.json\n",
+      "drwxrwxr-x 2 prabod prabod 4.0K Feb 13 23:03 assets\n",
+      "-rw-rw-r-- 1 prabod prabod 1.1K Feb 13 22:58 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.2K Feb 13 22:58 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.6M Feb 13 22:58 merges.txt\n",
+      "-rw-rw-r-- 1 prabod prabod 873M Feb 13 23:00 openvino_language_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 3.4M Feb 13 23:00 openvino_language_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod   40 Feb 13 23:01 openvino_multimodal_merge_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 9.8K Feb 13 23:01 openvino_multimodal_merge_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  132 Feb 13 23:00 openvino_patch_reshape_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod  24K Feb 13 23:00 openvino_patch_reshape_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  132 Feb 13 23:00 openvino_rotary_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod  30K Feb 13 23:00 openvino_rotary_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 446M Feb 13 22:58 openvino_text_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9K Feb 13 22:58 openvino_text_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 334M Feb 13 23:00 openvino_vision_embeddings_merger_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 2.1M Feb 13 23:00 openvino_vision_embeddings_merger_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod 2.9M Feb 13 23:00 openvino_vision_embeddings_model.bin\n",
+      "-rw-rw-r-- 1 prabod prabod 4.4K Feb 13 23:00 openvino_vision_embeddings_model.xml\n",
+      "-rw-rw-r-- 1 prabod prabod  567 Feb 13 22:58 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  613 Feb 13 22:58 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 4.3K Feb 13 22:58 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  11M Feb 13 22:58 tokenizer.json\n",
+      "-rw-rw-r-- 1 prabod prabod 2.7M Feb 13 22:58 vocab.json\n"
      ]
     }
    ],
@@ -704,14 +704,14 @@
      "output_type": "stream",
      "text": [
       "total 14M\n",
-      "-rw-rw-r-- 1 prabod prabod  392 Dec 10 07:05 added_tokens.json\n",
-      "-rw-rw-r-- 1 prabod prabod 1.1K Dec 10 07:05 chat_template.json\n",
-      "-rw-rw-r-- 1 prabod prabod 1.2K Dec 10 07:05 config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  567 Dec 10 07:05 preprocessor_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  613 Dec 10 07:05 special_tokens_map.json\n",
-      "-rw-rw-r-- 1 prabod prabod 4.3K Dec 10 07:05 tokenizer_config.json\n",
-      "-rw-rw-r-- 1 prabod prabod  11M Dec 10 07:05 tokenizer.json\n",
-      "-rw-rw-r-- 1 prabod prabod 2.7M Dec 10 07:05 vocab.json\n"
+      "-rw-rw-r-- 1 prabod prabod  392 Feb 13 23:03 added_tokens.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.1K Feb 13 23:03 chat_template.json\n",
+      "-rw-rw-r-- 1 prabod prabod 1.2K Feb 13 23:03 config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  567 Feb 13 23:03 preprocessor_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  613 Feb 13 23:03 special_tokens_map.json\n",
+      "-rw-rw-r-- 1 prabod prabod 4.3K Feb 13 23:03 tokenizer_config.json\n",
+      "-rw-rw-r-- 1 prabod prabod  11M Feb 13 23:03 tokenizer.json\n",
+      "-rw-rw-r-- 1 prabod prabod 2.7M Feb 13 23:03 vocab.json\n"
      ]
     }
    ],
@@ -719,225 +719,6 @@
     "!ls -lh {assets_dir}"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### 1.3 Test the openvino model"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 5,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "import openvino as ov\n",
-    "import torch\n",
-    "from pathlib import Path\n",
-    "core = ov.Core()\n",
-    "device = \"CPU\"\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "\n",
-    "model_path = Path(\"/mnt/research/Projects/ModelZoo/QWEN2-VL/test/Qwen2-VL-2B-Instruct\")\n",
-    "\n",
-    "language_model = core.read_model(model_path / LANGUAGE_MODEL_NAME)\n",
-    "compiled_language_model = core.compile_model(language_model, \"CPU\")\n",
-    "request = compiled_language_model.create_infer_request()\n",
-    "\n",
-    "image_embedding = core.compile_model(model_path / IMAGE_EMBEDDING_NAME, \"CPU\")\n",
-    "image_embedding_merger = core.compile_model(model_path / IMAGE_EMBEDDING_MERGER_NAME, \"CPU\")\n",
-    "text_embedding = core.compile_model(model_path / TEXT_EMBEDDING_NAME, \"CPU\")\n",
-    "rotary_embedding = core.compile_model(model_path / ROTARY_EMBEDDING_NAME, \"CPU\")\n",
-    "patch_reshape = core.compile_model(model_path / PATCH_RESHAPE_NAME, \"CPU\")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 9,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "The argument `trust_remote_code` is to be used with Auto classes. It has no effect here and is ignored.\n"
-     ]
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "56e7f99e52234dbc9d2f7cb0306cd668",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Loading checkpoint shards:   0%|          | 0/2 [00:00 1:\n",
-    "        hidden_states = torch.from_numpy(image_embedding(pixel_values)[0])\n",
-    "        rotary_pos_emb = torch.cat([torch.from_numpy(rotary_embedding(x)[0]) for x in image_grid_thw], dim=0)\n",
-    "        grid_thw = image_grid_thw\n",
-    "        cu_seqlens = torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2], grid_thw[:, 0]).cumsum(dim=0, dtype=torch.int32)\n",
-    "        cu_seqlens = torch.nn.functional.pad(cu_seqlens, (1, 0), value=0)\n",
-    "        attention_mask = torch.zeros((1, hidden_states.shape[0], hidden_states.shape[0]), dtype=torch.bool)\n",
-    "        causal_mask = torch.zeros_like(attention_mask, dtype=torch.float32)\n",
-    "        for i in range(1, len(cu_seqlens)):\n",
-    "            attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = True\n",
-    "\n",
-    "        causal_mask.masked_fill_(torch.logical_not(attention_mask), float(\"-inf\"))\n",
-    "\n",
-    "        image_embeds = torch.from_numpy(image_embedding_merger(\n",
-    "            {\n",
-    "                \"hidden_states\": hidden_states,\n",
-    "                \"rotary_pos_emb\": rotary_pos_emb,\n",
-    "                \"attention_mask\": attention_mask,\n",
-    "            }\n",
-    "        )[0])\n",
-    "        image_mask = input_ids == config.image_token_id\n",
-    "        inputs_embeds[image_mask] = image_embeds\n",
-    "    # break\n",
-    "    if i>0:\n",
-    "        inputs = {}\n",
-    "\n",
-    "    if current_input_ids.shape[-1] > 1:\n",
-    "        attention_mask = inputs_new[\"attention_mask\"]\n",
-    "        position_ids = torch.arange(current_input_ids.shape[1], device=current_input_ids.device).view(1, 1, -1).expand(3, current_input_ids.shape[0], -1)\n",
-    "    \n",
-    "    # Prepare inputs for the model\n",
-    "    inputs[\"inputs_embeds\"] = inputs_embeds\n",
-    "    inputs[\"attention_mask\"] = attention_mask\n",
-    "    inputs[\"position_ids\"] = position_ids\n",
-    "    if \"beam_idx\" in input_names:\n",
-    "        inputs[\"beam_idx\"] = np.arange(inputs_embeds.shape[0], dtype=int)\n",
-    "    \n",
-    "    # Start inference\n",
-    "    request.start_async(inputs, share_inputs=True)\n",
-    "    request.wait()\n",
-    "    \n",
-    "    # Get the logits and find the next token\n",
-    "    logits = torch.from_numpy(request.get_tensor(\"logits\").data)\n",
-    "    next_token = logits.argmax(-1)[0][-1]\n",
-    "\n",
-    "    # Append the generated token\n",
-    "    generated_tokens.append(next_token)\n",
-    "    \n",
-    "    # Update input_ids with the new token\n",
-    "    current_input_ids = torch.cat([next_token.unsqueeze(0).unsqueeze(0)], dim=-1)\n",
-    "    \n",
-    "    position_ids = torch.tensor(inputs_new[\"input_ids\"].shape[-1] + i).view(1, 1, -1).expand(3, current_input_ids.shape[0], -1)\n",
-    "\n",
-    "    inputs[\"position_ids\"] = position_ids\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 10,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Question:\n",
-      " Describe this Image\n",
-      "Answer:\n",
-      "The Bennett is sitting on the beach, wearing a plaid shirt and black pants. She is smiling and appears to be enjoying her time outdoors. A dog is sitting next to her, wearing a harness and leash. The beach is sandy and the sky\n"
-     ]
-    }
-   ],
-   "source": [
-    "output_text = processor.batch_decode(\n",
-    "    generated_tokens, skip_special_tokens=True, clean_up_tokenization_spaces=False\n",
-    ")\n",
-    "\n",
-    "print(\"Question:\\n Describe this Image\")\n",
-    "print(\"Answer:\")\n",
-    "print(\"\".join(output_text))"
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -996,14 +777,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "24/11/07 09:57:34 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native15331424460843812197/libtbb.so.2\n"
+      "25/02/14 00:53:12 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native16473116188009294604/libtbb.so.2\n"
      ]
     },
     {
@@ -1019,7 +800,7 @@
     }
    ],
    "source": [
-    "imageClassifier = Qwen2VLForMultiModal.pretrained() \\\n",
+    "imageClassifier = Qwen2VLTransformer.loadSavedModel(str(model_path),spark) \\\n",
     "            .setInputCols(\"image_assembler\") \\\n",
     "            .setOutputCol(\"answer\")"
    ]
@@ -1028,11 +809,54 @@
    "cell_type": "code",
    "execution_count": null,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "                                                                                \r"
+     ]
+    }
+   ],
    "source": [
     "imageClassifier.write().overwrite().save(\"Qwen2VL_spark_nlp\")"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "total 1.7G\n",
+      "drwxr-xr-x  4 prabod prabod 4.0K Feb 14 00:53 .\n",
+      "drwxr-xr-x 12 prabod root   4.0K Feb 14 00:53 ..\n",
+      "drwxr-xr-x  6 prabod prabod 4.0K Feb 14 00:53 fields\n",
+      "drwxr-xr-x  2 prabod prabod 4.0K Feb 14 00:53 metadata\n",
+      "-rw-r--r--  1 prabod prabod 876M Feb 14 00:53 openvino_language_model.xml\n",
+      "-rw-r--r--  1 prabod prabod 6.9M Feb 14 00:53 .openvino_language_model.xml.crc\n",
+      "-rw-r--r--  1 prabod prabod  11K Feb 14 00:53 openvino_multimodal_merge_model.xml\n",
+      "-rw-r--r--  1 prabod prabod   92 Feb 14 00:53 .openvino_multimodal_merge_model.xml.crc\n",
+      "-rw-r--r--  1 prabod prabod  24K Feb 14 00:53 openvino_patch_reshape_model.xml\n",
+      "-rw-r--r--  1 prabod prabod  200 Feb 14 00:53 .openvino_patch_reshape_model.xml.crc\n",
+      "-rw-r--r--  1 prabod prabod  30K Feb 14 00:53 openvino_rotary_embeddings_model.xml\n",
+      "-rw-r--r--  1 prabod prabod  248 Feb 14 00:53 .openvino_rotary_embeddings_model.xml.crc\n",
+      "-rw-r--r--  1 prabod prabod 446M Feb 14 00:53 openvino_text_embeddings_model.xml\n",
+      "-rw-r--r--  1 prabod prabod 3.5M Feb 14 00:53 .openvino_text_embeddings_model.xml.crc\n",
+      "-rw-r--r--  1 prabod prabod 336M Feb 14 00:53 openvino_vision_embeddings_merger_model.xml\n",
+      "-rw-r--r--  1 prabod prabod 2.7M Feb 14 00:53 .openvino_vision_embeddings_merger_model.xml.crc\n",
+      "-rw-r--r--  1 prabod prabod 2.9M Feb 14 00:53 openvino_vision_embeddings_model.xml\n",
+      "-rw-r--r--  1 prabod prabod  24K Feb 14 00:53 .openvino_vision_embeddings_model.xml.crc\n"
+     ]
+    }
+   ],
+   "source": [
+    "!ls -lah Qwen2VL_spark_nlp"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -1068,7 +892,7 @@
     "\n",
     "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n",
     "\n",
-    "imageClassifier = Qwen2VLForMultiModal.load(\"Qwen2VL_spark_nlp\")\\\n",
+    "imageClassifier = Qwen2VLTransformer.load(\"Qwen2VL_spark_nlp\")\\\n",
     "            .setMaxOutputLength(50) \\\n",
     "            .setInputCols(\"image_assembler\") \\\n",
     "            .setOutputCol(\"answer\")\n",
@@ -1085,15 +909,15 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "image_path: /mnt/research/Projects/ModelZoo/LLAVA/images/image1.jpg\n",
-      "[Annotation(document, 0, 363, This image features a cat comfortably laying inside a cardboard box. The cat appears to be relaxed and enjoying its cozy spot. The scene takes place on a carpeted floor, which adds to the overall warm and inviting atmosphere of the image. The cat's position inside the box creates a sense of security and contentment, making it an endearing and heartwarming scene., Map(), [])]\n"
+      "image_path: /home/prabod/Projects/spark-nlp/examples/python/transformers/openvino/images/image1.jpg\n",
+      "[Annotation(document, 0, 245, The image shows a cat lying inside a cardboard box. The cat appears to be relaxed and comfortable, with its eyes closed, suggesting it is resting or sleeping. The box is placed on a light-colored carpet, and the background includes a portion of a, Map(), [])]\n"
      ]
     }
    ],
@@ -1109,13 +933,6 @@
     "for result in annotations_result:\n",
     "    print(result[\"answer\"])"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {
diff --git a/python/sparknlp/annotator/cv/qwen2vl_transformer.py b/python/sparknlp/annotator/cv/qwen2vl_transformer.py
index 06d4a5644c5678..a0dfc52a8f1706 100644
--- a/python/sparknlp/annotator/cv/qwen2vl_transformer.py
+++ b/python/sparknlp/annotator/cv/qwen2vl_transformer.py
@@ -33,7 +33,7 @@ class Qwen2VLTransformer(AnnotatorModel,
     ...     .setInputCols(["image_assembler"]) \\
     ...     .setOutputCol("answer")
 
-    The default model is ``"Qwen/Qwen2-VL-7B-Instruct"``, if no name is provided.
+    The default model is ``"qwen2_vl_2b_instruct_int4"``, if no name is provided.
 
     For available pretrained models, please see the `Models Hub
     `__.
@@ -309,14 +309,14 @@ def loadSavedModel(folder, spark_session, use_openvino=False):
         return Qwen2VLTransformer(java_model=jModel)
 
     @staticmethod
-    def pretrained(name="phi3v", lang="en", remote_loc=None):
+    def pretrained(name="qwen2_vl_2b_instruct_int4", lang="en", remote_loc=None):
         """Downloads and loads a pretrained model.
 
         Parameters
         ----------
         name : str, optional
             Name of the pretrained model, by default
-            "phi3v"
+            "qwen2_vl_2b_instruct_int4"
         lang : str, optional
             Language of the pretrained model, by default "en"
         remote_loc : str, optional
@@ -325,7 +325,7 @@ def pretrained(name="phi3v", lang="en", remote_loc=None):
 
         Returns
         -------
-        CLIPForZeroShotClassification
+        Qwen2VLTransformer
             The restored model
         """
         from sparknlp.pretrained import ResourceDownloader
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala
index 714615ac7ceadf..5adefec3d6feee 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala
@@ -51,7 +51,7 @@ import org.apache.spark.sql.SparkSession
   *   .setInputCols("image_assembler")
   *   .setOutputCol("answer")
   * }}}
-  * The default model is `"Qwen/Qwen2-VL-@B-Instruct"`, if no name is provided.
+  * The default model is `"qwen2_vl_2b_instruct_int4"`, if no name is provided.
   *
   * For available pretrained models, please see the
   * [[https://sparknlp.org/models?task=Question+Answering Models Hub]].
@@ -382,6 +382,13 @@ class Qwen2VLTransformer(override val uid: String)
           spark,
           Seq((wrappers.get.multimodalMergeModel, "openvino_multimodal_merge_model.xml")),
           Qwen2VLTransformer.suffix)
+
+        writeOpenvinoModels(
+          path,
+          spark,
+          Seq((wrappers.get.rotaryEmbedding, "openvino_rotary_embeddings_model.xml")),
+          Qwen2VLTransformer.suffix)
+
       case _ =>
         throw new Exception(notSupportedEngineError)
     }
@@ -393,7 +400,7 @@ trait ReadablePretrainedQwen2VLTransformer
     extends ParamsAndFeaturesReadable[Qwen2VLTransformer]
     with HasPretrained[Qwen2VLTransformer] {
 
-  override val defaultModelName: Some[String] = Some("phi3v")
+  override val defaultModelName: Some[String] = Some("qwen2_vl_2b_instruct_int4")
 
   /** Java compliant-overrides */
   override def pretrained(): Qwen2VLTransformer = super.pretrained()
@@ -411,8 +418,8 @@ trait ReadablePretrainedQwen2VLTransformer
 
 trait ReadQwen2VLTransformerDLModel extends ReadOpenvinoModel {
   this: ParamsAndFeaturesReadable[Qwen2VLTransformer] =>
-  val suffix: String = "_phi3v"
-  override val openvinoFile: String = "phi3v_openvino"
+  val suffix: String = "_qwen2vl"
+  override val openvinoFile: String = "qwen2vl_openvino"
   def readModel(instance: Qwen2VLTransformer, path: String, spark: SparkSession): Unit = {
     instance.getEngine match {
       // LANGUAGE_MODEL_NAME = "openvino_language_model.xml"
diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala
index 0e457d4d6e20df..3709bad69bf471 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala
@@ -697,7 +697,8 @@ object PythonResourceDownloader {
     "NLLBTransformer" -> NLLBTransformer,
     "Phi3Transformer" -> Phi3Transformer,
     "QwenTransformer" -> QwenTransformer,
-    "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings)
+    "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings,
+    "Qwen2VLTransformer" -> Qwen2VLTransformer)
 
   // List pairs of types such as the one with key type can load a pretrained model from the value type
   val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering")

From d19b9f7ab397f565e4203d037a4673d3e4020e4b Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Fri, 14 Feb 2025 01:14:24 +0000
Subject: [PATCH 058/108] update documentation

Signed-off-by: Prabod Rathnayaka 
---
 .../transformer_entries/Qwen2VLTransformer.md | 111 ++++++++++++++++++
 1 file changed, 111 insertions(+)
 create mode 100644 docs/en/transformer_entries/Qwen2VLTransformer.md

diff --git a/docs/en/transformer_entries/Qwen2VLTransformer.md b/docs/en/transformer_entries/Qwen2VLTransformer.md
new file mode 100644
index 00000000000000..dd1f7df83ef28c
--- /dev/null
+++ b/docs/en/transformer_entries/Qwen2VLTransformer.md
@@ -0,0 +1,111 @@
+{%- capture title -%}
+Qwen2VLTransformer
+{%- endcapture -%}
+
+{%- capture description -%}
+Visual Question Answering and Multimodal Instruction Following using Qwen2-VL.
+
+Qwen2VLTransformer can load Qwen2 Vision-Language models for visual question answering and
+multimodal instruction following. The model consists of a vision encoder, a text encoder, and
+a text decoder. The vision encoder processes the input image, the text encoder integrates
+the encoding of the image with the input text, and the text decoder outputs the response to
+the query or instruction.
+
+Pretrained models can be loaded with `pretrained` of the companion object:
+
+```scala
+val visualQA = Qwen2VLTransformer.pretrained()
+ย  .setInputCols("image_assembler")
+ย  .setOutputCol("answer")
+```
+{%- capture input_anno -%}
+IMAGE
+{%- endcapture -%}
+
+{%- capture output_anno -%}
+DOCUMENT
+{%- endcapture -%}
+
+{%- capture python_example -%}
+import sparknlp
+from sparknlp.base import *
+from sparknlp.annotator import *
+from pyspark.ml import Pipeline
+from pyspark.sql.functions import lit
+
+image_df = spark.read.format("image").load(path=images_path) # Replace with your image path
+test_df = image_df.withColumn("text", lit("<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n"))
+
+imageAssembler = ImageAssembler()   
+ย  ย  .setInputCol("image")   
+ย  ย  .setOutputCol("image_assembler")
+
+visualQAClassifier = Qwen2VLTransformer.pretrained()   
+ย  ย  .setInputCols("image_assembler")   
+ย  ย  .setOutputCol("answer")
+
+pipeline = Pipeline().setStages([
+ย  ย  imageAssembler,
+ย  ย  visualQAClassifier
+])
+
+result = pipeline.fit(test_df).transform(test_df)
+result.select("image_assembler.origin", "answer.result").show(false)
+{%- endcapture -%}
+
+{%- capture scala_example -%}
+import spark.implicits._
+import com.johnsnowlabs.nlp.base._
+import com.johnsnowlabs.nlp.annotator._
+import org.apache.spark.ml.Pipeline
+import org.apache.spark.sql.DataFrame
+import org.apache.spark.sql.functions.lit
+
+val imageDF: DataFrame = spark.read
+ย  .format("image")
+ย  .option("dropInvalid", value = true)
+ย  .load(imageFolder) // Replace with your image folder
+
+val testDF: DataFrame = imageDF.withColumn("text", lit("<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Describe this image.<|im_end|>\n<|im_start|>assistant\n"))
+
+val imageAssembler: ImageAssembler = new ImageAssembler()
+ย  ย .setInputCol("image")
+ย  ย .setOutputCol("image_assembler")
+
+val visualQAClassifier = Qwen2VLTransformer.pretrained()
+ย  ย .setInputCols("image_assembler")
+ย  ย .setOutputCol("answer")
+
+val pipeline = new Pipeline().setStages(Array(
+ย  imageAssembler,
+ย  visualQAClassifier
+))
+
+val result = pipeline.fit(testDF).transform(testDF)
+
+result.select("image_assembler.origin", "answer.result").show(false)
+{%- endcapture -%}
+
+{%- capture api_link -%}
+[Qwen2VLTransformer](/api/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer)
+{%- endcapture -%}
+
+{%- capture python_api_link -%}
+[Qwen2VLTransformer](/api/python/reference/autosummary/sparknlp/annotator/cv/qwen2_vl/index.html#sparknlp.annotator.cv.qwen2_vl.Qwen2VLTransformer)
+{%- endcapture -%}
+
+{%- capture source_link -%}
+[Qwen2VLTransformer](https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala)
+{%- endcapture -%}
+
+{% include templates/anno_template.md
+title=title
+description=description
+input_anno=input_anno
+output_anno=output_anno
+python_example=python_example
+scala_example=scala_example
+api_link=api_link
+python_api_link=python_api_link
+source_link=source_link
+%}
\ No newline at end of file

From 2cd2cae02917650680c7a908dbe90791d0a3ec73 Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Fri, 14 Feb 2025 01:23:14 +0000
Subject: [PATCH 059/108] update model

Signed-off-by: Prabod Rathnayaka 
---
 .../johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala   | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala
index 5adefec3d6feee..32c820ac684996 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/Qwen2VLTransformer.scala
@@ -587,6 +587,10 @@ trait ReadQwen2VLTransformerDLModel extends ReadOpenvinoModel {
       .setVocabulary(vocabs)
       .setMerges(bytePairs)
       .setAddedTokens(addedTokens)
+      .setSize(preprocessorConfig.size)
+      .setImageMean(preprocessorConfig.image_mean)
+      .setImageStd(preprocessorConfig.image_std)
+      .setResample(preprocessorConfig.resample)
 
     val modelEngine =
       if (useOpenvino)

From 7052361cc65772d2996e9c872089eecf4561bd25 Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Wed, 25 Dec 2024 05:30:29 +0000
Subject: [PATCH 060/108] added preprocessing utils for MLLama

Signed-off-by: Prabod Rathnayaka 
---
 .../cv/util/transform/MllamaUtils.scala       | 330 ++++++++++++++++++
 1 file changed, 330 insertions(+)
 create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala

diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
new file mode 100644
index 00000000000000..248027f1fe359c
--- /dev/null
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
@@ -0,0 +1,330 @@
+package com.johnsnowlabs.nlp.annotators.cv.util.transform
+
+import scala.collection.mutable.ListBuffer
+import java.awt.image.BufferedImage
+import scala.collection.mutable.ArrayBuffer
+import ImageResizeUtils.resizeBufferedImage
+
+object MllamaUtils {
+
+  /** Get all supported aspect ratios for a given max number of image tiles
+    * @param maxImageTiles
+    * @return
+    */
+  def getAllSupportedAspectRatios(maxImageTiles: Int): List[(Int, Int)] = {
+    val aspectRatios = ListBuffer[(Int, Int)]()
+    for (width <- 1 to maxImageTiles) {
+      for (height <- 1 to maxImageTiles) {
+        if (width * height <= maxImageTiles) {
+          aspectRatios += ((width, height))
+        }
+      }
+    }
+
+    aspectRatios.toList
+  }
+
+  /** Get the size of the image that fits the canvas
+    * @param imageHeight
+    * @param imageWidth
+    * @param canvasHeight
+    * @param canvasWidth
+    * @param tileSize
+    * @return
+    */
+  def getImageSizeFitToCanvas(
+      imageHeight: Int,
+      imageWidth: Int,
+      canvasHeight: Int,
+      canvasWidth: Int,
+      tileSize: Int): (Int, Int) = {
+    val targetWidth = math.max(math.min(imageWidth, canvasWidth), tileSize)
+    val targetHeight = math.max(math.min(imageHeight, canvasHeight), tileSize)
+
+    val scaleH = targetHeight.toDouble / imageHeight.toDouble
+    val scaleW = targetWidth.toDouble / imageWidth.toDouble
+
+    if (scaleW < scaleH) {
+      (targetWidth, math.min(math.floor(imageHeight * scaleW).toInt, targetHeight))
+    } else {
+      (math.min(math.floor(imageWidth * scaleH).toInt, targetWidth), targetHeight)
+    }
+  }
+
+  /** Get the optimal tiled canvas size for the image
+    * @param imageHeight
+    * @param imageWidth
+    * @param maxImageTiles
+    * @param tileSize
+    * @return
+    */
+  def getOptimalTiledCanvas(
+      imageHeight: Int,
+      imageWidth: Int,
+      maxImageTiles: Int,
+      tileSize: Int): (Int, Int) = {
+    val possibleTileArrangements = getAllSupportedAspectRatios(maxImageTiles)
+    val possibleCanvasSizes = possibleTileArrangements.map { case (w, h) =>
+      (w * tileSize, h * tileSize)
+    }
+
+    val targetHeights = possibleCanvasSizes.map(_._1)
+    val targetWidths = possibleCanvasSizes.map(_._2)
+
+    val scaleH = targetHeights.map(_.toDouble / imageHeight.toDouble)
+    val scaleW = targetWidths.map(_.toDouble / imageWidth.toDouble)
+
+    val scales = scaleH.zip(scaleW).map { case (h, w) => if (w > h) h else w }
+
+    val upScalingOptions = scales.filter(_ >= 1.0)
+    val selectedScale = if (upScalingOptions.nonEmpty) {
+      upScalingOptions.min
+    } else {
+      scales.filter(_ < 1.0).max
+    }
+
+    val chosenCanvas = possibleCanvasSizes.filter { case (_, h) =>
+      (h.toDouble / imageHeight.toDouble == selectedScale) ||
+      (h.toDouble / imageWidth.toDouble == selectedScale)
+    }
+
+    if (chosenCanvas.size > 1) {
+      chosenCanvas.minBy { case (w, h) => w * h }
+    } else {
+      chosenCanvas.head
+    }
+  }
+
+  /** Convert a crop of an image to a 3D array
+    * @param imgCrop
+    * @return
+    */
+  def imageCropToArray(imgCrop: BufferedImage): Array[Array[Array[Int]]] = {
+    val height = imgCrop.getHeight
+    val width = imgCrop.getWidth
+
+    // Create a 3D array for RGB channels
+    val channels = 3
+    val cropArray = Array.ofDim[Int](channels, height, width)
+
+    for (y <- 0 until height; x <- 0 until width) {
+      val color = new java.awt.Color(imgCrop.getRGB(x, y))
+      cropArray(0)(y)(x) = color.getRed // Red channel
+      cropArray(1)(y)(x) = color.getGreen // Green channel
+      cropArray(2)(y)(x) = color.getBlue // Blue channel
+    }
+
+    cropArray
+  }
+
+  /** Split an image into tiles
+    * @param image
+    * @param numTilesHeight
+    * @param numTilesWidth
+    * @return
+    */
+  def splitToTiles(
+      image: BufferedImage,
+      numTilesHeight: Int,
+      numTilesWidth: Int): Array[Array[Array[Array[Float]]]] = {
+    val cropHeight = image.getHeight / numTilesHeight
+    val cropWidth = image.getWidth / numTilesWidth
+
+    val cropsBuffer = ArrayBuffer[Array[Array[Array[Float]]]]()
+
+    for (i <- 0 until numTilesHeight) {
+      for (j <- 0 until numTilesWidth) {
+        // Extract a crop of 336x336
+        val imgCrop = image.getSubimage(j * cropHeight, i * cropWidth, cropHeight, cropWidth)
+        // Convert the crop to a 3D array (3, 336, 336)
+        val cropArray = imageCropToArray(imgCrop)
+
+        // Normalize the crop if the option is enabled
+        val normalizedCrop = {
+          // Convert Int array to Double array if normalization is off
+          cropArray.map(_.map(_.map(_.toFloat / 255.0.toFloat)))
+        }
+
+        cropsBuffer.append(normalizedCrop)
+      }
+    }
+    cropsBuffer.toArray
+  }
+
+  /** Convert a 3D array to a BufferedImage
+    * @param imageArray
+    * @return
+    */
+  def arrayToBufferedImage(imageArray: Array[Array[Array[Int]]]): BufferedImage = {
+    val height = imageArray(0).length
+    val width = imageArray(0)(0).length
+
+    val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
+
+    for (y <- 0 until height; x <- 0 until width) {
+      val rgb = imageArray.map(_(y)(x)).map(_.toByte)
+      val color = new java.awt.Color(rgb(0), rgb(1), rgb(2))
+      image.setRGB(x, y, color.getRGB)
+    }
+
+    image
+  }
+
+  /** Pack images into a 6D array
+    * @param batchImages
+    * @param maxImageTiles
+    * @return
+    */
+  def packImages(
+      batchImages: List[Array[Array[Array[Array[Float]]]]],
+      maxImageTiles: Int): (Array[Array[Array[Array[Array[Array[Float]]]]]], List[List[Int]]) = {
+    val batchSize = batchImages.size
+    val maxNumImages = batchImages.map(_.length).max
+
+    val channels = batchImages.head.head.length
+    val tileHeight = batchImages.head.head.head.length
+    val tileWidth = batchImages.head.head.head.head.length
+
+    // (batch_size, max_num_images, max_image_tiles, channels, tile_height, tile_width).
+    val stackedImages = ArrayBuffer[Array[Array[Array[Array[Array[Float]]]]]]()
+
+    val allNumTiles = ListBuffer.empty[List[Int]]
+
+    // go over each sample in the batch
+    for ((images, i) <- batchImages.zipWithIndex) {
+      val numSampleTiles = ListBuffer.empty[Int]
+      val tempStackedImages = ArrayBuffer[Array[Array[Array[Array[Float]]]]]()
+      // go over each image in the sample
+
+      for ((image, j) <- images.zipWithIndex) {
+        val tempStackedTiles = ArrayBuffer[Array[Array[Array[Float]]]]()
+        val numTiles = image.length
+        numSampleTiles += numTiles
+        for {
+          k <- 0 until numTiles
+        } {
+          tempStackedTiles.append(image)
+        }
+        // add padded images to the sample
+        for (_ <- 0 until maxImageTiles - image.length) {
+          tempStackedTiles.append(Array.ofDim[Float](channels, tileHeight, tileWidth))
+        }
+        tempStackedImages.append(tempStackedTiles.toArray)
+      }
+
+      // add padded images to the sample.
+      for (_ <- 0 until maxNumImages - images.length) {
+        val tempStackedTiles = ArrayBuffer[Array[Array[Array[Float]]]]()
+        for (_ <- 0 until maxImageTiles) {
+          tempStackedTiles.append(Array.ofDim[Float](channels, tileHeight, tileWidth))
+        }
+        tempStackedImages.append(tempStackedTiles.toArray)
+
+      }
+      stackedImages.append(tempStackedImages.toArray)
+      allNumTiles += numSampleTiles.toList
+    }
+
+    (stackedImages.toArray, allNumTiles.toList)
+  }
+
+  /** build aspect ratio mask
+    * @param aspectRatios
+    * @param maxImageTiles
+    * @return
+    */
+  def buildAspectRatioMask(
+      aspectRatios: List[List[(Int, Int)]],
+      maxImageTiles: Int): Array[Array[Array[Int]]] = {
+    val batchSize = aspectRatios.size
+    val maxNumImages = aspectRatios.map(_.size).max
+
+    val aspectRatioMask = Array.ofDim[Int](batchSize, maxNumImages, maxImageTiles)
+
+    // Set the first tile to 1 for all aspect ratios
+    for {
+      i <- 0 until batchSize
+      j <- 0 until maxNumImages
+    } {
+      aspectRatioMask(i)(j)(0) = 1
+    }
+
+    for ((sampleAspectRatios, i) <- aspectRatios.zipWithIndex) {
+      for ((numTilesW, numTilesH) <- sampleAspectRatios) {
+        for (k <- 0 until numTilesW * numTilesH) {
+          aspectRatioMask(i)(numTilesH)(k) = 1
+        }
+      }
+    }
+
+    aspectRatioMask
+  }
+
+  /** Pack aspect ratios into a 3D array
+    * @param aspectRatios
+    * @param padValue
+    * @return
+    */
+  def packAspectRatios(
+      aspectRatios: List[List[(Int, Int)]],
+      padValue: Int = 1): Array[Array[Array[Int]]] = {
+    val batchSize = aspectRatios.size
+    val maxNumImages = aspectRatios.map(_.size).max
+
+    val aspectRatiosStacked = Array.fill(batchSize, maxNumImages, 2)(padValue)
+
+    for ((row, i) <- aspectRatios.zipWithIndex) {
+      if (row.nonEmpty) {
+        aspectRatiosStacked(i).take(row.size) = row.map(t => Array(t._1, t._2))
+      }
+    }
+
+    aspectRatiosStacked
+  }
+
+  /** Convert aspect ratios to IDs
+    * @param aspectRatios
+    * @param maxImageTiles
+    * @return
+    */
+  def convertAspectRatiosToIds(
+      aspectRatios: List[List[(Int, Int)]],
+      maxImageTiles: Int): Array[Array[Int]] = {
+    val batchSize = aspectRatios.size
+    val maxNumImages = aspectRatios.map(_.size).max
+    val supportedAspectRatios = getAllSupportedAspectRatios(maxImageTiles)
+
+    val aspectRatiosIds = Array.fill(batchSize, maxNumImages)(0) // Initialize with 0 for padding
+
+    for ((sampleAspectRatios, i) <- aspectRatios.zipWithIndex) {
+      for ((aspectRatio, j) <- sampleAspectRatios.zipWithIndex) {
+        aspectRatiosIds(i)(j) = supportedAspectRatios.indexOf(aspectRatio) + 1
+      }
+    }
+
+    aspectRatiosIds
+  }
+
+  /** Resize an image to fit the canvas
+    * @param width
+    * @param height
+    * @param resample
+    * @param maxImageTiles
+    * @param image
+    * @return
+    */
+  def resizeImage(width: Int, height: Int, resample: Int, maxImageTiles: Int)(
+      image: BufferedImage): (BufferedImage, (Int, Int)) = {
+    val imageHeight = image.getHeight
+    val imageWidth = image.getWidth
+
+    val (canvasWidth, canvasHeight) =
+      getOptimalTiledCanvas(imageHeight, imageWidth, maxImageTiles, height)
+
+    val numTilesHeight = canvasHeight / height
+    val numTilesWidth = canvasWidth / width
+    (
+      resizeBufferedImage(canvasWidth, canvasHeight, resample)(image),
+      (numTilesHeight, numTilesWidth))
+  }
+}

From 89c1803d0884ed70b78fc9a4cddf62a9b52ffb75 Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Wed, 8 Jan 2025 06:33:20 +0000
Subject: [PATCH 061/108] MLLama tokenizers and utils

Signed-off-by: Prabod Rathnayaka 
---
 .../scala/com/johnsnowlabs/ml/ai/MLLama.scala | 536 ++++++++++++++++++
 .../ml/openvino/OpenvinoWrapper.scala         |   3 +
 .../cv/util/transform/MllamaUtils.scala       | 155 ++++-
 .../tokenizer/bpe/BpeTokenizer.scala          |   7 +
 .../tokenizer/bpe/MLLamaTokenizer.scala       | 111 ++++
 5 files changed, 811 insertions(+), 1 deletion(-)
 create mode 100644 src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala
 create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala

diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala
new file mode 100644
index 00000000000000..b90f92f55e7307
--- /dev/null
+++ b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala
@@ -0,0 +1,536 @@
+package com.johnsnowlabs.ml.ai
+
+import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig
+import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers
+import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.MLLamaWrappers
+import com.johnsnowlabs.nlp.annotators.common.Sentence
+import com.johnsnowlabs.ml.util.{ONNX, Openvino}
+import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT
+import com.johnsnowlabs.nlp._
+import com.johnsnowlabs.nlp.annotators.common.SentenceSplit
+import com.johnsnowlabs.nlp.annotators.cv.util.transform.ImageResizeUtils
+import com.johnsnowlabs.nlp.annotators.cv.util.transform.MllamaUtils
+
+import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor
+import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils
+import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{
+  BpeTokenizer,
+  MLLamaTokenizer,
+  SpecialTokens
+}
+import org.intel.openvino.InferRequest
+
+import scala.collection.JavaConverters._
+
+private[johnsnowlabs] class MLLama(
+    val onnxWrappers: Option[DecoderWrappers],
+    val openvinoWrapper: Option[MLLamaWrappers],
+    merges: Map[(String, String), Int],
+    vocabulary: Map[String, Int],
+    addedTokens: Map[String, Int],
+    preprocessor: Preprocessor,
+    generationConfig: GenerationConfig,
+    imageTokenLength: Int,
+    imageToken: Int,
+    maxImageTiles: Int,
+    paddingConstant: Int = 0)
+    extends Serializable {
+
+  val detectedEngine: String =
+    if (onnxWrappers.isDefined) ONNX.name
+    else if (openvinoWrapper.isDefined) Openvino.name
+    else Openvino.name
+
+  private val GenerationConfig(
+    bosTokenId: Int,
+    paddingTokenId: Int,
+    eosTokenId: Int,
+    vocabSize: Int,
+    beginSuppressTokens,
+    suppressTokenIds,
+    forcedDecoderIds) =
+    generationConfig
+  val reversedVocabulary: Map[Int, String] = vocabulary.map(_.swap)
+
+  val specialTokens: SpecialTokens = SpecialTokens(
+    vocabulary,
+    startTokenString = reversedVocabulary(bosTokenId),
+    endTokenString = reversedVocabulary(eosTokenId),
+    unkTokenString = reversedVocabulary(eosTokenId),
+    maskTokenString = reversedVocabulary(eosTokenId),
+    padTokenString = reversedVocabulary(paddingTokenId),
+    additionalStrings = addedTokens.keys.toArray)
+
+  val bpeTokenizer: MLLamaTokenizer = BpeTokenizer
+    .forModel(
+      "mllama",
+      merges = merges,
+      vocab = vocabulary,
+      specialTokens = Some(specialTokens),
+      addPrefixSpaceToSentence = false,
+      alwaysAddPrefix = false)
+    .asInstanceOf[MLLamaTokenizer]
+
+  /** Decode a sequence of sentences
+    * @param sentences
+    *   Sequence of sentences
+    * @return
+    *   Sequence of decoded sentences
+    */
+  def decode(sentences: Array[Array[Int]]): Seq[String] = {
+    sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt)))
+  }
+
+  /** Encode a sequence of sentences
+    * @param sentences
+    *   Sequence of sentences
+    * @return
+    *   Sequence of encoded sentences
+    */
+  def encodeText(sentences: Seq[Annotation], imgTokenLen: List[Int]): Seq[Array[Int]] = {
+
+    val pattern = raw"<\|image\|>".r
+
+    // raise an error if the pattern is not found in the text
+    if (pattern.findFirstIn(sentences.head.result).isEmpty) {
+      throw new IllegalArgumentException("The pattern <\\|image\\|> is not found in the text")
+    }
+
+    // split the sentences into chunks based on the pattern and tokenize them
+    // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)]
+    val promptChunks = sentences
+      .map(s => {
+        val sentWithTask = s.result
+        var offsetLength = 0
+        pattern
+          .split(sentWithTask)
+          .zipWithIndex
+          .map(s => {
+            val sentenceWithTask = Sentence(
+              content = s._1,
+              start = offsetLength,
+              end = offsetLength + s._1.length,
+              index = s._2)
+            offsetLength += s._1.length
+            bpeTokenizer
+              .tokenize(sentenceWithTask)
+              .map(bpeTokenizer.encode)
+              .flatMap(_.map(_.pieceId))
+          })
+      })
+
+    // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]]
+    val tokens = promptChunks
+      .zip(imgTokenLen)
+      .map(s => {
+        val (promptChunk, imgTokenLen) = s
+        val imgPaddingTokens = Array.fill(imgTokenLen)(imageToken)
+        val combinedChunks = promptChunk
+          .map(_.toArray)
+          .reduce(_ ++ imgPaddingTokens ++ _)
+        Array(bosTokenId) ++ combinedChunks
+      })
+
+    //    val tokens = SentenceSplit
+    //      .unpack(sentences)
+    //      .map(s => {
+    //        val sentWithTask = s
+    //        bpeTokenizer
+    //          .tokenize(sentWithTask)
+    //          .map(bpeTokenizer.encode)
+    //          .flatMap(_.map(_.pieceId))
+    //      })
+    tokens
+  }
+
+  def encode(
+      imageAnnotations: Seq[AnnotationImage],
+      sentences: Seq[Annotation],
+      preprocessor: Preprocessor,
+      imageTokenLength: Int = imageTokenLength)
+      : (Seq[Array[Int]], Array[Array[Array[Array[Float]]]]) = {
+    val (preprocessedImages, aspectRatioIds, aspectRatioMask, numTiles) =
+      encodeImage(imageAnnotations.toArray, preprocessor, maxImageTiles, paddingConstant)
+    val encodedText = encodeText(sentences, List(imageTokenLength)).toArray
+
+    (encodedText, preprocessedImages)
+  }
+
+  def tag(
+      batch: Seq[Array[Int]],
+      images: Array[Array[Array[Array[Float]]]],
+      minOutputLength: Int,
+      maxOutputLength: Int,
+      doSample: Boolean,
+      temperature: Double,
+      topK: Int,
+      topP: Double,
+      repetitionPenalty: Double,
+      noRepeatNgramSize: Int,
+      randomSeed: Option[Long],
+      ignoreTokenIds: Array[Int] = Array(),
+      beamSize: Int,
+      maxInputLength: Int,
+      stopTokenIds: Array[Int]): Array[Array[Int]] = {
+
+    val pixelValues = images
+    val ignoreTokenIdsInt = ignoreTokenIds
+    val expandedDecoderInputsVals = batch
+    val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray
+    val maxSentenceLength = sequencesLength.max // - curLen
+    //    val pixelValues = images._1
+    //    val imageSizes = images._2
+    val numReturn_sequences = 1
+    // from config
+
+    var effectiveBatch_size = 1
+    var effectiveBatch_mult = 1
+
+    if (doSample) {
+      effectiveBatch_size = expandedDecoderInputsVals.length * numReturn_sequences
+      effectiveBatch_mult = numReturn_sequences
+    } else {
+      effectiveBatch_size = expandedDecoderInputsVals.length
+      effectiveBatch_mult = 1
+    }
+
+    val inferRequestLanguageModel =
+      openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request()
+    val inferRequestVisionEmbeddingsModel =
+      openvinoWrapper.get.visionEmbeddingsModel.getCompiledModel().create_infer_request()
+    val inferRequestTextEmbeddingsModel =
+      openvinoWrapper.get.textEmbeddingsModel.getCompiledModel().create_infer_request()
+    val inferRequestMergeModel =
+      openvinoWrapper.get.mergeModel.getCompiledModel().create_infer_request()
+
+    val generatedIds = generateGreedy(
+      batch.toArray,
+      batch.toArray,
+      pixelValues,
+      maxOutputLength,
+      inferRequestLanguageModel,
+      inferRequestVisionEmbeddingsModel,
+      inferRequestTextEmbeddingsModel,
+      inferRequestMergeModel)
+    generatedIds
+  }
+
+  def generateGreedy(
+      encoderInputIds: Array[Array[Int]],
+      decoderInputIds: Array[Array[Int]],
+      pixelValues: Array[Array[Array[Array[Float]]]],
+      maxOutputLength: Int,
+      inferRequestLanguageModel: InferRequest,
+      inferRequestVisionEmbeddingsModel: InferRequest,
+      inferRequestTextEmbeddingsModel: InferRequest,
+      inferRequestMergeModel: InferRequest): Array[Array[Int]] = {
+
+    var generatedIds: Array[Array[Int]] = Array()
+    var decoderInputIdsCopied = decoderInputIds
+    while (!greedyGenerationFinished(generatedIds, eosTokenId, maxOutputLength)) {
+      val decoderOutputs = getModelOutputs(
+        encoderInputIds,
+        decoderInputIdsCopied,
+        pixelValues,
+        inferRequestLanguageModel,
+        inferRequestVisionEmbeddingsModel,
+        inferRequestTextEmbeddingsModel,
+        inferRequestMergeModel)
+
+      val nextTokenIds = decoderOutputs.map { scores =>
+        argmax(scores)
+      }
+
+      if (generatedIds.isEmpty) {
+        generatedIds = nextTokenIds.map(Array(_))
+      } else {
+        generatedIds =
+          generatedIds.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) =>
+            currentIds ++ Array(nextId)
+          }
+      }
+
+      // extend decoder input ids
+      decoderInputIdsCopied =
+        decoderInputIdsCopied.zip(nextTokenIds).map { case (currentIds, nextId) =>
+          currentIds ++ Array(nextId)
+        }
+    }
+    generatedIds
+  }
+
+  def predict(
+      sentences: Seq[Annotation],
+      imageAnnotations: Seq[AnnotationImage],
+      batchSize: Int,
+      minOutputLength: Int,
+      maxOutputLength: Int,
+      doSample: Boolean,
+      temperature: Double,
+      topK: Int,
+      topP: Double,
+      repetitionPenalty: Double,
+      noRepeatNgramSize: Int,
+      randomSeed: Option[Long] = None,
+      ignoreTokenIds: Array[Int] = Array(),
+      beamSize: Int,
+      maxInputLength: Int): Seq[Annotation] = {
+
+    val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences, preprocessor)
+    val tagged = tag(
+      encodedText,
+      preprocessedImages,
+      minOutputLength,
+      maxOutputLength,
+      doSample,
+      temperature,
+      topK,
+      topP,
+      repetitionPenalty,
+      noRepeatNgramSize,
+      randomSeed,
+      ignoreTokenIds,
+      beamSize,
+      maxInputLength,
+      Array(eosTokenId))
+    val decoded = decode(tagged)
+
+    var sentBegin, nextSentEnd = 0
+    val annotations = decoded.map { content =>
+      nextSentEnd += content.length - 1
+      val annots = new Annotation(
+        annotatorType = DOCUMENT,
+        begin = sentBegin,
+        end = nextSentEnd,
+        result = content,
+        metadata = Map())
+      sentBegin += nextSentEnd + 1
+      annots
+    }
+    annotations
+  }
+
+  def getModelOutputs(
+      encoderInputIds: Array[Array[Int]],
+      decoderInputIds: Array[Array[Int]],
+      pixelValues: Array[Array[Array[Array[Float]]]],
+      inferRequestLanguageModel: InferRequest,
+      inferRequestVisionEmbeddingsModel: InferRequest,
+      inferRequestTextEmbeddingsModel: InferRequest,
+      inferRequestMergeModel: InferRequest): Array[Array[Float]] = {
+
+    val inputEmbeds = getMultimodalEmbeddings(
+      encoderInputIds,
+      decoderInputIds,
+      pixelValues,
+      inferRequestVisionEmbeddingsModel,
+      inferRequestTextEmbeddingsModel,
+      inferRequestMergeModel)
+
+    val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) =
+      if (encoderInputIds.head.length == decoderInputIds.head.length) {
+        // First pass
+        val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) }
+        val posIdsLong = decoderInputIds.flatMap { tokenIds =>
+          tokenIds.zipWithIndex.map { case (_, i) =>
+            i.toLong
+          }
+        }
+        (inpIdsLong, posIdsLong)
+      } else {
+        // Subsequent passes
+        val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong }
+        val posIdsLong = decoderInputIds.map { tokenIds =>
+          tokenIds.zipWithIndex.map { case (_, i) =>
+            i.toLong
+          }.last
+        }
+        (inpIdsLong, posIdsLong)
+      }
+    val attentionMask: Array[Long] =
+      decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) }
+
+    val batchSize: Int = decoderInputIds.length
+    val beamIdx: Array[Int] = new Array[Int](batchSize)
+    val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize)
+
+    val decoderAttentionMask: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask)
+    val decoderPositionIDs: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(shape, inputPositionIDsLong)
+    val beamIdxTensor: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(Array(batchSize), beamIdx)
+
+    inferRequestLanguageModel.set_tensor("inputs_embeds", inputEmbeds)
+    inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask)
+    inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs)
+    inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor)
+
+    inferRequestLanguageModel.infer()
+
+    val result = inferRequestLanguageModel.get_tensor("logits")
+    val logitsRaw = result.data()
+
+    val sequenceLength = inputIdsLong.length / batchSize
+    val decoderOutputs = (0 until batchSize).map(i => {
+      logitsRaw
+        .slice(
+          i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize,
+          i * sequenceLength * vocabSize + sequenceLength * vocabSize)
+    })
+    decoderOutputs.toArray
+  }
+
+  private def argmax(scores: Array[Float]): Int =
+    scores.zipWithIndex.maxBy { case (score, _) =>
+      score
+    }._2
+
+  private def greedyGenerationFinished(
+      decoderIds: Seq[Array[Int]],
+      eosTokenId: Int,
+      maxOutputLength: Int): Boolean = {
+    if (decoderIds.isEmpty) {
+      false
+    } else {
+      decoderIds.forall { ids =>
+        ids.length >= maxOutputLength || ids.last == eosTokenId
+      }
+    }
+  }
+
+  private def encodeImage(
+      annotations: Array[AnnotationImage],
+      preprocessor: Preprocessor,
+      maxImageTiles: Int,
+      paddingConstant: Int): (
+      Array[Array[Array[Array[Array[Array[Float]]]]]],
+      Array[Array[Int]],
+      Array[Array[Array[Int]]],
+      List[List[Int]]) = {
+
+    val (batchProcessedImages, batchAspectRatios)
+        : (Array[Array[Array[Array[Array[Float]]]]], Array[List[(Int, Int)]]) = annotations.map {
+      annot =>
+        val bufferedImage = ImageIOUtils.byteToBufferedImage(
+          bytes = annot.result,
+          w = annot.width,
+          h = annot.height,
+          nChannels = annot.nChannels)
+
+        val (resizedImage, (resizedImageHeight, resizedImageWidth)) =
+          if (preprocessor.do_resize) {
+            MllamaUtils.resizeImage(
+              width = preprocessor.size,
+              height = preprocessor.size,
+              resample = preprocessor.resample,
+              maxImageTiles = maxImageTiles)(bufferedImage)
+          } else (bufferedImage, (0, 0))
+
+        val paddedImage = MllamaUtils.pad(
+          image = resizedImage,
+          paddingConstant = paddingConstant,
+          aspectRatio = (resizedImageHeight, resizedImageWidth))
+
+        val normalizedImage =
+          ImageResizeUtils.normalizeAndConvertBufferedImage(
+            img = paddedImage,
+            mean = preprocessor.image_mean,
+            std = preprocessor.image_std,
+            doNormalize = preprocessor.do_normalize,
+            doRescale = preprocessor.do_rescale,
+            rescaleFactor = preprocessor.rescale_factor)
+
+        val normalizedImageBuffer =
+          MllamaUtils.floatArrayToBufferedImage(normalizedImage, preprocessor.rescale_factor)
+        val imageTiles = MllamaUtils.splitToTiles(
+          image = normalizedImageBuffer,
+          numTilesHeight = resizedImageHeight,
+          numTilesWidth = resizedImageWidth)
+
+        val aspectRatioList: List[(Int, Int)] = List((resizedImageHeight, resizedImageWidth))
+
+        (imageTiles, aspectRatioList)
+    }
+
+    val (images, numTiles) =
+      MllamaUtils.packImages(batchImages = batchProcessedImages, maxImageTiles = maxImageTiles)
+
+    val aspectRatioIds: Array[Array[Int]] =
+      MllamaUtils.convertAspectRatiosToIds(
+        batchAspectRatios.toList,
+        maxImageTiles = maxImageTiles)
+
+    val aspectRatioMask: Array[Array[Array[Int]]] =
+      MllamaUtils.buildAspectRatioMask(batchAspectRatios.toList, maxImageTiles = maxImageTiles)
+
+    (images, aspectRatioIds, aspectRatioMask, numTiles)
+
+  }
+
+  def getMultimodalEmbeddings(
+      encoderInputIds: Array[Array[Int]],
+      decoderInputIds: Array[Array[Int]],
+      pixelValues: Array[Array[Array[Array[Float]]]],
+      inferRequestVisionEmbeddingsModel: InferRequest,
+      inferRequestTextEmbeddingsModel: InferRequest,
+      inferRequestMergeModel: InferRequest): org.intel.openvino.Tensor = {
+    val inputIdsLong: Array[Long] =
+      if (encoderInputIds.head.length == decoderInputIds.head.length) {
+        // First pass
+        val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) }
+
+        inpIdsLong
+      } else {
+        // Subsequent passes
+        val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong }
+        inpIdsLong
+      }
+    val batchSize: Int = decoderInputIds.length
+    val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize)
+    val inputIdsLongTensor: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(shape, inputIdsLong)
+
+    val imageEmbeddings: org.intel.openvino.Tensor =
+      if (encoderInputIds.head.length == decoderInputIds.head.length) {
+        val pixelValuesTensor: org.intel.openvino.Tensor =
+          new org.intel.openvino.Tensor(
+            Array(batchSize, 3, 336, 336),
+            pixelValues.flatten.flatten.flatten.map(_.toFloat))
+
+        // Get image embeddings
+        inferRequestVisionEmbeddingsModel.set_input_tensor(pixelValuesTensor)
+
+        inferRequestVisionEmbeddingsModel.infer()
+
+        val imageEmbeddings = inferRequestVisionEmbeddingsModel.get_output_tensor()
+
+        // Get text embeddings
+        inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor)
+
+        inferRequestTextEmbeddingsModel.infer()
+
+        val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor()
+
+        // Merge image and text embeddings
+        inferRequestMergeModel.set_tensor("vision_embeds", imageEmbeddings)
+        inferRequestMergeModel.set_tensor("inputs_embeds", textEmbeddings)
+        inferRequestMergeModel.set_tensor("input_ids", inputIdsLongTensor)
+
+        inferRequestMergeModel.infer()
+
+        inferRequestMergeModel.get_tensor("final_embedding")
+      } else {
+        // Get text embeddings
+        inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor)
+
+        inferRequestTextEmbeddingsModel.infer()
+
+        val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor()
+
+        textEmbeddings
+      }
+    imageEmbeddings
+  }
+
+}
diff --git a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala
index 0c2f65d4315e4e..efeed5f9398275 100644
--- a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala
+++ b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala
@@ -218,4 +218,7 @@ object OpenvinoWrapper {
       decoderWithPast: OpenvinoWrapper)
   case class DecoderWrappers(decoder: OpenvinoWrapper)
   case class EncoderDecoderWithoutPastWrappers(encoder: OpenvinoWrapper, decoder: OpenvinoWrapper)
+  case class MLLamaWrappers(
+      visionEmbeddingsModel: OpenvinoWrapper,
+      languageModel: OpenvinoWrapper)
 }
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
index 248027f1fe359c..691149bfe9fa0f 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
@@ -4,10 +4,13 @@ import scala.collection.mutable.ListBuffer
 import java.awt.image.BufferedImage
 import scala.collection.mutable.ArrayBuffer
 import ImageResizeUtils.resizeBufferedImage
+import scala.collection.mutable.ArrayBuffer
+import scala.math.max
 
 object MllamaUtils {
 
   /** Get all supported aspect ratios for a given max number of image tiles
+    *
     * @param maxImageTiles
     * @return
     */
@@ -25,6 +28,7 @@ object MllamaUtils {
   }
 
   /** Get the size of the image that fits the canvas
+    *
     * @param imageHeight
     * @param imageWidth
     * @param canvasHeight
@@ -52,6 +56,7 @@ object MllamaUtils {
   }
 
   /** Get the optimal tiled canvas size for the image
+    *
     * @param imageHeight
     * @param imageWidth
     * @param maxImageTiles
@@ -96,6 +101,7 @@ object MllamaUtils {
   }
 
   /** Convert a crop of an image to a 3D array
+    *
     * @param imgCrop
     * @return
     */
@@ -118,6 +124,7 @@ object MllamaUtils {
   }
 
   /** Split an image into tiles
+    *
     * @param image
     * @param numTilesHeight
     * @param numTilesWidth
@@ -152,6 +159,7 @@ object MllamaUtils {
   }
 
   /** Convert a 3D array to a BufferedImage
+    *
     * @param imageArray
     * @return
     */
@@ -170,13 +178,36 @@ object MllamaUtils {
     image
   }
 
+  /** Convert a 3D array of floats to a BufferedImage
+    *
+    * @param imageArray
+    * @return
+    */
+  def floatArrayToBufferedImage(
+      imageArray: Array[Array[Array[Float]]],
+      rescaleFactor: Double): BufferedImage = {
+    val height = imageArray(0).length
+    val width = imageArray(0)(0).length
+
+    val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)
+
+    for (y <- 0 until height; x <- 0 until width) {
+      val rgb = imageArray.map(_(y)(x)).map { x => (x * (1 / rescaleFactor)).toInt }
+      val color = new java.awt.Color(rgb(0), rgb(1), rgb(2))
+      image.setRGB(x, y, color.getRGB)
+    }
+
+    image
+  }
+
   /** Pack images into a 6D array
+    *
     * @param batchImages
     * @param maxImageTiles
     * @return
     */
   def packImages(
-      batchImages: List[Array[Array[Array[Array[Float]]]]],
+      batchImages: Array[Array[Array[Array[Array[Float]]]]],
       maxImageTiles: Int): (Array[Array[Array[Array[Array[Array[Float]]]]]], List[List[Int]]) = {
     val batchSize = batchImages.size
     val maxNumImages = batchImages.map(_.length).max
@@ -229,6 +260,7 @@ object MllamaUtils {
   }
 
   /** build aspect ratio mask
+    *
     * @param aspectRatios
     * @param maxImageTiles
     * @return
@@ -261,6 +293,7 @@ object MllamaUtils {
   }
 
   /** Pack aspect ratios into a 3D array
+    *
     * @param aspectRatios
     * @param padValue
     * @return
@@ -283,6 +316,7 @@ object MllamaUtils {
   }
 
   /** Convert aspect ratios to IDs
+    *
     * @param aspectRatios
     * @param maxImageTiles
     * @return
@@ -306,6 +340,7 @@ object MllamaUtils {
   }
 
   /** Resize an image to fit the canvas
+    *
     * @param width
     * @param height
     * @param resample
@@ -327,4 +362,122 @@ object MllamaUtils {
       resizeBufferedImage(canvasWidth, canvasHeight, resample)(image),
       (numTilesHeight, numTilesWidth))
   }
+
+  def padConstant(
+      image: Array[Array[Float]],
+      padding: Int,
+      constantValue: Float): Array[Array[Float]] = {
+    val rows = image.length
+    val cols = image(0).length
+
+    val paddedRows = rows + 2 * padding
+    val paddedCols = cols + 2 * padding
+
+    val paddedImage = Array.ofDim[Float](paddedRows, paddedCols)
+
+    for (i <- 0 until paddedRows) {
+      for (j <- 0 until paddedCols) {
+        if (i >= padding && i < rows + padding && j >= padding && j < cols + padding) {
+          paddedImage(i)(j) = image(i - padding)(j - padding)
+        } else {
+          paddedImage(i)(j) = constantValue
+        }
+      }
+    }
+
+    paddedImage
+  }
+
+  def padBufferedImage(
+      image: BufferedImage,
+      padding: (Int, Int),
+      constantColor: Int): BufferedImage = {
+    val originalWidth = image.getWidth
+    val originalHeight = image.getHeight
+
+    val paddedWidth = originalWidth + 2 * padding._2
+    val paddedHeight = originalHeight + 2 * padding._1
+
+    val paddedImage = new BufferedImage(paddedWidth, paddedHeight, image.getType)
+
+    for (x <- 0 until paddedWidth; y <- 0 until paddedHeight) {
+      if (x >= padding._2 && x < originalWidth + padding._2 && y >= padding._1 && y < originalHeight + padding._1) {
+        paddedImage.setRGB(x, y, image.getRGB(x - padding._2, y - padding._1))
+      } else {
+        paddedImage.setRGB(x, y, constantColor)
+      }
+    }
+
+    paddedImage
+  }
+
+  def pad(image: BufferedImage, paddingConstant: Int, aspectRatio: (Int, Int)): BufferedImage = {
+    val originalWidth = image.getWidth
+    val originalHeight = image.getHeight
+
+    val numTilesHeight = aspectRatio._1
+    val numTilesWidth = aspectRatio._2
+
+    val paddedWidth = numTilesWidth * originalWidth
+    val paddedHeight = numTilesHeight * originalHeight
+
+    val paddingHeight = paddedHeight - originalHeight
+    val paddingWidth = paddedWidth - originalWidth
+
+    val paddedImage = padBufferedImage(image, (paddingHeight, paddingWidth), paddingConstant)
+    paddedImage
+  }
+
+  def getCrossAttentionTokenMask(inputIds: Array[Int], imageTokenId: Int): Array[Array[Int]] = {
+    val imageTokenLocations = inputIds.zipWithIndex.filter(_._1 == imageTokenId).map(_._2)
+
+    if (imageTokenLocations.isEmpty) {
+      Array.empty
+    } else if (imageTokenLocations.length == 1) {
+      Array(Array(imageTokenLocations(0), -1))
+    } else {
+      val visionMasks =
+        imageTokenLocations.sliding(2).map(pair => Array(pair(0), pair(1))).toArray
+      visionMasks.init.zip(visionMasks.tail).foreach { case (prev, curr) =>
+        if (prev(0) + 1 == curr(0)) {
+          prev(1) = curr(1)
+        }
+      }
+      visionMasks.last(0) = visionMasks.last(0)
+      visionMasks.last(1) = inputIds.length
+      visionMasks
+    }
+  }
+
+  def convertSparseCrossAttentionMaskToDense(
+      crossAttentionTokenMask: Array[Array[Array[Int]]],
+      numTiles: Array[Array[Int]],
+      maxNumTiles: Int,
+      length: Int): Array[Array[Array[Array[Int]]]] = {
+    val batchSize = crossAttentionTokenMask.length
+    val maxNumImages = crossAttentionTokenMask.map(_.length).max
+
+    val crossAttentionMask = Array.ofDim[Int](batchSize, length, maxNumImages, maxNumTiles)
+
+    for {
+      sampleIdx <- crossAttentionTokenMask.indices
+      (sampleMasks, sampleNumTiles) <- crossAttentionTokenMask(sampleIdx)
+        .zip(numTiles(sampleIdx))
+        .zipWithIndex
+      (locations, maskNumTiles) <- sampleMasks.zip(sampleNumTiles).zipWithIndex
+      if locations.length == 2
+    } {
+      val (start, end) = (locations(0), locations(1))
+      val effectiveEnd = if (end == -1) length else math.min(end, length)
+      for {
+        i <- start until effectiveEnd
+        j <- 0 until maskNumTiles
+      } {
+        crossAttentionMask(sampleIdx)(i)(maskIdx)(j) = 1
+      }
+    }
+
+    crossAttentionMask
+  }
+
 }
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala
index 8c72a8f99d6685..137b5fb437763c 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala
@@ -382,6 +382,13 @@ object BpeTokenizer {
           modelSpecialTokens(),
           padWithSequenceTokens,
           addPrefixSpaceToSentence = addPrefixSpaceToSentence)
+      case "mllama" =>
+        new MLLamaTokenizer(
+          merges,
+          vocab,
+          modelSpecialTokens(),
+          padWithSequenceTokens,
+          addPrefixSpaceToSentence = addPrefixSpaceToSentence)
       case _ =>
         throw new IllegalArgumentException("Model type \"" + modelType + "\" not supported yet.")
     }
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala
new file mode 100644
index 00000000000000..b2b31b95ef2c7c
--- /dev/null
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017-2022 John Snow Labs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.johnsnowlabs.nlp.annotators.tokenizer.bpe
+
+import com.johnsnowlabs.nlp.annotators.common.IndexedToken
+
+import java.nio.charset.Charset
+import scala.collection.mutable.ListBuffer
+import scala.util.matching.Regex
+import scala.collection.mutable
+
+class MLLamaTokenizer(
+    merges: Map[(String, String), Int],
+    vocab: Map[String, Int],
+    specialTokens: SpecialTokens,
+    padWithSequenceTokens: Boolean = true,
+    prependString: String = "",
+    addPrefixSpaceToSentence: Boolean = false,
+    alwaysAddPrefix: Boolean = true,
+    splitPatternRegex: Regex =
+      raw"""(?i)(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".r)
+    extends BpeTokenizer(
+      merges,
+      vocab,
+      specialTokens,
+      padWithSequenceTokens,
+      addPrefixSpaceToSentence,
+      alwaysAddPrefix) {
+
+  /** Mapping for bytes to a different set of unicode characters (especially white spaces). This
+    * improved model performance for gpt-2
+    */
+  protected val bytesToUnicodeMapping: Map[Int, String] = {
+    val bytes: ListBuffer[Int] =
+      ListBuffer.range('!', '~' + 1) ++ ListBuffer.range('ยก', 'ยฌ' + 1) ++ ListBuffer
+        .range('ยฎ', 'รฟ' + 1)
+    val characters: ListBuffer[Int] = bytes.clone
+    var n = 0
+    for (b <- 0 to 256) {
+      if (!bytes.contains(b)) {
+        bytes += b
+        characters += (256 + n)
+        n += 1
+      }
+    }
+    (bytes zip characters.map(_.toChar.toString)).toMap
+  }
+
+  // Differs from Transformers, space is always prepended.
+  // FIX: Space should not be prepended to all tokens, but to the beginning of the text only. Otherwise token
+  // such as '.' get space prepended and they should not.
+  override val prefixForPieceId: Option[String] =
+    if (prependString.nonEmpty) Some(prependString) else None
+
+  protected val decoderVocab: Map[Int, String] = vocab.map(x => (x._2, x._1))
+
+  protected val unicodeToByteMapping: Map[String, Int] =
+    bytesToUnicodeMapping.map(x => (x._2, x._1))
+
+  override def preProcessTokenForBpe(token: String): String = {
+    token
+      .getBytes("UTF-8")
+      .map { b => if (b < 0) 256 + b else b }
+      .foldLeft("")(_ + bytesToUnicodeMapping(_))
+  }
+
+  val splitPattern: Regex = splitPatternRegex
+
+  override def tokenizeSubText(text: String, indexOffset: Int): Array[IndexedToken] = {
+    // split pattern based on gpt2's bpe tokenizer
+    splitPattern
+      .findAllMatchIn(if (prefixForPieceId.isDefined || text.startsWith(" ")) text
+      else " " + text) // Prepend space to the beginning of text
+      .map(tok => IndexedToken(tok.matched, tok.start + indexOffset, tok.end + indexOffset - 1))
+      .toArray
+  }
+
+  def decodeTokens(tokens: Array[Int]): String = {
+    val decoded = new mutable.StringBuilder()
+    tokens.foreach { token =>
+      {
+        val decodedToken = decoderVocab(token)
+        if (!specialTokens.contains(decodedToken)) {
+          if (decodedToken.startsWith("<0x") && decodedToken.endsWith(">")) {
+            val strippedHex = decodedToken.replaceAll("<0x|>", "")
+            val byteValue = Integer.parseInt(strippedHex, 16)
+            decoded.append(byteValue.toChar)
+          } else {
+            decoded.append(decodedToken)
+          }
+        }
+      }
+
+    }
+    decoded.toString().replaceAll(decoderVocab(29871), " ").trim()
+  }
+}

From 58e309b1d753d55bfb2ba492634d1d96a1e9271c Mon Sep 17 00:00:00 2001
From: Prabod Rathnayaka 
Date: Mon, 20 Jan 2025 09:09:04 +0000
Subject: [PATCH 062/108] MLLama scala api

---
 .../scala/com/johnsnowlabs/ml/ai/MLLama.scala | 437 +++++++-----
 .../ml/openvino/OpenvinoWrapper.scala         |   3 +-
 .../ml/util/LoadExternalModel.scala           |  59 +-
 .../annotators/cv/MLLamaForMultimodal.scala   | 636 ++++++++++++++++++
 .../cv/util/transform/MllamaUtils.scala       | 151 +++--
 .../tokenizer/bpe/BpeTokenizer.scala          |   7 +-
 .../tokenizer/bpe/MLLamaTokenizer.scala       |  48 +-
 src/test/resources/images/demo.jpeg           | Bin 0 -> 496395 bytes
 src/test/resources/images/image1.jpg          | Bin 0 -> 404080 bytes
 .../cv/MLLamaForMultimodalTestSpec.scala      | 191 ++++++
 10 files changed, 1271 insertions(+), 261 deletions(-)
 create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala
 create mode 100644 src/test/resources/images/demo.jpeg
 create mode 100644 src/test/resources/images/image1.jpg
 create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala

diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala
index b90f92f55e7307..39d7b7dccc9d01 100644
--- a/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala
+++ b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala
@@ -30,9 +30,9 @@ private[johnsnowlabs] class MLLama(
     addedTokens: Map[String, Int],
     preprocessor: Preprocessor,
     generationConfig: GenerationConfig,
-    imageTokenLength: Int,
     imageToken: Int,
-    maxImageTiles: Int,
+    maxImageTiles: Int = 4,
+    numVisionTokens: Int = 1601,
     paddingConstant: Int = 0)
     extends Serializable {
 
@@ -68,7 +68,8 @@ private[johnsnowlabs] class MLLama(
       vocab = vocabulary,
       specialTokens = Some(specialTokens),
       addPrefixSpaceToSentence = false,
-      alwaysAddPrefix = false)
+      alwaysAddPrefix = true,
+      prependString = "")
     .asInstanceOf[MLLamaTokenizer]
 
   /** Decode a sequence of sentences
@@ -87,78 +88,94 @@ private[johnsnowlabs] class MLLama(
     * @return
     *   Sequence of encoded sentences
     */
-  def encodeText(sentences: Seq[Annotation], imgTokenLen: List[Int]): Seq[Array[Int]] = {
-
-    val pattern = raw"<\|image\|>".r
-
-    // raise an error if the pattern is not found in the text
-    if (pattern.findFirstIn(sentences.head.result).isEmpty) {
-      throw new IllegalArgumentException("The pattern <\\|image\\|> is not found in the text")
-    }
-
-    // split the sentences into chunks based on the pattern and tokenize them
-    // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)]
-    val promptChunks = sentences
-      .map(s => {
-        val sentWithTask = s.result
-        var offsetLength = 0
-        pattern
-          .split(sentWithTask)
-          .zipWithIndex
-          .map(s => {
-            val sentenceWithTask = Sentence(
-              content = s._1,
-              start = offsetLength,
-              end = offsetLength + s._1.length,
-              index = s._2)
-            offsetLength += s._1.length
-            bpeTokenizer
-              .tokenize(sentenceWithTask)
-              .map(bpeTokenizer.encode)
-              .flatMap(_.map(_.pieceId))
-          })
-      })
-
-    // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]]
-    val tokens = promptChunks
-      .zip(imgTokenLen)
+  def encodeText(sentences: Seq[Annotation]): Seq[Array[Int]] = {
+
+//    val pattern = raw"<\|image\|>".r
+//
+//    // raise an error if the pattern is not found in the text
+//    if (pattern.findFirstIn(sentences.head.result).isEmpty) {
+//      throw new IllegalArgumentException("The pattern <\\|image\\|> is not found in the text")
+//    }
+//
+//    // split the sentences into chunks based on the pattern and tokenize them
+//    // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)]
+//    val promptChunks = sentences
+//      .map(s => {
+//        val sentWithTask = s.result
+//        var offsetLength = 0
+//        pattern
+//          .split(sentWithTask)
+//          .zipWithIndex
+//          .map(s => {
+//            val sentenceWithTask = Sentence(
+//              content = s._1,
+//              start = offsetLength,
+//              end = offsetLength + s._1.length,
+//              index = s._2)
+//            offsetLength += s._1.length
+//            bpeTokenizer
+//              .tokenize(sentenceWithTask)
+//              .map(bpeTokenizer.encode)
+//              .flatMap(_.map(_.pieceId))
+//          })
+//      })
+//
+//    // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]]
+//    val tokens = promptChunks
+//      .zip(imgTokenLen)
+//      .map(s => {
+//        val (promptChunk, imgTokenLen) = s
+//        val imgPaddingTokens = Array.fill(imgTokenLen)(imageToken)
+//        val combinedChunks = promptChunk
+//          .map(_.toArray)
+//          .reduce(_ ++ imgPaddingTokens ++ _)
+//        Array(bosTokenId) ++ combinedChunks
+//      })
+
+    val tokens = SentenceSplit
+      .unpack(sentences)
       .map(s => {
-        val (promptChunk, imgTokenLen) = s
-        val imgPaddingTokens = Array.fill(imgTokenLen)(imageToken)
-        val combinedChunks = promptChunk
-          .map(_.toArray)
-          .reduce(_ ++ imgPaddingTokens ++ _)
-        Array(bosTokenId) ++ combinedChunks
+        val sentWithTask = s
+        Array(bosTokenId) ++ bpeTokenizer
+          .tokenize(sentWithTask)
+          .map(bpeTokenizer.encode)
+          .flatMap(_.map(_.pieceId))
       })
-
-    //    val tokens = SentenceSplit
-    //      .unpack(sentences)
-    //      .map(s => {
-    //        val sentWithTask = s
-    //        bpeTokenizer
-    //          .tokenize(sentWithTask)
-    //          .map(bpeTokenizer.encode)
-    //          .flatMap(_.map(_.pieceId))
-    //      })
     tokens
   }
 
-  def encode(
+  private def encode(
       imageAnnotations: Seq[AnnotationImage],
       sentences: Seq[Annotation],
-      preprocessor: Preprocessor,
-      imageTokenLength: Int = imageTokenLength)
-      : (Seq[Array[Int]], Array[Array[Array[Array[Float]]]]) = {
+      preprocessor: Preprocessor): Map[String, Any] = {
     val (preprocessedImages, aspectRatioIds, aspectRatioMask, numTiles) =
       encodeImage(imageAnnotations.toArray, preprocessor, maxImageTiles, paddingConstant)
-    val encodedText = encodeText(sentences, List(imageTokenLength)).toArray
+    val encodedText = encodeText(sentences).toArray
+
+    println(encodedText.map(_.mkString(", ")).mkString("\n"))
+
+    val crossAttentionMask = encodedText.map { sentence =>
+      MllamaUtils.getCrossAttentionTokenMask(sentence, imageToken)
+    }
+    val maxLength = encodedText.map(_.length).max
+    val crossAttentionMaskDense = MllamaUtils.convertSparseCrossAttentionMaskToDense(
+      crossAttentionMask,
+      numTiles.map(_.toArray).toArray,
+      maxImageTiles,
+      maxLength)
+
+    Map(
+      "pixelValues" -> preprocessedImages,
+      "aspectRatioIds" -> aspectRatioIds,
+      "aspectRatioMask" -> aspectRatioMask,
+      "crossAttentionMask" -> crossAttentionMaskDense,
+      "numTiles" -> numTiles,
+      "encodedText" -> encodedText)
 
-    (encodedText, preprocessedImages)
   }
 
   def tag(
-      batch: Seq[Array[Int]],
-      images: Array[Array[Array[Array[Float]]]],
+      inputs: Map[String, Any],
       minOutputLength: Int,
       maxOutputLength: Int,
       doSample: Boolean,
@@ -173,13 +190,11 @@ private[johnsnowlabs] class MLLama(
       maxInputLength: Int,
       stopTokenIds: Array[Int]): Array[Array[Int]] = {
 
-    val pixelValues = images
+    val inputIds = inputs("encodedText").asInstanceOf[Array[Array[Int]]]
     val ignoreTokenIdsInt = ignoreTokenIds
-    val expandedDecoderInputsVals = batch
+    val expandedDecoderInputsVals = inputIds
     val sequencesLength = expandedDecoderInputsVals.map(x => x.length).toArray
     val maxSentenceLength = sequencesLength.max // - curLen
-    //    val pixelValues = images._1
-    //    val imageSizes = images._2
     val numReturn_sequences = 1
     // from config
 
@@ -198,44 +213,54 @@ private[johnsnowlabs] class MLLama(
       openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request()
     val inferRequestVisionEmbeddingsModel =
       openvinoWrapper.get.visionEmbeddingsModel.getCompiledModel().create_infer_request()
-    val inferRequestTextEmbeddingsModel =
-      openvinoWrapper.get.textEmbeddingsModel.getCompiledModel().create_infer_request()
-    val inferRequestMergeModel =
-      openvinoWrapper.get.mergeModel.getCompiledModel().create_infer_request()
+    val inferRequestReshapeModel =
+      openvinoWrapper.get.reshapeModel.getCompiledModel().create_infer_request()
 
     val generatedIds = generateGreedy(
-      batch.toArray,
-      batch.toArray,
-      pixelValues,
+      inputIds,
+      inputIds,
+      inputs,
       maxOutputLength,
       inferRequestLanguageModel,
       inferRequestVisionEmbeddingsModel,
-      inferRequestTextEmbeddingsModel,
-      inferRequestMergeModel)
+      inferRequestReshapeModel)
     generatedIds
   }
 
   def generateGreedy(
       encoderInputIds: Array[Array[Int]],
       decoderInputIds: Array[Array[Int]],
-      pixelValues: Array[Array[Array[Array[Float]]]],
+      inputs: Map[String, Any],
       maxOutputLength: Int,
       inferRequestLanguageModel: InferRequest,
       inferRequestVisionEmbeddingsModel: InferRequest,
-      inferRequestTextEmbeddingsModel: InferRequest,
-      inferRequestMergeModel: InferRequest): Array[Array[Int]] = {
+      inferRequestReshapeModel: InferRequest): Array[Array[Int]] = {
 
     var generatedIds: Array[Array[Int]] = Array()
-    var decoderInputIdsCopied = decoderInputIds
+    var decoderInputIdsCopied = decoderInputIds.clone()
+    val pixelValues =
+      inputs("pixelValues").asInstanceOf[Array[Array[Array[Array[Array[Array[Float]]]]]]]
+    val aspectRatioIds = inputs("aspectRatioIds").asInstanceOf[Array[Array[Int]]]
+    val aspectRatioMask = inputs("aspectRatioMask").asInstanceOf[Array[Array[Array[Int]]]]
+
+    val (crossAttentionOutputNames, crossAttentionKeyValues) = getCrossAttentionKeyValues(
+      encoderInputIds,
+      decoderInputIds,
+      pixelValues,
+      aspectRatioIds,
+      aspectRatioMask,
+      inferRequestVisionEmbeddingsModel)
+
     while (!greedyGenerationFinished(generatedIds, eosTokenId, maxOutputLength)) {
       val decoderOutputs = getModelOutputs(
         encoderInputIds,
         decoderInputIdsCopied,
-        pixelValues,
+        inputs,
+        crossAttentionOutputNames,
+        crossAttentionKeyValues,
         inferRequestLanguageModel,
         inferRequestVisionEmbeddingsModel,
-        inferRequestTextEmbeddingsModel,
-        inferRequestMergeModel)
+        inferRequestReshapeModel)
 
       val nextTokenIds = decoderOutputs.map { scores =>
         argmax(scores)
@@ -256,6 +281,7 @@ private[johnsnowlabs] class MLLama(
           currentIds ++ Array(nextId)
         }
     }
+//    println(generatedIds.map(_.mkString(", ")).mkString("\n"))
     generatedIds
   }
 
@@ -276,10 +302,10 @@ private[johnsnowlabs] class MLLama(
       beamSize: Int,
       maxInputLength: Int): Seq[Annotation] = {
 
-    val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences, preprocessor)
+    val inputs = encode(imageAnnotations, sentences, preprocessor)
+
     val tagged = tag(
-      encodedText,
-      preprocessedImages,
+      inputs,
       minOutputLength,
       maxOutputLength,
       doSample,
@@ -313,21 +339,17 @@ private[johnsnowlabs] class MLLama(
   def getModelOutputs(
       encoderInputIds: Array[Array[Int]],
       decoderInputIds: Array[Array[Int]],
-      pixelValues: Array[Array[Array[Array[Float]]]],
+      inputs: Map[String, Any],
+      crossAttentionOutputNames: Array[String],
+      crossAttentionKeyValues: Array[org.intel.openvino.Tensor],
       inferRequestLanguageModel: InferRequest,
       inferRequestVisionEmbeddingsModel: InferRequest,
-      inferRequestTextEmbeddingsModel: InferRequest,
-      inferRequestMergeModel: InferRequest): Array[Array[Float]] = {
-
-    val inputEmbeds = getMultimodalEmbeddings(
-      encoderInputIds,
-      decoderInputIds,
-      pixelValues,
-      inferRequestVisionEmbeddingsModel,
-      inferRequestTextEmbeddingsModel,
-      inferRequestMergeModel)
-
-    val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) =
+      inferRequestReshapeModel: InferRequest): Array[Array[Float]] = {
+    val crossAttentionMask =
+      inputs("crossAttentionMask").asInstanceOf[Array[Array[Array[Array[Int]]]]]
+    val numTiles = inputs("numTiles").asInstanceOf[List[List[Int]]]
+    val (inputIdsLong, inputPositionIDsLong, crossAttentionMaskDense)
+        : (Array[Long], Array[Long], Array[Array[Array[Array[Int]]]]) =
       if (encoderInputIds.head.length == decoderInputIds.head.length) {
         // First pass
         val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) }
@@ -336,7 +358,7 @@ private[johnsnowlabs] class MLLama(
             i.toLong
           }
         }
-        (inpIdsLong, posIdsLong)
+        (inpIdsLong, posIdsLong, crossAttentionMask)
       } else {
         // Subsequent passes
         val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong }
@@ -345,7 +367,16 @@ private[johnsnowlabs] class MLLama(
             i.toLong
           }.last
         }
-        (inpIdsLong, posIdsLong)
+        val crossAttentionMask = decoderInputIds.map { sentence =>
+          MllamaUtils.getCrossAttentionTokenMask(sentence, imageToken)
+        }
+        val maxLength = decoderInputIds.map(_.length).max
+        val crossAttentionMaskDense = MllamaUtils.convertSparseCrossAttentionMaskToDense(
+          crossAttentionMask,
+          numTiles.map(_.toArray).toArray,
+          maxImageTiles,
+          maxLength)
+        (inpIdsLong, posIdsLong, crossAttentionMaskDense)
       }
     val attentionMask: Array[Long] =
       decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) }
@@ -354,6 +385,9 @@ private[johnsnowlabs] class MLLama(
     val beamIdx: Array[Int] = new Array[Int](batchSize)
     val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize)
 
+    val inputIdsTensor: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(shape, inputIdsLong)
+
     val decoderAttentionMask: org.intel.openvino.Tensor =
       new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask)
     val decoderPositionIDs: org.intel.openvino.Tensor =
@@ -361,15 +395,68 @@ private[johnsnowlabs] class MLLama(
     val beamIdxTensor: org.intel.openvino.Tensor =
       new org.intel.openvino.Tensor(Array(batchSize), beamIdx)
 
-    inferRequestLanguageModel.set_tensor("inputs_embeds", inputEmbeds)
+    val crossAttentionMaskDenseTensor: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(
+        Array(
+          batchSize,
+          crossAttentionMaskDense.head.length,
+          crossAttentionMaskDense.head.head.length,
+          crossAttentionMaskDense.head.head.head.length),
+        crossAttentionMaskDense.flatten.flatten.flatten.map(_.toLong))
+
+    val numVisionTokensTensor: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(Array[Int](), Array(numVisionTokens.toLong))
+
+    val pastCrossAttentionKVLength: org.intel.openvino.Tensor =
+      new org.intel.openvino.Tensor(
+        Array[Int](),
+        Array(
+          crossAttentionKeyValues.head
+            .get_shape()(crossAttentionKeyValues.head.get_shape().length - 2)
+            .toLong))
+    inferRequestReshapeModel.set_tensor("current_input_ids", inputIdsTensor)
+    inferRequestReshapeModel.set_tensor("attention_mask", decoderAttentionMask)
+    inferRequestReshapeModel.set_tensor("cross_attention_mask", crossAttentionMaskDenseTensor)
+    inferRequestReshapeModel.set_tensor("num_vision_tokens", numVisionTokensTensor)
+    inferRequestReshapeModel.set_tensor("past_cross_attn_kv_length", pastCrossAttentionKVLength)
+
+    inferRequestReshapeModel.infer()
+    val crossAttentionMaskReshaped =
+      if (encoderInputIds.head.length == decoderInputIds.head.length) {
+        inferRequestReshapeModel.get_tensor("cross_attention_mask_first_pass")
+      } else {
+        inferRequestReshapeModel.get_tensor("cross_attention_mask_second_pass")
+      }
+    val cachePosition = inferRequestReshapeModel.get_tensor("cache_position")
+    val fullTextRowMaskedOutMask =
+      inferRequestReshapeModel.get_tensor("full_text_row_masked_out_mask")
+
+//    val crossAttentionMaskReshapedTensor: org.intel.openvino.Tensor =
+//      new org.intel.openvino.Tensor(
+//        crossAttentionMaskReshaped.get_shape(),
+//        crossAttentionMaskReshaped.as_int().map(_.toFloat))
+
+    inferRequestLanguageModel.set_tensor("input_ids", inputIdsTensor)
     inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask)
     inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs)
     inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor)
+    inferRequestLanguageModel.set_tensor("cross_attention_mask", crossAttentionMaskReshaped)
+    inferRequestLanguageModel.set_tensor("cache_position", cachePosition)
+    inferRequestLanguageModel.set_tensor(
+      "full_text_row_masked_out_mask",
+      fullTextRowMaskedOutMask)
+
+    for (i <- crossAttentionKeyValues.indices) {
+      inferRequestLanguageModel.set_tensor(
+        crossAttentionOutputNames(i),
+        crossAttentionKeyValues(i))
+    }
 
     inferRequestLanguageModel.infer()
 
     val result = inferRequestLanguageModel.get_tensor("logits")
     val logitsRaw = result.data()
+    val logitShape = result.get_shape()
 
     val sequenceLength = inputIdsLong.length / batchSize
     val decoderOutputs = (0 until batchSize).map(i => {
@@ -409,52 +496,63 @@ private[johnsnowlabs] class MLLama(
       Array[Array[Array[Int]]],
       List[List[Int]]) = {
 
-    val (batchProcessedImages, batchAspectRatios)
-        : (Array[Array[Array[Array[Array[Float]]]]], Array[List[(Int, Int)]]) = annotations.map {
-      annot =>
+    val processed: Array[(Array[Array[Array[Array[Float]]]], List[(Int, Int)])] =
+      annotations.map { annot =>
         val bufferedImage = ImageIOUtils.byteToBufferedImage(
           bytes = annot.result,
           w = annot.width,
           h = annot.height,
           nChannels = annot.nChannels)
 
-        val (resizedImage, (resizedImageHeight, resizedImageWidth)) =
+        val (resizedImage, (numTilesHeight, numTilesWidth)) =
           if (preprocessor.do_resize) {
             MllamaUtils.resizeImage(
               width = preprocessor.size,
               height = preprocessor.size,
               resample = preprocessor.resample,
               maxImageTiles = maxImageTiles)(bufferedImage)
-          } else (bufferedImage, (0, 0))
+          } else (bufferedImage, (annot.height, annot.width))
 
         val paddedImage = MllamaUtils.pad(
           image = resizedImage,
           paddingConstant = paddingConstant,
-          aspectRatio = (resizedImageHeight, resizedImageWidth))
-
-        val normalizedImage =
-          ImageResizeUtils.normalizeAndConvertBufferedImage(
-            img = paddedImage,
-            mean = preprocessor.image_mean,
-            std = preprocessor.image_std,
-            doNormalize = preprocessor.do_normalize,
-            doRescale = preprocessor.do_rescale,
-            rescaleFactor = preprocessor.rescale_factor)
-
-        val normalizedImageBuffer =
-          MllamaUtils.floatArrayToBufferedImage(normalizedImage, preprocessor.rescale_factor)
-        val imageTiles = MllamaUtils.splitToTiles(
-          image = normalizedImageBuffer,
-          numTilesHeight = resizedImageHeight,
-          numTilesWidth = resizedImageWidth)
-
-        val aspectRatioList: List[(Int, Int)] = List((resizedImageHeight, resizedImageWidth))
+          aspectRatio = (numTilesHeight, numTilesWidth),
+          tileHeight = preprocessor.size,
+          tileWidth = preprocessor.size)
+
+//        val normalizedImage =
+//          ImageResizeUtils.normalizeAndConvertBufferedImage(
+//            img = paddedImage,
+//            mean = preprocessor.image_mean,
+//            std = preprocessor.image_std,
+//            doNormalize = preprocessor.do_normalize,
+//            doRescale = preprocessor.do_rescale,
+//            rescaleFactor = preprocessor.rescale_factor)
+
+//        val normalizedImageBuffer =
+//          MllamaUtils.floatArrayToBufferedImage(normalizedImage, preprocessor.rescale_factor)
+
+        val imageTiles: Array[Array[Array[Array[Float]]]] = MllamaUtils.splitToTiles(
+          image = paddedImage,
+          numTilesHeight = numTilesHeight,
+          numTilesWidth = numTilesWidth,
+          mean = preprocessor.image_mean,
+          std = preprocessor.image_std,
+          doNormalize = preprocessor.do_normalize,
+          doRescale = preprocessor.do_rescale,
+          rescaleFactor = preprocessor.rescale_factor)
+
+        val aspectRatioList: List[(Int, Int)] = List((numTilesHeight, numTilesWidth))
 
         (imageTiles, aspectRatioList)
-    }
+      }
+
+    val (batchProcessedImages, batchAspectRatios) = processed.unzip
 
     val (images, numTiles) =
-      MllamaUtils.packImages(batchImages = batchProcessedImages, maxImageTiles = maxImageTiles)
+      MllamaUtils.packImages(
+        batchImages = List(batchProcessedImages),
+        maxImageTiles = maxImageTiles)
 
     val aspectRatioIds: Array[Array[Int]] =
       MllamaUtils.convertAspectRatiosToIds(
@@ -468,13 +566,24 @@ private[johnsnowlabs] class MLLama(
 
   }
 
-  def getMultimodalEmbeddings(
+  def getCrossAttentionKeyValues(
       encoderInputIds: Array[Array[Int]],
       decoderInputIds: Array[Array[Int]],
-      pixelValues: Array[Array[Array[Array[Float]]]],
-      inferRequestVisionEmbeddingsModel: InferRequest,
-      inferRequestTextEmbeddingsModel: InferRequest,
-      inferRequestMergeModel: InferRequest): org.intel.openvino.Tensor = {
+      pixelValues: Array[Array[Array[Array[Array[Array[Float]]]]]],
+      aspectRatioIds: Array[Array[Int]],
+      aspectRatioMask: Array[Array[Array[Int]]],
+      inferRequestVisionEmbeddingsModel: InferRequest)
+      : (Array[String], Array[org.intel.openvino.Tensor]) = {
+
+    // filter out the cross attention output names only containing the word "cross_attn_key_values"
+    val crossAttentionOutputNames =
+      openvinoWrapper.get.visionEmbeddingsModel
+        .getCompiledModel()
+        .outputs()
+        .asScala
+        .filter(_.get_any_name().contains("cross_attn_key_values"))
+        .map(_.get_any_name())
+        .toArray
     val inputIdsLong: Array[Long] =
       if (encoderInputIds.head.length == decoderInputIds.head.length) {
         // First pass
@@ -488,49 +597,51 @@ private[johnsnowlabs] class MLLama(
       }
     val batchSize: Int = decoderInputIds.length
     val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize)
-    val inputIdsLongTensor: org.intel.openvino.Tensor =
-      new org.intel.openvino.Tensor(shape, inputIdsLong)
 
-    val imageEmbeddings: org.intel.openvino.Tensor =
+    val crossAttentionKeyValues: Array[org.intel.openvino.Tensor] =
       if (encoderInputIds.head.length == decoderInputIds.head.length) {
+        val pixelValuesShape = Array(
+          pixelValues.length,
+          pixelValues.head.length,
+          pixelValues.head.head.length,
+          pixelValues.head.head.head.length,
+          pixelValues.head.head.head.head.length,
+          pixelValues.head.head.head.head.head.length)
         val pixelValuesTensor: org.intel.openvino.Tensor =
           new org.intel.openvino.Tensor(
-            Array(batchSize, 3, 336, 336),
-            pixelValues.flatten.flatten.flatten.map(_.toFloat))
-
-        // Get image embeddings
-        inferRequestVisionEmbeddingsModel.set_input_tensor(pixelValuesTensor)
-
-        inferRequestVisionEmbeddingsModel.infer()
-
-        val imageEmbeddings = inferRequestVisionEmbeddingsModel.get_output_tensor()
+            pixelValuesShape,
+            pixelValues.flatten.flatten.flatten.flatten.flatten.map(_.toFloat))
 
-        // Get text embeddings
-        inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor)
+        val aspectRatioIdsShape = Array(aspectRatioIds.length, aspectRatioIds.head.length)
+        val aspectRatioIdsTensor: org.intel.openvino.Tensor =
+          new org.intel.openvino.Tensor(aspectRatioIdsShape, aspectRatioIds.flatten.map(_.toLong))
 
-        inferRequestTextEmbeddingsModel.infer()
+        val aspectRatioMaskShape = Array(
+          aspectRatioMask.length,
+          aspectRatioMask.head.length,
+          aspectRatioMask.head.head.length)
 
-        val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor()
+        val aspectRatioMaskTensor: org.intel.openvino.Tensor = new org.intel.openvino.Tensor(
+          aspectRatioMaskShape,
+          aspectRatioMask.flatten.flatten.map(_.toLong))
 
-        // Merge image and text embeddings
-        inferRequestMergeModel.set_tensor("vision_embeds", imageEmbeddings)
-        inferRequestMergeModel.set_tensor("inputs_embeds", textEmbeddings)
-        inferRequestMergeModel.set_tensor("input_ids", inputIdsLongTensor)
+        // Get image embeddings
+        inferRequestVisionEmbeddingsModel.set_tensor("pixel_values", pixelValuesTensor)
+        inferRequestVisionEmbeddingsModel.set_tensor("aspect_ratio_ids", aspectRatioIdsTensor)
+        inferRequestVisionEmbeddingsModel.set_tensor("aspect_ratio_mask", aspectRatioMaskTensor)
 
-        inferRequestMergeModel.infer()
+        inferRequestVisionEmbeddingsModel.infer()
 
-        inferRequestMergeModel.get_tensor("final_embedding")
+        val crossAttentionKeyValues = crossAttentionOutputNames.map { outputName =>
+          inferRequestVisionEmbeddingsModel.get_tensor(outputName)
+        }
+        crossAttentionKeyValues
       } else {
-        // Get text embeddings
-        inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor)
-
-        inferRequestTextEmbeddingsModel.infer()
-
-        val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor()
-
-        textEmbeddings
+        // shouldn't be called
+        throw new IllegalArgumentException("Should not be called for subsequent passes")
+        Array()
       }
-    imageEmbeddings
+    (crossAttentionOutputNames, crossAttentionKeyValues)
   }
 
 }
diff --git a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala
index efeed5f9398275..48ae691cf86521 100644
--- a/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala
+++ b/src/main/scala/com/johnsnowlabs/ml/openvino/OpenvinoWrapper.scala
@@ -220,5 +220,6 @@ object OpenvinoWrapper {
   case class EncoderDecoderWithoutPastWrappers(encoder: OpenvinoWrapper, decoder: OpenvinoWrapper)
   case class MLLamaWrappers(
       visionEmbeddingsModel: OpenvinoWrapper,
-      languageModel: OpenvinoWrapper)
+      languageModel: OpenvinoWrapper,
+      reshapeModel: OpenvinoWrapper)
 }
diff --git a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala
index cd0761f0f9daa3..403613a13c0901 100644
--- a/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala
+++ b/src/main/scala/com/johnsnowlabs/ml/util/LoadExternalModel.scala
@@ -18,6 +18,7 @@ package com.johnsnowlabs.ml.util
 
 import com.johnsnowlabs.ml.tensorflow.sentencepiece.SentencePieceWrapper
 import com.johnsnowlabs.nlp.util.io.{ExternalResource, ReadAs, ResourceHelper}
+import org.glassfish.jersey.internal.inject.Custom
 
 import java.io.File
 import java.nio.file.Paths
@@ -103,22 +104,40 @@ object LoadExternalModel {
 
   }
 
-  def isOpenvinoModel(modelPath: String, isEncoderDecoder: Boolean): Boolean = {
-    if (isEncoderDecoder) {
-      val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml")
-      val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin")
-      val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml")
-      val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin")
-      val ovDecoderModelWithPastXml = new File(modelPath, s"${Openvino.decoderModelWithPast}.xml")
-      val ovDecoderModelWithPastBin = new File(modelPath, s"${Openvino.decoderModelWithPast}.bin")
-
-      ovEncoderModelXml.exists() && ovEncoderModelBin.exists() &&
-      ovDecoderModelXml.exists() && ovDecoderModelBin.exists() &&
-      ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists()
+  def isOpenvinoModel(
+      modelPath: String,
+      isEncoderDecoder: Boolean,
+      custom: Option[List[String]] = None): Boolean = {
+
+    if (custom.isDefined) {
+      for (model <- custom.get) {
+        val ovModelXml = new File(modelPath, s"${model}.xml")
+        val ovModelBin = new File(modelPath, s"${model}.bin")
+        if (!ovModelXml.exists() || !ovModelBin.exists()) {
+          println(s"Model $model not found in $modelPath")
+          return false
+        }
+      }
+      true
     } else {
-      val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml")
-      val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin")
-      modelXml.exists() && modelBin.exists()
+      if (isEncoderDecoder) {
+        val ovEncoderModelXml = new File(modelPath, s"${Openvino.encoderModel}.xml")
+        val ovEncoderModelBin = new File(modelPath, s"${Openvino.encoderModel}.bin")
+        val ovDecoderModelXml = new File(modelPath, s"${Openvino.decoderModel}.xml")
+        val ovDecoderModelBin = new File(modelPath, s"${Openvino.decoderModel}.bin")
+        val ovDecoderModelWithPastXml =
+          new File(modelPath, s"${Openvino.decoderModelWithPast}.xml")
+        val ovDecoderModelWithPastBin =
+          new File(modelPath, s"${Openvino.decoderModelWithPast}.bin")
+
+        ovEncoderModelXml.exists() && ovEncoderModelBin.exists() &&
+        ovDecoderModelXml.exists() && ovDecoderModelBin.exists() &&
+        ovDecoderModelWithPastXml.exists() && ovDecoderModelWithPastBin.exists()
+      } else {
+        val modelXml = new File(modelPath, s"${Openvino.ovModel}.xml")
+        val modelBin = new File(modelPath, s"${Openvino.ovModel}.bin")
+        modelXml.exists() && modelBin.exists()
+      }
     }
   }
 
@@ -126,7 +145,8 @@ object LoadExternalModel {
       modelPath: String,
       isEncoderDecoder: Boolean = false,
       withPast: Boolean = false,
-      isDecoder: Boolean = false): String = {
+      isDecoder: Boolean = false,
+      custom: Option[List[String]] = None): String = {
 
     /** Check if the path is correct */
     val f = new File(modelPath)
@@ -146,7 +166,7 @@ object LoadExternalModel {
     val onnxModelExist = isOnnxModel(modelPath, isEncoderDecoder, withPast, isDecoder)
 
     /*Openvino required model files*/
-    val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder)
+    val openvinoModelExist = isOpenvinoModel(modelPath, isEncoderDecoder, custom)
 
     if (tfSavedModelExist) {
       TensorFlow.name
@@ -176,10 +196,11 @@ object LoadExternalModel {
       path: String,
       isEncoderDecoder: Boolean = false,
       withPast: Boolean = false,
-      isDecoder: Boolean = false): (String, String) = {
+      isDecoder: Boolean = false,
+      custom: Option[List[String]] = None): (String, String) = {
     val localPath: String = ResourceHelper.copyToLocal(path)
 
-    (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder))
+    (localPath, detectEngine(localPath, isEncoderDecoder, withPast, isDecoder, custom))
   }
 
   def loadTextAsset(assetPath: String, assetName: String): Array[String] = {
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala
new file mode 100644
index 00000000000000..85c4670be118d4
--- /dev/null
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala
@@ -0,0 +1,636 @@
+/*
+ * Copyright 2017-2024 John Snow Labs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.johnsnowlabs.nlp.annotators.cv
+
+import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig
+import com.johnsnowlabs.ml.ai.MLLama
+import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers
+import com.johnsnowlabs.ml.util.LoadExternalModel.{
+  loadJsonStringAsset,
+  loadTextAsset,
+  modelSanityCheck,
+  notSupportedEngineError
+}
+import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor
+import com.johnsnowlabs.ml.util.Openvino
+import com.johnsnowlabs.nlp.AnnotatorType.{DOCUMENT, IMAGE}
+import com.johnsnowlabs.nlp._
+import org.json4s.{DefaultFormats, JValue}
+import org.json4s.jackson.JsonMethods.parse
+import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOpenvinoModel}
+import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.MLLamaWrappers
+import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature}
+import org.apache.spark.broadcast.Broadcast
+import org.apache.spark.ml.param.{IntArrayParam, IntParam}
+import org.apache.spark.ml.util.Identifiable
+import org.apache.spark.sql.SparkSession
+
+/** MLLamaForMultimodal can load LLAVA Vision models for visual question answering. The model
+  * consists of a vision encoder, a text encoder as well as a text decoder. The vision encoder
+  * will encode the input image, the text encoder will encode the input question together with the
+  * encoding of the image, and the text decoder will output the answer to the question.
+  *
+  * Pretrained models can be loaded with `pretrained` of the companion object:
+  * {{{
+  * val visualQA = MLLamaForMultimodal.pretrained()
+  *   .setInputCols("image_assembler")
+  *   .setOutputCol("answer")
+  * }}}
+  * The default model is `"mllama"`, if no name is provided.
+  *
+  * For available pretrained models please see the
+  * [[https://sparknlp.org/models?task=Question+Answering Models Hub]].
+  *
+  * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To
+  * see which models are compatible and how to import them see
+  * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended
+  * examples, see
+  * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTest.scala]].
+  *
+  * ==Example==
+  * {{{
+  * import spark.implicits._
+  * import com.johnsnowlabs.nlp.base._
+  * import com.johnsnowlabs.nlp.annotator._
+  * import org.apache.spark.ml.Pipeline
+  *
+  * val imageDF: DataFrame = ResourceHelper.spark.read
+  *  .format("image")
+  *  .option("dropInvalid", value = true)
+  *  .load(imageFolder)
+  *
+  * val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n <|image|> \nWhat is unusual on this picture? \n ASSISTANT:\n"))
+  *
+  * val imageAssembler: ImageAssembler = new ImageAssembler()
+  *   .setInputCol("image")
+  *   .setOutputCol("image_assembler")
+  *
+  * val visualQAClassifier = MLLamaForMultimodal.pretrained()
+  *   .setInputCols("image_assembler")
+  *   .setOutputCol("answer")
+  *
+  * val pipeline = new Pipeline().setStages(Array(
+  *   imageAssembler,
+  *   visualQAClassifier
+  * ))
+  *
+  * val result = pipeline.fit(testDF).transform(testDF)
+  *
+  * result.select("image_assembler.origin", "answer.result").show(false)
+  * +--------------------------------------+------+
+  * |origin                                |result|
+  * +--------------------------------------+------+
+  * |[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]|
+  * +--------------------------------------+------+
+  * }}}
+  *
+  * @see
+  *   [[CLIPForZeroShotClassification]] for Zero Shot Image Classifier
+  * @see
+  *   [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer
+  *   based classifiers
+  * @param uid
+  *   required uid for storing annotator to disk
+  * @groupname anno Annotator types
+  * @groupdesc anno
+  *   Required input and expected output annotator types
+  * @groupname Ungrouped Members
+  * @groupname param Parameters
+  * @groupname setParam Parameter setters
+  * @groupname getParam Parameter getters
+  * @groupname Ungrouped Members
+  * @groupprio param  1
+  * @groupprio anno  2
+  * @groupprio Ungrouped 3
+  * @groupprio setParam  4
+  * @groupprio getParam  5
+  * @groupdesc param
+  *   A list of (hyper-)parameter keys this annotator can take. Users can set and get the
+  *   parameter values through setters and getters, respectively.
+  */
+
+class MLLamaForMultimodal(override val uid: String)
+    extends AnnotatorModel[MLLamaForMultimodal]
+    with HasBatchedAnnotateImage[MLLamaForMultimodal]
+    with HasImageFeatureProperties
+    with WriteOpenvinoModel
+    with HasGeneratorProperties
+    with HasEngine {
+
+  /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator
+    * type
+    */
+  def this() = this(Identifiable.randomUID("MLLamaForMultimodal"))
+
+  /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator
+    * type
+    */
+  override val inputAnnotatorTypes: Array[AnnotatorType] = Array(IMAGE)
+  override val outputAnnotatorType: AnnotatorType = DOCUMENT
+
+  /** @group setParam */
+  def setRandomSeed(value: Int): MLLamaForMultimodal.this.type = {
+    if (randomSeed.isEmpty) {
+      this.randomSeed = Some(value)
+    }
+    this
+  }
+
+  /** A list of token ids which are ignored in the decoder's output (Default: `Array()`)
+    *
+    * @group param
+    */
+  var ignoreTokenIds = new IntArrayParam(
+    this,
+    "ignoreTokenIds",
+    "A list of token ids which are ignored in the decoder's output")
+
+  /** @group setParam */
+  def setIgnoreTokenIds(tokenIds: Array[Int]): MLLamaForMultimodal.this.type = {
+    set(ignoreTokenIds, tokenIds)
+  }
+
+  /** @group getParam */
+  def getIgnoreTokenIds: Array[Int] = $(ignoreTokenIds)
+
+  /** Vocabulary used to encode the words to ids with bpeTokenizer.encode
+    *
+    * @group param
+    */
+  val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected()
+
+  /** @group setParam */
+  def setVocabulary(value: Map[String, Int]): this.type = set(vocabulary, value)
+
+  /** Holding merges.txt coming from RoBERTa model
+    *
+    * @group param
+    */
+  val merges: MapFeature[(String, String), Int] = new MapFeature(this, "merges").setProtected()
+
+  /** @group setParam */
+  def setMerges(value: Map[(String, String), Int]): this.type = set(merges, value)
+
+  /** Additional tokens to be added to the vocabulary
+    *
+    * @group param
+    */
+  val addedTokens: MapFeature[String, Int] = new MapFeature(this, "addedTokens").setProtected()
+
+  /** @group setParam */
+  def setAddedTokens(value: Map[String, Int]): this.type = set(addedTokens, value)
+
+  /** Stop tokens to terminate the generation
+    *
+    * @group param
+    */
+  override val stopTokenIds =
+    new IntArrayParam(this, "stopTokenIds", "Stop tokens to terminate the generation")
+
+  /** @group setParam */
+  override def setStopTokenIds(value: Array[Int]): this.type = {
+    set(stopTokenIds, value)
+  }
+
+  /** @group getParam */
+  override def getStopTokenIds: Array[Int] = $(stopTokenIds)
+
+  private var _model: Option[Broadcast[MLLama]] = None
+  val generationConfig: StructFeature[GenerationConfig] =
+    new StructFeature(this, "generationConfig").setProtected()
+
+  def setGenerationConfig(value: GenerationConfig): this.type =
+    set(generationConfig, value)
+
+  def getGenerationConfig: GenerationConfig = $$(generationConfig)
+
+  val imageToken =
+    new IntParam(this, "imageToken", "Token id for image embeddings")
+
+  /** @group setParam */
+  def setImageToken(value: Int): this.type = set(imageToken, value)
+
+  /** @group getParam */
+  def getImageToken: Int = $(imageToken)
+
+  val maxImageTiles =
+    new IntParam(this, "maxImageTiles", "Maximum number of image tiles")
+
+  /** @group setParam */
+  def setMaxImageTiles(value: Int): this.type = set(maxImageTiles, value)
+
+  /** @group getParam */
+  def getMaxImageTiles: Int = $(maxImageTiles)
+
+  val numVisionTokens =
+    new IntParam(this, "numVisionTokens", "Number of vision tokens")
+
+  /** @group setParam */
+  def setNumVisionTokens(value: Int): this.type = set(numVisionTokens, value)
+
+  /** @group getParam */
+  def getNumVisionTokens: Int = $(numVisionTokens)
+
+  val paddingConstant =
+    new IntParam(this, "paddingConstant", "Padding constant for the model. Default is 0")
+
+  /** @group setParam */
+  def setPaddingConstant(value: Int): this.type = set(paddingConstant, value)
+
+  /** @group getParam */
+  def getPaddingConstant: Int = $(paddingConstant)
+
+  /** @group setParam */
+  def setModelIfNotSet(
+      spark: SparkSession,
+      preprocessor: Preprocessor,
+      onnxWrappers: Option[DecoderWrappers],
+      openvinoWrapper: Option[MLLamaWrappers]): this.type = {
+    if (_model.isEmpty) {
+      _model = Some(
+        spark.sparkContext.broadcast(
+          new MLLama(
+            onnxWrappers,
+            openvinoWrapper,
+            $$(merges),
+            $$(vocabulary),
+            $$(addedTokens),
+            preprocessor,
+            generationConfig = getGenerationConfig,
+            imageToken = getImageToken,
+            maxImageTiles = getMaxImageTiles,
+            numVisionTokens = getNumVisionTokens,
+            paddingConstant = getPaddingConstant)))
+    }
+    this
+  }
+
+  /** @group getParam */
+  def getModelIfNotSet: MLLama = _model.get.value
+
+  setDefault(
+    minOutputLength -> 0,
+    maxOutputLength -> 20,
+    doSample -> false,
+    temperature -> 0.6,
+    topK -> -1,
+    topP -> 0.9,
+    repetitionPenalty -> 1.0,
+    noRepeatNgramSize -> 3,
+    ignoreTokenIds -> Array(),
+    batchSize -> 1,
+    beamSize -> 1,
+    maxInputLength -> 4096,
+    stopTokenIds -> Array(128001, 128008, 128009),
+    imageToken -> 128256,
+    maxImageTiles -> 576,
+    numVisionTokens -> 32000,
+    paddingConstant -> 1601)
+
+  /** takes a document and annotations and produces new annotations of this annotator's annotation
+    * type
+    *
+    * @param batchedAnnotations
+    *   Annotations in batches that correspond to inputAnnotationCols generated by previous
+    *   annotators if any
+    * @return
+    *   any number of annotations processed for every batch of input annotations. Not necessary
+    *   one to one relationship
+    */
+  override def batchAnnotate(
+      batchedAnnotations: Seq[Array[AnnotationImage]]): Seq[Seq[Annotation]] = {
+
+    batchedAnnotations
+      //      .filter { annotationImages =>
+      //        annotationImages.exists(_.text.nonEmpty)
+      //      }
+      .map { cleanAnnotationImages =>
+        val validImages = cleanAnnotationImages.filter(_.result.nonEmpty)
+        val questionAnnotations = extractInputAnnotation(validImages)
+
+        getModelIfNotSet.predict(
+          questionAnnotations,
+          validImages.toSeq,
+          batchSize = $(batchSize),
+          minOutputLength = $(minOutputLength),
+          maxOutputLength = $(maxOutputLength),
+          doSample = $(doSample),
+          temperature = $(temperature),
+          topK = $(topK),
+          topP = $(topP),
+          repetitionPenalty = $(repetitionPenalty),
+          noRepeatNgramSize = $(noRepeatNgramSize),
+          randomSeed = this.randomSeed,
+          ignoreTokenIds = $(ignoreTokenIds),
+          beamSize = $(beamSize),
+          maxInputLength = $(maxInputLength))
+      }
+  }
+
+  private def extractInputAnnotation(
+      annotationImages: Array[AnnotationImage]): Seq[Annotation] = {
+    val questions = annotationImages.map(annotationImage => {
+      val imageText =
+        if (annotationImage.text.nonEmpty) annotationImage.text
+        else
+          "<|user|> \n <|image|> This is an image\n <|end|>\n <|assistant|>\n" // default question
+      Annotation(imageText)
+    })
+
+    questions
+  }
+
+  override def onWrite(path: String, spark: SparkSession): Unit = {
+    super.onWrite(path, spark)
+    getEngine match {
+      case Openvino.name =>
+        val wrappers = getModelIfNotSet.openvinoWrapper
+        writeOpenvinoModels(
+          path,
+          spark,
+          Seq(
+            (
+              wrappers.get.languageModel,
+              "llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.xml")),
+          MLLamaForMultimodal.suffix)
+
+        writeOpenvinoModels(
+          path,
+          spark,
+          Seq((wrappers.get.visionEmbeddingsModel, "openvino_vision_encoder.xml")),
+          MLLamaForMultimodal.suffix)
+
+        writeOpenvinoModels(
+          path,
+          spark,
+          Seq((wrappers.get.reshapeModel, "openvino_reshape_model.xml")),
+          MLLamaForMultimodal.suffix)
+      case _ =>
+        throw new Exception(notSupportedEngineError)
+    }
+  }
+
+}
+
+trait ReadablePretrainedMLLamaForMultimodal
+    extends ParamsAndFeaturesReadable[MLLamaForMultimodal]
+    with HasPretrained[MLLamaForMultimodal] {
+
+  override val defaultModelName: Some[String] = Some("mllama")
+
+  /** Java compliant-overrides */
+  override def pretrained(): MLLamaForMultimodal = super.pretrained()
+
+  override def pretrained(name: String): MLLamaForMultimodal =
+    super.pretrained(name)
+
+  override def pretrained(name: String, lang: String): MLLamaForMultimodal =
+    super.pretrained(name, lang)
+
+  override def pretrained(name: String, lang: String, remoteLoc: String): MLLamaForMultimodal =
+    super.pretrained(name, lang, remoteLoc)
+
+}
+
+trait ReadMLLamaForMultimodalDLModel extends ReadOpenvinoModel {
+  this: ParamsAndFeaturesReadable[MLLamaForMultimodal] =>
+  val suffix: String = "_mllama"
+  override val openvinoFile: String = "mllama_openvino"
+  def readModel(instance: MLLamaForMultimodal, path: String, spark: SparkSession): Unit = {
+    instance.getEngine match {
+      case Openvino.name =>
+        val languageModelWrappers =
+          readOpenvinoModels(
+            path,
+            spark,
+            Seq("llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.xml"),
+            suffix)
+
+        val visionEmbeddingsModelWrappers =
+          readOpenvinoModels(path, spark, Seq("openvino_vision_encoder.xml"), suffix)
+
+        val reshapeModelWrapper =
+          readOpenvinoModels(path, spark, Seq("openvino_reshape_model.xml"), suffix)
+
+        val ovWrapper = MLLamaWrappers(
+          languageModel = languageModelWrappers(
+            "llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.xml"),
+          visionEmbeddingsModel = visionEmbeddingsModelWrappers("openvino_vision_encoder.xml"),
+          reshapeModel = reshapeModelWrapper("openvino_reshape_model.xml"))
+        val preprocessor = Preprocessor(
+          do_normalize = true,
+          do_resize = true,
+          "LLAVAFeatureExtractor",
+          instance.getImageMean,
+          instance.getImageStd,
+          instance.getResample,
+          instance.getSize)
+        instance.setModelIfNotSet(spark, preprocessor, None, Some(ovWrapper))
+      case _ => {
+        throw new Exception(notSupportedEngineError)
+      }
+    }
+  }
+
+  addReader(readModel)
+
+  def loadSavedModel(
+      modelPath: String,
+      spark: SparkSession,
+      useOpenvino: Boolean = false): MLLamaForMultimodal = {
+    implicit val formats: DefaultFormats.type = DefaultFormats // for json4
+    val (localModelPath, detectedEngine) =
+      modelSanityCheck(
+        modelPath,
+        isDecoder = false,
+        custom = Some(
+          List(
+            "llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers",
+            "openvino_vision_encoder",
+            "openvino_reshape_model")))
+    val modelConfig: JValue =
+      parse(loadJsonStringAsset(localModelPath, "config.json"))
+    val preprocessorConfigJsonContent =
+      loadJsonStringAsset(localModelPath, "preprocessor_config.json")
+    val preprocessorConfig = Preprocessor.loadPreprocessorConfig(preprocessorConfigJsonContent)
+
+    val parsedPreprocessorConfig: JValue = parse(preprocessorConfigJsonContent)
+    val beginSuppressTokens: Array[Int] =
+      (modelConfig \ "begin_suppress_tokens").extract[Array[Int]]
+
+    val suppressTokenIds: Array[Int] =
+      (modelConfig \ "suppress_tokens").extract[Array[Int]]
+
+    val forcedDecoderIds: Array[(Int, Int)] =
+      (modelConfig \ "forced_decoder_ids").extract[Array[Array[Int]]].map {
+        case idxWithTokenId: Array[Int] if idxWithTokenId.length == 2 =>
+          (idxWithTokenId(0), idxWithTokenId(1))
+        case _ =>
+          throw new Exception(
+            "Could not extract forced_decoder_ids. Should be a list of tuples with 2 entries.")
+      }
+
+    val maxImageTiles = (parsedPreprocessorConfig \ "max_image_tiles").extract[Int]
+
+    def arrayOrNone[T](array: Array[T]): Option[Array[T]] =
+      if (array.nonEmpty) Some(array) else None
+
+    val generationConfig: JValue =
+      parse(loadJsonStringAsset(localModelPath, "generation_config.json"))
+    val bosTokenId = (generationConfig \ "bos_token_id").extract[Int]
+    val eosTokenIdArray = (generationConfig \ "eos_token_id").extract[Array[Int]]
+    val eosTokenId = eosTokenIdArray.head
+    val padTokenId = (generationConfig \ "pad_token_id").extract[Int]
+    val vocabSize = (modelConfig \ "text_config" \ "vocab_size").extract[Int]
+
+    val imageToken = (modelConfig \ "image_token_index").extract[Int]
+    val imageSize = (modelConfig \ "vision_config" \ "image_size").extract[Int]
+    val patchSize = (modelConfig \ "vision_config" \ "patch_size").extract[Int]
+
+    val numVisionTokens = Math
+      .pow(imageSize / patchSize, 2)
+      .toInt + 1
+
+//    val numVisionTokens = Math
+//      .pow(
+//        ((modelConfig \ "vision_config" \ "image_size")
+//          .extract[Int] / (modelConfig \ "vision_config" \ "patch_size").extract[Int]).toInt,
+//        2)
+//      .toInt + 1
+
+    // Check if tokenizer.json exists
+    val tokenizerPath = s"$localModelPath/assets/tokenizer.json"
+    val tokenizerExists = new java.io.File(tokenizerPath).exists()
+    val (vocabs, addedTokens, bytePairs) = if (tokenizerExists) {
+      val tokenizerConfig: JValue = parse(loadJsonStringAsset(localModelPath, "tokenizer.json"))
+      // extract vocab from tokenizer.json ( model -> vocab)
+      var vocabs: Map[String, Int] =
+        (tokenizerConfig \ "model" \ "vocab").extract[Map[String, Int]]
+
+      // extract merges from tokenizer.json ( model -> merges)
+      val bytePairs = (tokenizerConfig \ "model" \ "merges")
+        .extract[List[Array[String]]]
+        .filter(w => w.length == 2)
+        .map { case Array(c1, c2) => (c1, c2) }
+        .zipWithIndex
+        .toMap
+
+      // extract added_tokens from tokenizer.json (added_tokens)
+      // "added_tokens": [
+      //    {
+      //      "id": 128000,
+      //      "content": "<|begin_of_text|>",
+      //      "single_word": false,
+      //      "lstrip": false,
+      //      "rstrip": false,
+      //      "normalized": false,
+      //      "special": true
+      //    }, ...
+      //  ]
+      val addedTokens = (tokenizerConfig \ "added_tokens")
+        .extract[List[Map[String, Any]]]
+        .map { token =>
+          val id = token("id").asInstanceOf[BigInt].intValue()
+          val content = token("content").asInstanceOf[String]
+          (content, id)
+        }
+        .toMap
+
+      // update vocab with added tokens
+      addedTokens.foreach { case (content, id) =>
+        vocabs += (content -> id)
+      }
+      (vocabs, addedTokens, bytePairs)
+    } else {
+      val vocabs = loadTextAsset(localModelPath, "vocab.txt").zipWithIndex.toMap
+      val addedTokens = loadTextAsset(localModelPath, "added_tokens.txt").zipWithIndex.toMap
+      val bytePairs = loadTextAsset(localModelPath, "merges.txt")
+        .map(_.split(" "))
+        .filter(w => w.length == 2)
+        .map { case Array(c1, c2) => (c1, c2) }
+        .zipWithIndex
+        .toMap
+      (vocabs, addedTokens, bytePairs)
+    }
+//    val vocabSize = vocabs.size
+    val annotatorModel = new MLLamaForMultimodal()
+      .setGenerationConfig(
+        GenerationConfig(
+          bosTokenId,
+          padTokenId,
+          eosTokenId,
+          vocabSize,
+          arrayOrNone(beginSuppressTokens),
+          arrayOrNone(suppressTokenIds),
+          arrayOrNone(forcedDecoderIds)))
+      .setVocabulary(vocabs)
+      .setMerges(bytePairs)
+      .setAddedTokens(addedTokens)
+      .setImageToken(imageToken)
+      .setMaxImageTiles(maxImageTiles)
+      .setNumVisionTokens(numVisionTokens)
+
+    val modelEngine =
+      if (useOpenvino)
+        Openvino.name
+      else
+        detectedEngine
+    annotatorModel.set(annotatorModel.engine, modelEngine)
+
+    detectedEngine match {
+      case Openvino.name =>
+        val visionWrapper =
+          OpenvinoWrapper.read(
+            spark,
+            localModelPath,
+            zipped = false,
+            useBundle = true,
+            detectedEngine = detectedEngine,
+            modelName = "openvino_vision_encoder")
+        val reshapeWrapper =
+          OpenvinoWrapper.read(
+            spark,
+            localModelPath,
+            zipped = false,
+            useBundle = true,
+            detectedEngine = detectedEngine,
+            modelName = "openvino_reshape_model")
+        val languageModelWrapper =
+          OpenvinoWrapper.read(
+            spark,
+            localModelPath,
+            zipped = false,
+            useBundle = true,
+            detectedEngine = detectedEngine,
+            modelName = "llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers")
+
+        val openvinoWrapper = MLLamaWrappers(
+          languageModel = languageModelWrapper,
+          visionEmbeddingsModel = visionWrapper,
+          reshapeModel = reshapeWrapper)
+        annotatorModel.setModelIfNotSet(spark, preprocessorConfig, None, Some(openvinoWrapper))
+      case _ =>
+        throw new Exception(notSupportedEngineError)
+    }
+
+    annotatorModel
+  }
+}
+
+object MLLamaForMultimodal
+    extends ReadablePretrainedMLLamaForMultimodal
+    with ReadMLLamaForMultimodalDLModel
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
index 691149bfe9fa0f..175745723b3854 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala
@@ -4,8 +4,6 @@ import scala.collection.mutable.ListBuffer
 import java.awt.image.BufferedImage
 import scala.collection.mutable.ArrayBuffer
 import ImageResizeUtils.resizeBufferedImage
-import scala.collection.mutable.ArrayBuffer
-import scala.math.max
 
 object MllamaUtils {
 
@@ -49,9 +47,9 @@ object MllamaUtils {
     val scaleW = targetWidth.toDouble / imageWidth.toDouble
 
     if (scaleW < scaleH) {
-      (targetWidth, math.min(math.floor(imageHeight * scaleW).toInt, targetHeight))
+      (math.min(math.floor(imageHeight * scaleW).toInt, targetHeight), targetWidth)
     } else {
-      (math.min(math.floor(imageWidth * scaleH).toInt, targetWidth), targetHeight)
+      (targetHeight, math.min(math.floor(imageWidth * scaleH).toInt, targetWidth))
     }
   }
 
@@ -73,8 +71,8 @@ object MllamaUtils {
       (w * tileSize, h * tileSize)
     }
 
-    val targetHeights = possibleCanvasSizes.map(_._1)
-    val targetWidths = possibleCanvasSizes.map(_._2)
+    val targetHeights = possibleCanvasSizes.map(_._2)
+    val targetWidths = possibleCanvasSizes.map(_._1)
 
     val scaleH = targetHeights.map(_.toDouble / imageHeight.toDouble)
     val scaleW = targetWidths.map(_.toDouble / imageWidth.toDouble)
@@ -88,10 +86,10 @@ object MllamaUtils {
       scales.filter(_ < 1.0).max
     }
 
-    val chosenCanvas = possibleCanvasSizes.filter { case (_, h) =>
-      (h.toDouble / imageHeight.toDouble == selectedScale) ||
-      (h.toDouble / imageWidth.toDouble == selectedScale)
-    }
+    val chosenCanvas =
+      possibleCanvasSizes.zip(scales).filter { case (_, s) => s == selectedScale }.map {
+        case (size, _) => size
+      }
 
     if (chosenCanvas.size > 1) {
       chosenCanvas.minBy { case (w, h) => w * h }
@@ -133,7 +131,12 @@ object MllamaUtils {
   def splitToTiles(
       image: BufferedImage,
       numTilesHeight: Int,
-      numTilesWidth: Int): Array[Array[Array[Array[Float]]]] = {
+      numTilesWidth: Int,
+      mean: Array[Double],
+      std: Array[Double],
+      doNormalize: Boolean,
+      doRescale: Boolean,
+      rescaleFactor: Double): Array[Array[Array[Array[Float]]]] = {
     val cropHeight = image.getHeight / numTilesHeight
     val cropWidth = image.getWidth / numTilesWidth
 
@@ -141,16 +144,23 @@ object MllamaUtils {
 
     for (i <- 0 until numTilesHeight) {
       for (j <- 0 until numTilesWidth) {
-        // Extract a crop of 336x336
+        // Extract a crop of the image
         val imgCrop = image.getSubimage(j * cropHeight, i * cropWidth, cropHeight, cropWidth)
-        // Convert the crop to a 3D array (3, 336, 336)
-        val cropArray = imageCropToArray(imgCrop)
+        // Convert the crop to a 3D array (3, height, width)
+//        val cropArray = imageCropToArray(imgCrop)
+        val normalizedCrop = ImageResizeUtils.normalizeAndConvertBufferedImage(
+          img = imgCrop,
+          mean = mean,
+          std = std,
+          doNormalize = doNormalize,
+          doRescale = doRescale,
+          rescaleFactor = rescaleFactor)
 
         // Normalize the crop if the option is enabled
-        val normalizedCrop = {
-          // Convert Int array to Double array if normalization is off
-          cropArray.map(_.map(_.map(_.toFloat / 255.0.toFloat)))
-        }
+//        val normalizedCrop = {
+//          // Convert Int array to Double array if normalization is off
+//          cropArray.map(_.map(_.map(_.toFloat / 255.0.toFloat)))
+//        }
 
         cropsBuffer.append(normalizedCrop)
       }
@@ -207,14 +217,13 @@ object MllamaUtils {
     * @return
     */
   def packImages(
-      batchImages: Array[Array[Array[Array[Array[Float]]]]],
+      batchImages: List[Array[Array[Array[Array[Array[Float]]]]]],
       maxImageTiles: Int): (Array[Array[Array[Array[Array[Array[Float]]]]]], List[List[Int]]) = {
     val batchSize = batchImages.size
     val maxNumImages = batchImages.map(_.length).max
-
-    val channels = batchImages.head.head.length
-    val tileHeight = batchImages.head.head.head.length
-    val tileWidth = batchImages.head.head.head.head.length
+    val channels = batchImages.head.head.head.length
+    val tileHeight = batchImages.head.head.head.head.length
+    val tileWidth = batchImages.head.head.head.head.head.length
 
     // (batch_size, max_num_images, max_image_tiles, channels, tile_height, tile_width).
     val stackedImages = ArrayBuffer[Array[Array[Array[Array[Array[Float]]]]]]()
@@ -234,7 +243,7 @@ object MllamaUtils {
         for {
           k <- 0 until numTiles
         } {
-          tempStackedTiles.append(image)
+          tempStackedTiles.append(image(k))
         }
         // add padded images to the sample
         for (_ <- 0 until maxImageTiles - image.length) {
@@ -271,6 +280,7 @@ object MllamaUtils {
     val batchSize = aspectRatios.size
     val maxNumImages = aspectRatios.map(_.size).max
 
+    // Initialize the 3D array with zeros
     val aspectRatioMask = Array.ofDim[Int](batchSize, maxNumImages, maxImageTiles)
 
     // Set the first tile to 1 for all aspect ratios
@@ -281,10 +291,14 @@ object MllamaUtils {
       aspectRatioMask(i)(j)(0) = 1
     }
 
+    // Set the aspect ratio mask for the rest of the tiles
     for ((sampleAspectRatios, i) <- aspectRatios.zipWithIndex) {
-      for ((numTilesW, numTilesH) <- sampleAspectRatios) {
-        for (k <- 0 until numTilesW * numTilesH) {
-          aspectRatioMask(i)(numTilesH)(k) = 1
+      for ((aspectRatio, j) <- sampleAspectRatios.zipWithIndex) {
+        val (numTilesW, numTilesH) = aspectRatio
+        val numTiles = numTilesW * numTilesH
+
+        for (k <- 0 until math.min(numTiles, maxImageTiles)) {
+          aspectRatioMask(i)(j)(k) = 1
         }
       }
     }
@@ -308,7 +322,9 @@ object MllamaUtils {
 
     for ((row, i) <- aspectRatios.zipWithIndex) {
       if (row.nonEmpty) {
-        aspectRatiosStacked(i).take(row.size) = row.map(t => Array(t._1, t._2))
+        for ((aspectRatio, j) <- row.zipWithIndex) {
+          aspectRatiosStacked(i)(j) = Array(aspectRatio._1, aspectRatio._2)
+        }
       }
     }
 
@@ -353,14 +369,15 @@ object MllamaUtils {
     val imageHeight = image.getHeight
     val imageWidth = image.getWidth
 
-    val (canvasWidth, canvasHeight) =
+    val (canvasHeight, canvasWidth) =
       getOptimalTiledCanvas(imageHeight, imageWidth, maxImageTiles, height)
 
     val numTilesHeight = canvasHeight / height
     val numTilesWidth = canvasWidth / width
-    (
-      resizeBufferedImage(canvasWidth, canvasHeight, resample)(image),
-      (numTilesHeight, numTilesWidth))
+
+    val (newHeight, newWidth) =
+      getImageSizeFitToCanvas(imageHeight, imageWidth, canvasHeight, canvasWidth, height)
+    (resizeBufferedImage(newWidth, newHeight, resample)(image), (numTilesHeight, numTilesWidth))
   }
 
   def padConstant(
@@ -390,36 +407,51 @@ object MllamaUtils {
 
   def padBufferedImage(
       image: BufferedImage,
-      padding: (Int, Int),
+      totalPadding: (Int, Int),
       constantColor: Int): BufferedImage = {
     val originalWidth = image.getWidth
     val originalHeight = image.getHeight
 
-    val paddedWidth = originalWidth + 2 * padding._2
-    val paddedHeight = originalHeight + 2 * padding._1
+    val (totalPaddingHeight, totalPaddingWidth) = totalPadding
+
+    // Calculate padding on each side
+    val paddingWidthLeft = totalPaddingWidth
+    val paddingHeightTop = totalPaddingHeight
+
+    val paddedWidth = originalWidth + totalPaddingWidth
+    val paddedHeight = originalHeight + totalPaddingHeight
 
     val paddedImage = new BufferedImage(paddedWidth, paddedHeight, image.getType)
 
+    val colorRGB = new java.awt.Color(0, 0, 0)
+
     for (x <- 0 until paddedWidth; y <- 0 until paddedHeight) {
-      if (x >= padding._2 && x < originalWidth + padding._2 && y >= padding._1 && y < originalHeight + padding._1) {
-        paddedImage.setRGB(x, y, image.getRGB(x - padding._2, y - padding._1))
+      if (x < originalWidth
+        &&
+        y < originalHeight) {
+        paddedImage.setRGB(x, y, image.getRGB(x, y))
       } else {
-        paddedImage.setRGB(x, y, constantColor)
+        paddedImage.setRGB(x, y, colorRGB.getRGB)
       }
     }
 
     paddedImage
   }
 
-  def pad(image: BufferedImage, paddingConstant: Int, aspectRatio: (Int, Int)): BufferedImage = {
+  def pad(
+      image: BufferedImage,
+      paddingConstant: Int,
+      aspectRatio: (Int, Int),
+      tileHeight: Int,
+      tileWidth: Int): BufferedImage = {
     val originalWidth = image.getWidth
     val originalHeight = image.getHeight
 
     val numTilesHeight = aspectRatio._1
     val numTilesWidth = aspectRatio._2
 
-    val paddedWidth = numTilesWidth * originalWidth
-    val paddedHeight = numTilesHeight * originalHeight
+    val paddedWidth = numTilesWidth * tileWidth
+    val paddedHeight = numTilesHeight * tileHeight
 
     val paddingHeight = paddedHeight - originalHeight
     val paddingWidth = paddedWidth - originalWidth
@@ -457,27 +489,32 @@ object MllamaUtils {
     val batchSize = crossAttentionTokenMask.length
     val maxNumImages = crossAttentionTokenMask.map(_.length).max
 
+    // Initialize the 4D array with zeros
     val crossAttentionMask = Array.ofDim[Int](batchSize, length, maxNumImages, maxNumTiles)
 
-    for {
-      sampleIdx <- crossAttentionTokenMask.indices
-      (sampleMasks, sampleNumTiles) <- crossAttentionTokenMask(sampleIdx)
-        .zip(numTiles(sampleIdx))
-        .zipWithIndex
-      (locations, maskNumTiles) <- sampleMasks.zip(sampleNumTiles).zipWithIndex
-      if locations.length == 2
-    } {
-      val (start, end) = (locations(0), locations(1))
-      val effectiveEnd = if (end == -1) length else math.min(end, length)
-      for {
-        i <- start until effectiveEnd
-        j <- 0 until maskNumTiles
-      } {
-        crossAttentionMask(sampleIdx)(i)(maskIdx)(j) = 1
+    for (sampleIdx <- crossAttentionTokenMask.indices) {
+      val sampleMasks = crossAttentionTokenMask(sampleIdx)
+      val sampleNumTiles = numTiles(sampleIdx)
+
+      for (maskIdx <- sampleMasks.indices) {
+        val locations = sampleMasks(maskIdx)
+        val maskNumTiles = sampleNumTiles(maskIdx)
+
+        if (locations.length == 2) {
+          val start = locations(0)
+          var end = locations(1)
+
+          // Handle the case where `end == -1`
+          if (end == -1) end = length
+          end = math.min(end, length)
+
+          for (i <- start until end; j <- 0 until maskNumTiles) {
+            crossAttentionMask(sampleIdx)(i)(maskIdx)(j) = 1
+          }
+        }
       }
     }
 
     crossAttentionMask
   }
-
 }
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala
index 137b5fb437763c..7aba09606e1e28 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/BpeTokenizer.scala
@@ -319,7 +319,8 @@ object BpeTokenizer {
       padWithSequenceTokens: Boolean = false,
       addPrefixSpaceToSentence: Boolean = false,
       specialTokens: Option[SpecialTokens] = None,
-      alwaysAddPrefix: Boolean = true): BpeTokenizer = {
+      alwaysAddPrefix: Boolean = true,
+      prependString: String = ""): BpeTokenizer = {
 
     def modelSpecialTokens() = specialTokens match {
       case Some(specialTok) => specialTok
@@ -388,7 +389,9 @@ object BpeTokenizer {
           vocab,
           modelSpecialTokens(),
           padWithSequenceTokens,
-          addPrefixSpaceToSentence = addPrefixSpaceToSentence)
+          addPrefixSpaceToSentence = addPrefixSpaceToSentence,
+          alwaysAddPrefix = alwaysAddPrefix,
+          prependString = prependString)
       case _ =>
         throw new IllegalArgumentException("Model type \"" + modelType + "\" not supported yet.")
     }
diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala
index b2b31b95ef2c7c..ee3cfe9be15173 100644
--- a/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala
+++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/tokenizer/bpe/MLLamaTokenizer.scala
@@ -30,9 +30,9 @@ class MLLamaTokenizer(
     padWithSequenceTokens: Boolean = true,
     prependString: String = "",
     addPrefixSpaceToSentence: Boolean = false,
-    alwaysAddPrefix: Boolean = true,
+    alwaysAddPrefix: Boolean = false,
     splitPatternRegex: Regex =
-      raw"""(?i)(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".r)
+      raw"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+""".r)
     extends BpeTokenizer(
       merges,
       vocab,
@@ -84,28 +84,38 @@ class MLLamaTokenizer(
     // split pattern based on gpt2's bpe tokenizer
     splitPattern
       .findAllMatchIn(if (prefixForPieceId.isDefined || text.startsWith(" ")) text
-      else " " + text) // Prepend space to the beginning of text
+      else text) // Prepend space to the beginning of text
       .map(tok => IndexedToken(tok.matched, tok.start + indexOffset, tok.end + indexOffset - 1))
       .toArray
   }
 
+//  def decodeTokens(tokens: Array[Int]): String = {
+//    val decoded = new mutable.StringBuilder()
+//    tokens.foreach { token =>
+//      {
+//        val decodedToken = decoderVocab(token)
+//        if (!specialTokens.contains(decodedToken)) {
+//          if (decodedToken.startsWith("<0x") && decodedToken.endsWith(">")) {
+//            val strippedHex = decodedToken.replaceAll("<0x|>", "")
+//            val byteValue = Integer.parseInt(strippedHex, 16)
+//            decoded.append(byteValue.toChar)
+//          } else {
+//            decoded.append(decodedToken)
+//          }
+//        }
+//      }
+//
+//    }
+//    decoded.toString().replaceAll(decoderVocab(29871), " ").trim()
+//  }
   def decodeTokens(tokens: Array[Int]): String = {
-    val decoded = new mutable.StringBuilder()
-    tokens.foreach { token =>
-      {
-        val decodedToken = decoderVocab(token)
-        if (!specialTokens.contains(decodedToken)) {
-          if (decodedToken.startsWith("<0x") && decodedToken.endsWith(">")) {
-            val strippedHex = decodedToken.replaceAll("<0x|>", "")
-            val byteValue = Integer.parseInt(strippedHex, 16)
-            decoded.append(byteValue.toChar)
-          } else {
-            decoded.append(decodedToken)
-          }
-        }
-      }
+    val text = tokens
+      .map(token => decoderVocab(token))
+      .filter(x => !specialTokens.contains(x))
+      .mkString("")
 
-    }
-    decoded.toString().replaceAll(decoderVocab(29871), " ").trim()
+    val bytes =
+      text.map(x => unicodeToByteMapping(x.toString)).map(x => x.toByte).toArray
+    new String(bytes, Charset.forName("UTF-8"))
   }
 }
diff --git a/src/test/resources/images/demo.jpeg b/src/test/resources/images/demo.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..9fdc040050624556464ffa5112dde397ccd792c6
GIT binary patch
literal 496395
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

b#|8Akr6q0}+q%$Qk{CO^tP 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 z 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

    ^=uNjw*GxuS{YH@o6;?~( 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

    x9M;npyQp#(X99 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 X5rZ?v!)yU4=wr!^xL{B+^YQ-x|7O;r literal 0 HcmV?d00001 diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala new file mode 100644 index 00000000000000..4a2fb1d8db7721 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala @@ -0,0 +1,191 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.nlp.annotators.cv + +import com.johnsnowlabs.nlp.base.LightPipeline +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, ImageAssembler} +import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit +import org.scalatest.flatspec.AnyFlatSpec + +class MLLamaForMultimodalTestSpec extends AnyFlatSpec { + + lazy val model = getLLAVAForMultiModalPipelineModel + + "LLAVAForMultiModal" should "answer a question for a given image" taggedAs SlowTest in { + + val testDF = getTestDF + val result = model.transform(testDF) + + val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") + + answerAnnotation.foreach { annotation => + annotation.foreach(a => assert(a.result.nonEmpty)) + } + + answerAnnotation.foreach { annotation => + annotation.foreach(a => println(a.result)) + } + + } + + it should "work with light pipeline annotate" taggedAs FastTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/images/image1.jpg" + val resultAnnotate = + lightPipeline.annotate( + imagePath, + "<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n") + println(s"resultAnnotate: $resultAnnotate") + + assert(resultAnnotate("answer").head.contains("cat")) + } + + it should "work with light pipeline full annotate" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/images/bluetick.jpg" + val resultFullAnnotate = + lightPipeline.fullAnnotateImage( + imagePath, + "<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n") + + val answerAnnotation = resultFullAnnotate("answer").head.asInstanceOf[Annotation] + + println(s"imageName.result: ${answerAnnotation.result}") + assert(answerAnnotation.result.nonEmpty) + } + + it should "fullAnnotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n" + val questions = Array(question, "", question) + + val resultFullAnnotate = lightPipeline.fullAnnotateImages(imagesPath, questions) + + resultFullAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { iAnnotation => + val annotation = iAnnotation.asInstanceOf[Annotation] + assert( + annotation.result.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + } + + it should "annotate with empty Map when a text is empty" taggedAs SlowTest in { + val lightPipeline = new LightPipeline(model) + val imagesPath = Array( + "src/test/resources/image/bluetick.jpg", + "src/test/resources/image/chihuahua.jpg", + "src/test/resources/image/egyptian_cat.jpeg") + val question = + "<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n" + val questions = Array(question, "", question) + + val resultAnnotate = lightPipeline.annotate(imagesPath, questions) + + resultAnnotate.foreach { annotate => + println(s"annotate: $annotate") + } + + resultAnnotate.zip(imagesPath).foreach { case (annotateMap, imagePath) => + imagePath match { + case "src/test/resources/image/chihuahua.jpg" => + // For the chihuahua image, the annotateMap should be empty because the question is empty + assert( + annotateMap.nonEmpty, + s"Expected empty map for image: $imagePath, but got: $annotateMap") + + case _ => + assert(annotateMap.nonEmpty, s"Expected non-empty map for image: $imagePath") + + annotateMap.get("answer") match { + case Some(annotations) => + annotations.foreach { annotation => + assert( + annotation.nonEmpty, + s"Expected non-empty result for image: $imagePath, but got empty result") + } + case None => + fail(s"'answer' key not found in annotateMap for image: $imagePath") + } + } + } + + } + + private def getLLAVAForMultiModalPipelineModel = { + val testDF = getTestDF + + val imageAssembler: ImageAssembler = new ImageAssembler() + .setInputCol("image") + .setOutputCol("image_assembler") + + val loadModel = MLLamaForMultimodal + .loadSavedModel( + "/mnt/research/Projects/ModelZoo/LLAMA-3.2-VI/Llama-3.2-11B-Vision-Instruct/OV", + ResourceHelper.spark) + .setInputCols("image_assembler") + .setOutputCol("answer") + .setMaxOutputLength(50) + + val newPipeline: Pipeline = + new Pipeline().setStages(Array(imageAssembler, loadModel)) + + newPipeline.fit(testDF) + } + + private def getTestDF: DataFrame = { + val imageFolder = "src/test/resources/images/" + val imageDF: DataFrame = ResourceHelper.spark.read + .format("image") + .option("dropInvalid", value = true) + .load(imageFolder) + + val testDF: DataFrame = imageDF.withColumn( + "text", + lit( + "<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n")) + + testDF + } + +} From 46fe9076b09c94af03fc1286acea815402f3b7d3 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 21 Jan 2025 05:49:43 +0000 Subject: [PATCH 063/108] MLLama scala api changes Signed-off-by: Prabod Rathnayaka --- .../scala/com/johnsnowlabs/ml/ai/MLLama.scala | 102 +++++++----------- .../annotators/cv/MLLamaForMultimodal.scala | 3 +- .../cv/util/transform/MllamaUtils.scala | 7 -- .../cv/MLLamaForMultimodalTestSpec.scala | 8 +- 4 files changed, 44 insertions(+), 76 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala index 39d7b7dccc9d01..54098dfd40f7ec 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala @@ -90,47 +90,12 @@ private[johnsnowlabs] class MLLama( */ def encodeText(sentences: Seq[Annotation]): Seq[Array[Int]] = { -// val pattern = raw"<\|image\|>".r -// -// // raise an error if the pattern is not found in the text -// if (pattern.findFirstIn(sentences.head.result).isEmpty) { -// throw new IllegalArgumentException("The pattern <\\|image\\|> is not found in the text") -// } -// -// // split the sentences into chunks based on the pattern and tokenize them -// // eg in python prompt_chunks = [self.tokenizer(chunk).input_ids for chunk in re.split(pattern, texts)] -// val promptChunks = sentences -// .map(s => { -// val sentWithTask = s.result -// var offsetLength = 0 -// pattern -// .split(sentWithTask) -// .zipWithIndex -// .map(s => { -// val sentenceWithTask = Sentence( -// content = s._1, -// start = offsetLength, -// end = offsetLength + s._1.length, -// index = s._2) -// offsetLength += s._1.length -// bpeTokenizer -// .tokenize(sentenceWithTask) -// .map(bpeTokenizer.encode) -// .flatMap(_.map(_.pieceId)) -// }) -// }) -// -// // inject the image padding tokens of length imgTokenLen between the prompt chunks and reduce the Seq[Array[Array[Int]]] to Seq[Array[Int]] -// val tokens = promptChunks -// .zip(imgTokenLen) -// .map(s => { -// val (promptChunk, imgTokenLen) = s -// val imgPaddingTokens = Array.fill(imgTokenLen)(imageToken) -// val combinedChunks = promptChunk -// .map(_.toArray) -// .reduce(_ ++ imgPaddingTokens ++ _) -// Array(bosTokenId) ++ combinedChunks -// }) + val pattern = raw"<\|image\|>".r + + // raise an error if the pattern is not found in the text + if (pattern.findFirstIn(sentences.head.result).isEmpty) { + throw new IllegalArgumentException("The pattern <\\|image\\|> is not found in the text") + } val tokens = SentenceSplit .unpack(sentences) @@ -152,7 +117,7 @@ private[johnsnowlabs] class MLLama( encodeImage(imageAnnotations.toArray, preprocessor, maxImageTiles, paddingConstant) val encodedText = encodeText(sentences).toArray - println(encodedText.map(_.mkString(", ")).mkString("\n")) +// println(encodedText.map(_.mkString(", ")).mkString("\n")) val crossAttentionMask = encodedText.map { sentence => MllamaUtils.getCrossAttentionTokenMask(sentence, imageToken) @@ -456,7 +421,6 @@ private[johnsnowlabs] class MLLama( val result = inferRequestLanguageModel.get_tensor("logits") val logitsRaw = result.data() - val logitShape = result.get_shape() val sequenceLength = inputIdsLong.length / batchSize val decoderOutputs = (0 until batchSize).map(i => { @@ -468,10 +432,24 @@ private[johnsnowlabs] class MLLama( decoderOutputs.toArray } - private def argmax(scores: Array[Float]): Int = - scores.zipWithIndex.maxBy { case (score, _) => - score - }._2 + private def argmax(scores: Array[Float]): Int = { + // Validate that the array is not empty + require(scores.nonEmpty, "Input array must not be empty") + + // Initialize variables to track the maximum score and its index + var maxIndex = 0 + var maxValue = scores(0) + + // Iterate through the array to find the maximum value and its index + for (i <- 1 until scores.length) { + if (scores(i) > maxValue) { + maxValue = scores(i) + maxIndex = i + } + } + + maxIndex + } private def greedyGenerationFinished( decoderIds: Seq[Array[Int]], @@ -584,19 +562,6 @@ private[johnsnowlabs] class MLLama( .filter(_.get_any_name().contains("cross_attn_key_values")) .map(_.get_any_name()) .toArray - val inputIdsLong: Array[Long] = - if (encoderInputIds.head.length == decoderInputIds.head.length) { - // First pass - val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } - - inpIdsLong - } else { - // Subsequent passes - val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } - inpIdsLong - } - val batchSize: Int = decoderInputIds.length - val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) val crossAttentionKeyValues: Array[org.intel.openvino.Tensor] = if (encoderInputIds.head.length == decoderInputIds.head.length) { @@ -610,7 +575,7 @@ private[johnsnowlabs] class MLLama( val pixelValuesTensor: org.intel.openvino.Tensor = new org.intel.openvino.Tensor( pixelValuesShape, - pixelValues.flatten.flatten.flatten.flatten.flatten.map(_.toFloat)) + pixelValues.flatten.flatten.flatten.flatten.flatten) val aspectRatioIdsShape = Array(aspectRatioIds.length, aspectRatioIds.head.length) val aspectRatioIdsTensor: org.intel.openvino.Tensor = @@ -632,9 +597,18 @@ private[johnsnowlabs] class MLLama( inferRequestVisionEmbeddingsModel.infer() - val crossAttentionKeyValues = crossAttentionOutputNames.map { outputName => - inferRequestVisionEmbeddingsModel.get_tensor(outputName) - } + val crossAttentionKeyValues: Array[org.intel.openvino.Tensor] = + crossAttentionOutputNames.map { outputName => + inferRequestVisionEmbeddingsModel.get_tensor(outputName) + } +// crossAttentionKeyValues.zip(crossAttentionOutputNames).foreach { +// case (value, name) => { +// println(s"Name: $name") +// println(s"Shape: ${value.get_shape().mkString(", ")}") +// println(s"Values: ${value.data().sum}") +// } +// } + // return the cross attention output names and the key values crossAttentionKeyValues } else { // shouldn't be called diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala index 85c4670be118d4..ba34eefaaf565d 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala @@ -347,7 +347,8 @@ class MLLamaForMultimodal(override val uid: String) val imageText = if (annotationImage.text.nonEmpty) annotationImage.text else - "<|user|> \n <|image|> This is an image\n <|end|>\n <|assistant|>\n" // default question + """<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n""" + + """\n<|image|>This is an image<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n""".stripMargin // default question Annotation(imageText) }) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala index 175745723b3854..f9e6710aa6d2d1 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/transform/MllamaUtils.scala @@ -147,7 +147,6 @@ object MllamaUtils { // Extract a crop of the image val imgCrop = image.getSubimage(j * cropHeight, i * cropWidth, cropHeight, cropWidth) // Convert the crop to a 3D array (3, height, width) -// val cropArray = imageCropToArray(imgCrop) val normalizedCrop = ImageResizeUtils.normalizeAndConvertBufferedImage( img = imgCrop, mean = mean, @@ -156,12 +155,6 @@ object MllamaUtils { doRescale = doRescale, rescaleFactor = rescaleFactor) - // Normalize the crop if the option is enabled -// val normalizedCrop = { -// // Convert Int array to Double array if normalization is off -// cropArray.map(_.map(_.map(_.toFloat / 255.0.toFloat))) -// } - cropsBuffer.append(normalizedCrop) } } diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala index 4a2fb1d8db7721..c62044404d7522 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala @@ -27,9 +27,9 @@ import org.scalatest.flatspec.AnyFlatSpec class MLLamaForMultimodalTestSpec extends AnyFlatSpec { - lazy val model = getLLAVAForMultiModalPipelineModel + lazy val model = getMLLamaForMultiModalPipelineModel - "LLAVAForMultiModal" should "answer a question for a given image" taggedAs SlowTest in { + "MLLamaForMultiModal" should "answer a question for a given image" taggedAs SlowTest in { val testDF = getTestDF val result = model.transform(testDF) @@ -46,7 +46,7 @@ class MLLamaForMultimodalTestSpec extends AnyFlatSpec { } - it should "work with light pipeline annotate" taggedAs FastTest in { + it should "work with light pipeline annotate" taggedAs SlowTest in { val lightPipeline = new LightPipeline(model) val imagePath = "src/test/resources/images/image1.jpg" val resultAnnotate = @@ -152,7 +152,7 @@ class MLLamaForMultimodalTestSpec extends AnyFlatSpec { } - private def getLLAVAForMultiModalPipelineModel = { + private def getMLLamaForMultiModalPipelineModel = { val testDF = getTestDF val imageAssembler: ImageAssembler = new ImageAssembler() From c19f4eb348774ffcdd9289f10d074c25c65b5608 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 23 Jan 2025 07:46:45 +0000 Subject: [PATCH 064/108] MLLama python api Signed-off-by: Prabod Rathnayaka --- python/sparknlp/annotator/cv/__init__.py | 3 +- .../annotator/cv/mllama_for_multimodal.py | 340 ++++++++++++++++++ python/sparknlp/internal/__init__.py | 8 + .../cv/mllama_for_multimodal_test.py | 82 +++++ .../scala/com/johnsnowlabs/ml/ai/MLLama.scala | 97 +++-- .../annotators/cv/MLLamaForMultimodal.scala | 15 +- .../cv/MLLamaForMultimodalTestSpec.scala | 4 +- 7 files changed, 488 insertions(+), 61 deletions(-) create mode 100644 python/sparknlp/annotator/cv/mllama_for_multimodal.py create mode 100644 python/test/annotator/cv/mllama_for_multimodal_test.py diff --git a/python/sparknlp/annotator/cv/__init__.py b/python/sparknlp/annotator/cv/__init__.py index 37eeaf696bb2a8..08d5051ef12cf8 100644 --- a/python/sparknlp/annotator/cv/__init__.py +++ b/python/sparknlp/annotator/cv/__init__.py @@ -16,4 +16,5 @@ from sparknlp.annotator.cv.convnext_for_image_classification import * from sparknlp.annotator.cv.vision_encoder_decoder_for_image_captioning import * from sparknlp.annotator.cv.clip_for_zero_shot_classification import * -from sparknlp.annotator.cv.blip_for_question_answering import * \ No newline at end of file +from sparknlp.annotator.cv.blip_for_question_answering import * +from sparknlp.annotator.cv.mllama_for_multimodal import * \ No newline at end of file diff --git a/python/sparknlp/annotator/cv/mllama_for_multimodal.py b/python/sparknlp/annotator/cv/mllama_for_multimodal.py new file mode 100644 index 00000000000000..a62e2ee99ab264 --- /dev/null +++ b/python/sparknlp/annotator/cv/mllama_for_multimodal.py @@ -0,0 +1,340 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sparknlp.common import * + +class MLLamaForMultimodal(AnnotatorModel, + HasBatchedAnnotateImage, + HasImageFeatureProperties, + HasEngine, + HasCandidateLabelsProperties, + HasRescaleFactor): + """ +MLLamaForMultimodal can load LLAMA 3.2 Vision models for visual question answering. +The model consists of a vision encoder, a text encoder, and a text decoder. +The vision encoder encodes the input image, the text encoder processes the input question +alongside the image encoding, and the text decoder generates the answer to the question. + +The Llama 3.2-Vision collection comprises pretrained and instruction-tuned multimodal large +language models (LLMs) available in 11B and 90B sizes. These models are optimized for visual +recognition, image reasoning, captioning, and answering general questions about images. +The models outperform many open-source and proprietary multimodal models on standard industry +benchmarks. + +Pretrained models can be loaded with :meth:`.pretrained` of the companion object: + +>>> visualQAClassifier = MLLamaForMultimodal.pretrained() \\ +... .setInputCols(["image_assembler"]) \\ +... .setOutputCol("answer") + +The default model is `"mllama"`, if no name is provided. + +For available pretrained models, refer to the `Models Hub +`__. + +Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. +To check compatibility and learn how to import them, see `Import Transformers into Spark NLP ๐Ÿš€ +`_. For extended examples, refer to +the `MLLamaForMultimodal Test Suite +`_. + +====================== ====================== +Input Annotation types Output Annotation type +====================== ====================== +``IMAGE`` ``DOCUMENT`` +====================== ====================== + +Parameters +---------- +batchSize : int, optional + Batch size. Larger values allow faster processing but require more memory, + by default 2. +configProtoBytes : bytes, optional + ConfigProto from TensorFlow, serialized into a byte array. +maxSentenceLength : int, optional + Maximum sentence length to process, by default 50. + +Examples +-------- +>>> import sparknlp +>>> from sparknlp.base import * +>>> from sparknlp.annotator import * +>>> from pyspark.ml import Pipeline +>>> from pyspark.sql.functions import lit +>>> image_df = SparkSessionForTest.spark.read.format("image").load(path=images_path) +>>> test_df = image_df.withColumn( +... "text", +... lit("<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n") +... ) +>>> imageAssembler = ImageAssembler() \\ +... .setInputCol("image") \\ +... .setOutputCol("image_assembler") +>>> visualQAClassifier = MLLamaForMultimodal.pretrained() \\ +... .setInputCols("image_assembler") \\ +... .setOutputCol("answer") +>>> pipeline = Pipeline().setStages([ +... imageAssembler, +... visualQAClassifier +... ]) +>>> result = pipeline.fit(test_df).transform(test_df) +>>> result.select("image_assembler.origin", "answer.result").show(truncate=False) ++--------------------------------------+----------------------------------------------------------------------+ +|origin |result | ++--------------------------------------+----------------------------------------------------------------------+ +|[file:///content/images/cat_image.jpg]|[The unusual aspect of this picture is the presence of two cats lying on a pink couch]| ++--------------------------------------+----------------------------------------------------------------------+ +""" + + + name = "MLLamaForMultimodal" + + inputAnnotatorTypes = [AnnotatorType.IMAGE] + + outputAnnotatorType = AnnotatorType.DOCUMENT + + configProtoBytes = Param(Params._dummy(), + "configProtoBytes", + "ConfigProto from tensorflow, serialized into byte array. Get with " + "config_proto.SerializeToString()", + TypeConverters.toListInt) + + minOutputLength = Param(Params._dummy(), "minOutputLength", "Minimum length of the sequence to be generated", + typeConverter=TypeConverters.toInt) + + maxOutputLength = Param(Params._dummy(), "maxOutputLength", "Maximum length of output text", + typeConverter=TypeConverters.toInt) + + doSample = Param(Params._dummy(), "doSample", "Whether or not to use sampling; use greedy decoding otherwise", + typeConverter=TypeConverters.toBoolean) + + temperature = Param(Params._dummy(), "temperature", "The value used to module the next token probabilities", + typeConverter=TypeConverters.toFloat) + + topK = Param(Params._dummy(), "topK", + "The number of highest probability vocabulary tokens to keep for top-k-filtering", + typeConverter=TypeConverters.toInt) + + topP = Param(Params._dummy(), "topP", + "If set to float < 1, only the most probable tokens with probabilities that add up to ``top_p`` or higher are kept for generation", + typeConverter=TypeConverters.toFloat) + + repetitionPenalty = Param(Params._dummy(), "repetitionPenalty", + "The parameter for repetition penalty. 1.0 means no penalty. See `this paper `__ for more details", + typeConverter=TypeConverters.toFloat) + + noRepeatNgramSize = Param(Params._dummy(), "noRepeatNgramSize", + "If set to int > 0, all ngrams of that size can only occur once", + typeConverter=TypeConverters.toInt) + + ignoreTokenIds = Param(Params._dummy(), "ignoreTokenIds", + "A list of token ids which are ignored in the decoder's output", + typeConverter=TypeConverters.toListInt) + beamSize = Param(Params._dummy(), "beamSize", + "The Number of beams for beam search.", + typeConverter=TypeConverters.toInt) + + def setMaxSentenceSize(self, value): + """Sets Maximum sentence length that the annotator will process, by + default 50. + + Parameters + ---------- + value : int + Maximum sentence length that the annotator will process + """ + return self._set(maxSentenceLength=value) + + def setIgnoreTokenIds(self, value): + """A list of token ids which are ignored in the decoder's output. + + Parameters + ---------- + value : List[int] + The words to be filtered out + """ + return self._set(ignoreTokenIds=value) + + def setConfigProtoBytes(self, b): + """Sets configProto from tensorflow, serialized into byte array. + + Parameters + ---------- + b : List[int] + ConfigProto from tensorflow, serialized into byte array + """ + return self._set(configProtoBytes=b) + + def setMinOutputLength(self, value): + """Sets minimum length of the sequence to be generated. + + Parameters + ---------- + value : int + Minimum length of the sequence to be generated + """ + return self._set(minOutputLength=value) + + def setMaxOutputLength(self, value): + """Sets maximum length of output text. + + Parameters + ---------- + value : int + Maximum length of output text + """ + return self._set(maxOutputLength=value) + + def setDoSample(self, value): + """Sets whether or not to use sampling, use greedy decoding otherwise. + + Parameters + ---------- + value : bool + Whether or not to use sampling; use greedy decoding otherwise + """ + return self._set(doSample=value) + + def setTemperature(self, value): + """Sets the value used to module the next token probabilities. + + Parameters + ---------- + value : float + The value used to module the next token probabilities + """ + return self._set(temperature=value) + + def setTopK(self, value): + """Sets the number of highest probability vocabulary tokens to keep for + top-k-filtering. + + Parameters + ---------- + value : int + Number of highest probability vocabulary tokens to keep + """ + return self._set(topK=value) + + def setTopP(self, value): + """Sets the top cumulative probability for vocabulary tokens. + + If set to float < 1, only the most probable tokens with probabilities + that add up to ``topP`` or higher are kept for generation. + + Parameters + ---------- + value : float + Cumulative probability for vocabulary tokens + """ + return self._set(topP=value) + + def setRepetitionPenalty(self, value): + """Sets the parameter for repetition penalty. 1.0 means no penalty. + + Parameters + ---------- + value : float + The repetition penalty + + References + ---------- + See `Ctrl: A Conditional Transformer Language Model For Controllable + Generation `__ for more details. + """ + return self._set(repetitionPenalty=value) + + def setNoRepeatNgramSize(self, value): + """Sets size of n-grams that can only occur once. + + If set to int > 0, all ngrams of that size can only occur once. + + Parameters + ---------- + value : int + N-gram size can only occur once + """ + return self._set(noRepeatNgramSize=value) + + def setBeamSize(self, value): + """Sets the number of beam size for beam search, by default `4`. + + Parameters + ---------- + value : int + Number of beam size for beam search + """ + return self._set(beamSize=value) + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cv.MLLamaForMultimodal", + java_model=None): + super(MLLamaForMultimodal, self).__init__( + classname=classname, + java_model=java_model + ) + self._setDefault( + batchSize=1, + minOutputLength=0, + maxOutputLength=50, + doSample=False, + temperature=1, + topK=50, + topP=1, + repetitionPenalty=1.0, + noRepeatNgramSize=0, + ignoreTokenIds=[], + beamSize=1, + ) + + @staticmethod + def loadSavedModel(folder, spark_session, use_openvino=False): + """Loads a locally saved model. + + Parameters + ---------- + folder : str + Folder of the saved model + spark_session : pyspark.sql.SparkSession + The current SparkSession + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.internal import _MLLamaForMultimodalLoader + jModel = _MLLamaForMultimodalLoader(folder, spark_session._jsparkSession, use_openvino)._java_obj + return MLLamaForMultimodal(java_model=jModel) + + @staticmethod + def pretrained(name="mllama", lang="en", remote_loc=None): + """Downloads and loads a pretrained model. + + Parameters + ---------- + name : str, optional + Name of the pretrained model, by default + "phi3v" + lang : str, optional + Language of the pretrained model, by default "en" + remote_loc : str, optional + Optional remote address of the resource, by default None. Will use + Spark NLPs repositories otherwise. + + Returns + ------- + CLIPForZeroShotClassification + The restored model + """ + from sparknlp.pretrained import ResourceDownloader + return ResourceDownloader.downloadModel(MLLamaForMultimodal, name, lang, remote_loc) \ No newline at end of file diff --git a/python/sparknlp/internal/__init__.py b/python/sparknlp/internal/__init__.py index 4cb5321e8a8691..4c820d12484173 100644 --- a/python/sparknlp/internal/__init__.py +++ b/python/sparknlp/internal/__init__.py @@ -318,6 +318,14 @@ def __init__(self, path, jspark, use_openvino=False): use_openvino, ) +class _MLLamaForMultimodalLoader(ExtendedJavaWrapper): + def __init__(self, path, jspark, use_openvino=False): + super(_MLLamaForMultimodalLoader, self).__init__( + "com.johnsnowlabs.nlp.annotators.cv.MLLamaForMultimodal.loadSavedModel", + path, + jspark, + use_openvino + ) class _NLLBLoader(ExtendedJavaWrapper): def __init__(self, path, jspark, use_openvino=False): diff --git a/python/test/annotator/cv/mllama_for_multimodal_test.py b/python/test/annotator/cv/mllama_for_multimodal_test.py new file mode 100644 index 00000000000000..d4dccd966df5cf --- /dev/null +++ b/python/test/annotator/cv/mllama_for_multimodal_test.py @@ -0,0 +1,82 @@ +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +import pytest +import os + +from sparknlp.annotator import * +from sparknlp.base import * +from pyspark.sql.functions import lit +from test.util import SparkSessionForTest,SparkContextForTest + + +class MLLamaForMultimodalTestSetup(unittest.TestCase): + + def setUp(self): + self.images_path = os.getcwd() + "/../src/test/resources/image/" + self.spark = SparkContextForTest.spark + + image_df = SparkSessionForTest.spark.read.format("image").load( + path=self.images_path + ) + + self.test_df = image_df.withColumn("text", lit("<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n")) + + image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") + + imageClassifier = (MLLamaForMultimodal.pretrained() \ + .setInputCols("image_assembler") \ + .setOutputCol("answer")) + + self.pipeline = Pipeline( + stages=[ + image_assembler, + imageClassifier, + ] + ) + + self.model = self.pipeline.fit(self.test_df) + +@pytest.mark.slow +class MLLamaForMultimodalTest(MLLamaForMultimodalTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + result = self.model.transform(self.test_df).collect() + + for row in result: + self.assertTrue(row["answer"] != "") + + +@pytest.mark.slow +class LightMLLamaForMultimodalTest(MLLamaForMultimodalTestSetup, unittest.TestCase): + + def setUp(self): + super().setUp() + + def runTest(self): + light_pipeline = LightPipeline(self.model) + image_path = self.images_path + "bluetick.jpg" + + print("image_path: " + image_path) + annotations_result = light_pipeline.fullAnnotateImage( + image_path, + "<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n" + ) + # print(annotations_result) + for result in annotations_result: + self.assertTrue(len(result["image_assembler"]) > 0) + self.assertTrue(len(result["answer"]) > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala index 54098dfd40f7ec..f0136ab7f4947b 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/MLLama.scala @@ -117,8 +117,6 @@ private[johnsnowlabs] class MLLama( encodeImage(imageAnnotations.toArray, preprocessor, maxImageTiles, paddingConstant) val encodedText = encodeText(sentences).toArray -// println(encodedText.map(_.mkString(", ")).mkString("\n")) - val crossAttentionMask = encodedText.map { sentence => MllamaUtils.getCrossAttentionTokenMask(sentence, imageToken) } @@ -173,14 +171,12 @@ private[johnsnowlabs] class MLLama( effectiveBatch_size = expandedDecoderInputsVals.length effectiveBatch_mult = 1 } - - val inferRequestLanguageModel = + val inferRequestLanguageModel: InferRequest = openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request() - val inferRequestVisionEmbeddingsModel = + val inferRequestVisionEmbeddingsModel: InferRequest = openvinoWrapper.get.visionEmbeddingsModel.getCompiledModel().create_infer_request() - val inferRequestReshapeModel = + val inferRequestReshapeModel: InferRequest = openvinoWrapper.get.reshapeModel.getCompiledModel().create_infer_request() - val generatedIds = generateGreedy( inputIds, inputIds, @@ -208,7 +204,7 @@ private[johnsnowlabs] class MLLama( val aspectRatioIds = inputs("aspectRatioIds").asInstanceOf[Array[Array[Int]]] val aspectRatioMask = inputs("aspectRatioMask").asInstanceOf[Array[Array[Array[Int]]]] - val (crossAttentionOutputNames, crossAttentionKeyValues) = getCrossAttentionKeyValues( + val crossAttentionKeyValues = getCrossAttentionKeyValues( encoderInputIds, decoderInputIds, pixelValues, @@ -221,11 +217,8 @@ private[johnsnowlabs] class MLLama( encoderInputIds, decoderInputIdsCopied, inputs, - crossAttentionOutputNames, crossAttentionKeyValues, - inferRequestLanguageModel, - inferRequestVisionEmbeddingsModel, - inferRequestReshapeModel) + inferRequestLanguageModel) val nextTokenIds = decoderOutputs.map { scores => argmax(scores) @@ -246,7 +239,6 @@ private[johnsnowlabs] class MLLama( currentIds ++ Array(nextId) } } -// println(generatedIds.map(_.mkString(", ")).mkString("\n")) generatedIds } @@ -305,13 +297,11 @@ private[johnsnowlabs] class MLLama( encoderInputIds: Array[Array[Int]], decoderInputIds: Array[Array[Int]], inputs: Map[String, Any], - crossAttentionOutputNames: Array[String], - crossAttentionKeyValues: Array[org.intel.openvino.Tensor], - inferRequestLanguageModel: InferRequest, - inferRequestVisionEmbeddingsModel: InferRequest, - inferRequestReshapeModel: InferRequest): Array[Array[Float]] = { - val crossAttentionMask = - inputs("crossAttentionMask").asInstanceOf[Array[Array[Array[Array[Int]]]]] + crossAttentionKeyValues: Array[(String, org.intel.openvino.Tensor)], + inferRequestLanguageModel: InferRequest): Array[Array[Float]] = { + val inferRequestReshapeModel = + openvinoWrapper.get.reshapeModel.getCompiledModel().create_infer_request() + val numTiles = inputs("numTiles").asInstanceOf[List[List[Int]]] val (inputIdsLong, inputPositionIDsLong, crossAttentionMaskDense) : (Array[Long], Array[Long], Array[Array[Array[Array[Int]]]]) = @@ -323,6 +313,8 @@ private[johnsnowlabs] class MLLama( i.toLong } } + val crossAttentionMask = + inputs("crossAttentionMask").asInstanceOf[Array[Array[Array[Array[Int]]]]] (inpIdsLong, posIdsLong, crossAttentionMask) } else { // Subsequent passes @@ -376,8 +368,8 @@ private[johnsnowlabs] class MLLama( new org.intel.openvino.Tensor( Array[Int](), Array( - crossAttentionKeyValues.head - .get_shape()(crossAttentionKeyValues.head.get_shape().length - 2) + crossAttentionKeyValues.head._2 + .get_shape()(crossAttentionKeyValues.head._2.get_shape().length - 2) .toLong)) inferRequestReshapeModel.set_tensor("current_input_ids", inputIdsTensor) inferRequestReshapeModel.set_tensor("attention_mask", decoderAttentionMask) @@ -396,6 +388,23 @@ private[johnsnowlabs] class MLLama( val fullTextRowMaskedOutMask = inferRequestReshapeModel.get_tensor("full_text_row_masked_out_mask") + // recreate the tensors by extracting the values from the reshaped tensors + + val clonedCrossAttentionMaskReshapedTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor( + crossAttentionMaskReshaped.get_shape(), + crossAttentionMaskReshaped.data().map(_.toFloat)) + + val clonedCachePositionTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor( + cachePosition.get_shape(), + cachePosition.as_int().map(_.toLong)) + + val clonedFullTextRowMaskedOutMaskTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor( + fullTextRowMaskedOutMask.get_shape(), + fullTextRowMaskedOutMask.data().map(_.toFloat)) + // val crossAttentionMaskReshapedTensor: org.intel.openvino.Tensor = // new org.intel.openvino.Tensor( // crossAttentionMaskReshaped.get_shape(), @@ -405,16 +414,16 @@ private[johnsnowlabs] class MLLama( inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask) inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs) inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor) - inferRequestLanguageModel.set_tensor("cross_attention_mask", crossAttentionMaskReshaped) - inferRequestLanguageModel.set_tensor("cache_position", cachePosition) + inferRequestLanguageModel.set_tensor( + "cross_attention_mask", + clonedCrossAttentionMaskReshapedTensor) + inferRequestLanguageModel.set_tensor("cache_position", clonedCachePositionTensor) inferRequestLanguageModel.set_tensor( "full_text_row_masked_out_mask", - fullTextRowMaskedOutMask) + clonedFullTextRowMaskedOutMaskTensor) - for (i <- crossAttentionKeyValues.indices) { - inferRequestLanguageModel.set_tensor( - crossAttentionOutputNames(i), - crossAttentionKeyValues(i)) + for ((name, tensor) <- crossAttentionKeyValues) { + inferRequestLanguageModel.set_tensor(name, tensor) } inferRequestLanguageModel.infer() @@ -498,18 +507,6 @@ private[johnsnowlabs] class MLLama( tileHeight = preprocessor.size, tileWidth = preprocessor.size) -// val normalizedImage = -// ImageResizeUtils.normalizeAndConvertBufferedImage( -// img = paddedImage, -// mean = preprocessor.image_mean, -// std = preprocessor.image_std, -// doNormalize = preprocessor.do_normalize, -// doRescale = preprocessor.do_rescale, -// rescaleFactor = preprocessor.rescale_factor) - -// val normalizedImageBuffer = -// MllamaUtils.floatArrayToBufferedImage(normalizedImage, preprocessor.rescale_factor) - val imageTiles: Array[Array[Array[Array[Float]]]] = MllamaUtils.splitToTiles( image = paddedImage, numTilesHeight = numTilesHeight, @@ -551,7 +548,7 @@ private[johnsnowlabs] class MLLama( aspectRatioIds: Array[Array[Int]], aspectRatioMask: Array[Array[Array[Int]]], inferRequestVisionEmbeddingsModel: InferRequest) - : (Array[String], Array[org.intel.openvino.Tensor]) = { + : Array[(String, org.intel.openvino.Tensor)] = { // filter out the cross attention output names only containing the word "cross_attn_key_values" val crossAttentionOutputNames = @@ -563,7 +560,7 @@ private[johnsnowlabs] class MLLama( .map(_.get_any_name()) .toArray - val crossAttentionKeyValues: Array[org.intel.openvino.Tensor] = + val crossAttentionKeyValues: Array[(String, org.intel.openvino.Tensor)] = { if (encoderInputIds.head.length == decoderInputIds.head.length) { val pixelValuesShape = Array( pixelValues.length, @@ -597,17 +594,10 @@ private[johnsnowlabs] class MLLama( inferRequestVisionEmbeddingsModel.infer() - val crossAttentionKeyValues: Array[org.intel.openvino.Tensor] = + val crossAttentionKeyValues: Array[(String, org.intel.openvino.Tensor)] = crossAttentionOutputNames.map { outputName => - inferRequestVisionEmbeddingsModel.get_tensor(outputName) + (outputName, inferRequestVisionEmbeddingsModel.get_tensor(outputName)) } -// crossAttentionKeyValues.zip(crossAttentionOutputNames).foreach { -// case (value, name) => { -// println(s"Name: $name") -// println(s"Shape: ${value.get_shape().mkString(", ")}") -// println(s"Values: ${value.data().sum}") -// } -// } // return the cross attention output names and the key values crossAttentionKeyValues } else { @@ -615,7 +605,8 @@ private[johnsnowlabs] class MLLama( throw new IllegalArgumentException("Should not be called for subsequent passes") Array() } - (crossAttentionOutputNames, crossAttentionKeyValues) + } + crossAttentionKeyValues } } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala index ba34eefaaf565d..9d8c5fa9c86932 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala @@ -39,11 +39,18 @@ import org.apache.spark.ml.param.{IntArrayParam, IntParam} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.SparkSession -/** MLLamaForMultimodal can load LLAVA Vision models for visual question answering. The model +/** MLLamaForMultimodal can load LLAMA 3.2 Vision models for visual question answering. The model * consists of a vision encoder, a text encoder as well as a text decoder. The vision encoder * will encode the input image, the text encoder will encode the input question together with the * encoding of the image, and the text decoder will output the answer to the question. * + * The Llama 3.2-Vision collection of multimodal large language models (LLMs) is a collection of + * pretrained and instruction-tuned image reasoning generative models in 11B and 90B sizes (text + * + images in / text out). The Llama 3.2-Vision instruction-tuned models are optimized for + * visual recognition, image reasoning, captioning, and answering general questions about an + * image. The models outperform many of the available open source and closed multimodal models on + * common industry benchmarks. + * * Pretrained models can be loaded with `pretrained` of the companion object: * {{{ * val visualQA = MLLamaForMultimodal.pretrained() @@ -73,7 +80,7 @@ import org.apache.spark.sql.SparkSession * .option("dropInvalid", value = true) * .load(imageFolder) * - * val testDF: DataFrame = imageDF.withColumn("text", lit("USER: \n <|image|> \nWhat is unusual on this picture? \n ASSISTANT:\n")) + * val testDF: DataFrame = imageDF.withColumn("text", lit("<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n")) * * val imageAssembler: ImageAssembler = new ImageAssembler() * .setInputCol("image") @@ -298,8 +305,8 @@ class MLLamaForMultimodal(override val uid: String) stopTokenIds -> Array(128001, 128008, 128009), imageToken -> 128256, maxImageTiles -> 576, - numVisionTokens -> 32000, - paddingConstant -> 1601) + numVisionTokens -> 1601, + paddingConstant -> 0) /** takes a document and annotations and produces new annotations of this annotator's annotation * type diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala index c62044404d7522..30ec2f838c57ff 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodalTestSpec.scala @@ -160,9 +160,7 @@ class MLLamaForMultimodalTestSpec extends AnyFlatSpec { .setOutputCol("image_assembler") val loadModel = MLLamaForMultimodal - .loadSavedModel( - "/mnt/research/Projects/ModelZoo/LLAMA-3.2-VI/Llama-3.2-11B-Vision-Instruct/OV", - ResourceHelper.spark) + .pretrained() .setInputCols("image_assembler") .setOutputCol("answer") .setMaxOutputLength(50) From 1e0500fe3a651bfcdd7ea54d92946b41771f5a4d Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Fri, 14 Feb 2025 03:34:28 +0000 Subject: [PATCH 065/108] update default model, notebook and documentation Signed-off-by: Prabod Rathnayaka --- .../MLLamaForMultimodal.md | 116 +++ ...ingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb | 907 ++++++++++++++++++ .../annotator/cv/mllama_for_multimodal.py | 8 +- .../annotators/cv/MLLamaForMultimodal.scala | 8 +- .../nlp/pretrained/ResourceDownloader.scala | 3 +- 5 files changed, 1035 insertions(+), 7 deletions(-) create mode 100644 docs/en/transformer_entries/MLLamaForMultimodal.md create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb diff --git a/docs/en/transformer_entries/MLLamaForMultimodal.md b/docs/en/transformer_entries/MLLamaForMultimodal.md new file mode 100644 index 00000000000000..f97456d9c795e4 --- /dev/null +++ b/docs/en/transformer_entries/MLLamaForMultimodal.md @@ -0,0 +1,116 @@ +{%- capture title -%} +MLLamaForMultimodal +{%- endcapture -%} + +{%- capture description -%} +Visual Question Answering using MLLama. + +MLLamaForMultimodal can load LLAMA 3.2 Vision models for visual question answering. +The model consists of a vision encoder, a text encoder, and a text decoder. +The vision encoder encodes the input image, the text encoder processes the input question +alongside the image encoding, and the text decoder generates the answer to the question. + +The Llama 3.2-Vision collection comprises pretrained and instruction-tuned multimodal large +language models (LLMs) available in 11B and 90B sizes. These models are optimized for visual +recognition, image reasoning, captioning, and answering general questions about images. +The models outperform many open-source and proprietary multimodal models on standard industry +benchmarks. + +Pretrained models can be loaded with `pretrained` of the companion object: + +```scala +val visualQAClassifier = MLLamaForMultimodal.pretrained() +ย .setInputCols("image_assembler") +ย .setOutputCol("answer") +``` +{%- capture input_anno -%} +IMAGE +{%- endcapture -%} + +{%- capture output_anno -%} +DOCUMENT +{%- endcapture -%} + +{%- capture python_example -%} +import sparknlp +from sparknlp.base import * +from sparknlp.annotator import * +from pyspark.ml import Pipeline +from pyspark.sql.functions import lit + +image_df = spark.read.format("image").load(path=images_path) # Replace with your image path +test_df = image_df.withColumn( +ย  ย  "text", +ย  ย  lit("<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n") +) +imageAssembler = ImageAssembler() \\ +ย  ย  .setInputCol("image") \\ +ย  ย  .setOutputCol("image_assembler") +visualQAClassifier = MLLamaForMultimodal.pretrained() \\ +ย  ย  .setInputCols("image_assembler") \\ +ย  ย  .setOutputCol("answer") +pipeline = Pipeline().setStages([ +ย  ย  imageAssembler, +ย  ย  visualQAClassifier +]) +result = pipeline.fit(test_df).transform(test_df) +result.select("image_assembler.origin", "answer.result").show(truncate=False) + +{%- endcapture -%} + +{%- capture scala_example -%} +import spark.implicits._ +import com.johnsnowlabs.nlp.base._ +import com.johnsnowlabs.nlp.annotator._ +import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.lit + +val imageDF: DataFrame = spark.read +ย  .format("image") +ย  .option("dropInvalid", value = true) +ย  .load(imageFolder) // Replace with your image folder + +val testDF: DataFrame = imageDF.withColumn("text", lit("<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n")) + +val imageAssembler: ImageAssembler = new ImageAssembler() +ย  ย .setInputCol("image") +ย  ย .setOutputCol("image_assembler") + +val visualQAClassifier = MLLamaForMultimodal.pretrained() +ย  ย .setInputCols("image_assembler") +ย  ย .setOutputCol("answer") + +val pipeline = new Pipeline().setStages(Array( +ย  imageAssembler, +ย  visualQAClassifier +)) + +val result = pipeline.fit(testDF).transform(testDF) + +result.select("image_assembler.origin", "answer.result").show(truncate=false) +{%- endcapture -%} + +{%- capture api_link -%} +[MLLamaForMultimodal](/api/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal) +{%- endcapture -%} + +{%- capture python_api_link -%} +[MLLamaForMultimodal](/api/python/reference/autosummary/sparknlp/annotator/cv/m_llama_for_multimodal/index.html#sparknlp.annotator.cv.mllama_for_multimodal.MLLamaForMultimodal) +{%- endcapture -%} + +{%- capture source_link -%} +[MLLamaForMultimodal](https://github.com/JohnSnowLabs/spark-nlp/tree/master/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala) +{%- endcapture -%} + +{% include templates/anno_template.md +title=title +description=description +input_anno=input_anno +output_anno=output_anno +python_example=python_example +scala_example=scala_example +api_link=api_link +python_api_link=python_api_link +source_link=source_link +%} \ No newline at end of file diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb new file mode 100644 index 00000000000000..f9f0f3e406cbb4 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb @@ -0,0 +1,907 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import OpenVINO MLLama models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and importing MLLama models from HuggingFace for use in Spark NLP, with [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html). The focus is on converting the model to the OpenVINO format and applying precision optimizations (INT8 and INT4), to enhance the performance and efficiency on CPU platforms using [Optimum Intel](https://huggingface.co/docs/optimum/main/en/intel/inference).\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance CPU inference for models. So please make sure you have upgraded to the latest Spark NLP release.\n", + "- Model quantization is a computationally expensive process, so it is recommended to use a runtime with more than 32GB memory for exporting the quantized model from HuggingFace.\n", + "- You can import MLLama models via `MLLama`. These models are usually under `Text Generation` category and have `MLLama` in their labels.\n", + "- Reference: [MLLama](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD_VISION.md)\n", + "- Some [example models](https://huggingface.co/models?search=MLLama)\n", + "- Openvino export taken from [Openvino Notebooks](https://github.com/openvinotoolkit/openvino_notebooks/tree/b4a0791/notebooks/mllama-3.2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Export and Save the HuggingFace model\n", + "\n", + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future release, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install -q \"torch>=2.1\" \"torchvision\" \"Pillow\" \"tqdm\" \"datasets>=2.14.6\" \"gradio>=4.36\" \"nncf>=2.14.0\" --extra-index-url https://download.pytorch.org/whl/cpu\n", + "%pip install -q \"transformers>=4.45\" --extra-index-url https://download.pytorch.org/whl/cpu\n", + "%pip install -Uq \"openvino>=2024.5.0\"\n", + "%pip install -q --upgrade ipywidgets\n", + "\n", + "utility_files = [\"notebook_utils.py\", \"cmd_helper.py\"]\n", + "\n", + "import requests\n", + "from pathlib import Path\n", + "\n", + "if not Path(\"ov_mllama_helper.py\").exists():\n", + " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/b4a0791/notebooks/mllama-3.2/ov_mllama_helper.py\")\n", + " open(\"ov_mllama_helper.py\", \"w\").write(r.text)\n", + "\n", + "if not Path(\"gradio_helper.py\").exists():\n", + " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/b4a0791/notebooks/mllama-3.2/gradio_helper.py\")\n", + " open(\"gradio_helper.py\", \"w\").write(r.text)\n", + "\n", + "if not Path(\"ov_mllama_compression.py\").exists():\n", + " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/b4a0791/notebooks/mllama-3.2/ov_mllama_compression.py\")\n", + " open(\"ov_mllama_compression.py\", \"w\").write(r.text)\n", + "\n", + "if not Path(\"data_preprocessing.py\").exists():\n", + " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/b4a0791/notebooks/mllama-3.2/data_preprocessing.py\")\n", + " open(\"data_preprocessing\", \"w\").write(r.text)\n", + "\n", + "if not Path(\"notebook_utils.py\").exists():\n", + " r = requests.get(url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/b4a0791/utils/notebook_utils.py\")\n", + " open(\"notebook_utils.py\", \"w\").write(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Convert the model to OpenVino" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/prabod/anaconda3/envs/mllama/lib/python3.9/importlib/util.py:245: DeprecationWarning: The `openvino.runtime` module is deprecated and will be removed in the 2026.0 release. Please replace `openvino.runtime` with `openvino`.\n", + " self.__spec__.loader.exec_module(self)\n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "from ov_mllama_helper import convert_mllama\n", + "\n", + "model_id = \"meta-llama/Llama-3.2-11B-Vision-Instruct\"\n", + "model_dir = Path(model_id.split(\"/\")[-1]) / \"OV\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "dc77113413684c39ba2b488d2504c7f8", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Dropdown(description='Device:', options=('CPU', 'AUTO'), value='CPU')" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from notebook_utils import device_widget\n", + "\n", + "device = device_widget(\"CPU\", exclude=[\"NPU\"])\n", + "\n", + "device" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('Llama-3.2-11B-Vision-Instruct/OV')" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_dir" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "convert_mllama(model_id, model_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ov_mllama_compression import compress\n", + "from ov_mllama_compression import compression_widgets_helper\n", + "\n", + "compression_scenario, compress_args = compression_widgets_helper()\n", + "\n", + "compression_scenario" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "compression_kwargs = {key: value.value for key, value in compress_args.items()}\n", + "\n", + "language_model_path = compress(model_dir, **compression_kwargs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ov_mllama_compression import vision_encoder_selection_widget\n", + "\n", + "vision_encoder_options = vision_encoder_selection_widget(device.value)\n", + "\n", + "vision_encoder_options" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import AutoProcessor\n", + "import nncf\n", + "import openvino as ov\n", + "import gc\n", + "\n", + "from data_preprocessing import prepare_dataset_vision\n", + "\n", + "processor = AutoProcessor.from_pretrained(model_dir)\n", + "core = ov.Core()\n", + "\n", + "fp_vision_encoder_path = model_dir / \"openvino_vision_encoder.xml\"\n", + "int8_vision_encoder_path = model_dir / fp_vision_encoder_path.name.replace(\".xml\", \"_int8.xml\")\n", + "int8_wc_vision_encoder_path = model_dir / fp_vision_encoder_path.name.replace(\".xml\", \"_int8_wc.xml\")\n", + "\n", + "\n", + "if vision_encoder_options.value == \"INT8 quantization\":\n", + " if not int8_vision_encoder_path.exists():\n", + " calibration_data = prepare_dataset_vision(processor, 100)\n", + " ov_model = core.read_model(fp_vision_encoder_path)\n", + " calibration_dataset = nncf.Dataset(calibration_data)\n", + " quantized_model = nncf.quantize(\n", + " model=ov_model,\n", + " calibration_dataset=calibration_dataset,\n", + " model_type=nncf.ModelType.TRANSFORMER,\n", + " advanced_parameters=nncf.AdvancedQuantizationParameters(smooth_quant_alpha=0.6),\n", + " )\n", + " ov.save_model(quantized_model, int8_vision_encoder_path)\n", + " del quantized_model\n", + " del ov_model\n", + " del calibration_dataset\n", + " del calibration_data\n", + " gc.collect()\n", + "\n", + " vision_encoder_path = int8_vision_encoder_path\n", + "elif vision_encoder_options.value == \"INT8 weights compression\":\n", + " if not int8_wc_vision_encoder_path.exists():\n", + " ov_model = core.read_model(fp_vision_encoder_path)\n", + " compressed_model = nncf.compress_weights(ov_model)\n", + " ov.save_model(compressed_model, int8_wc_vision_encoder_path)\n", + " vision_encoder_path = int8_wc_vision_encoder_path\n", + "else:\n", + " vision_encoder_path = fp_vision_encoder_path" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from transformers import AutoProcessor, AutoConfig\n", + "\n", + "model_id = \"meta-llama/Llama-3.2-11B-Vision-Instruct\"\n", + "processor = AutoProcessor.from_pretrained(model_id)\n", + "config = AutoConfig.from_pretrained(model_id)\n", + "\n", + "import requests\n", + "from PIL import Image\n", + "\n", + "\n", + "question = \"What is unusual on this image?\"\n", + "\n", + "messages = [\n", + " {\"role\": \"user\", \"content\": [{\"type\": \"image\"}, {\"type\": \"text\", \"text\": question}]},\n", + "]\n", + "text = processor.tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)\n", + "url = \"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\"\n", + "raw_image = Image.open(requests.get(url, stream=True).raw)\n", + "\n", + "inputs = processor(text=text, images=[raw_image], return_tensors=\"pt\")\n", + "\n", + "pixel_values = inputs[\"pixel_values\"]\n", + "aspect_ratio_ids = inputs[\"aspect_ratio_ids\"]\n", + "aspect_ratio_mask = inputs[\"aspect_ratio_mask\"]\n", + "\n", + "image_inputs = {\n", + " \"pixel_values\": pixel_values,\n", + " \"aspect_ratio_ids\": aspect_ratio_ids,\n", + " \"aspect_ratio_mask\": aspect_ratio_mask,\n", + "}\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "model_path = Path(\"/mnt/research/Projects/ModelZoo/LLAMA-3.2-VI/Llama-3.2-11B-Vision-Instruct/OV\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import openvino as ov\n", + "from pathlib import Path\n", + "core = ov.Core()\n", + "\n", + "IMAGE_ENCODER_NAME = \"openvino_vision_encoder.xml\"\n", + "\n", + "image_encoder = core.compile_model(model_path / IMAGE_ENCODER_NAME,\"CPU\")\n", + "cross_attn_outputs = [key.get_any_name() for key in image_encoder.outputs if \"cross_attn_key_values\" in key.get_any_name()]\n", + "\n", + "\n", + "image_request = image_encoder.create_infer_request()\n", + "image_request.start_async([pixel_values, aspect_ratio_ids, aspect_ratio_mask], share_inputs=True)\n", + "image_request.wait()\n", + "cross_attn_key_values = [image_request.get_tensor(name) for name in cross_attn_outputs]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import torch\n", + "\n", + "class PreprocessingMasks(torch.nn.Module):\n", + " def __init__(self,):\n", + " super().__init__()\n", + "\n", + " def forward(\n", + " self,\n", + " cross_attention_mask,\n", + " attention_mask,\n", + " current_input_ids,\n", + " num_vision_tokens,\n", + " past_cross_attn_kv_length\n", + " ):\n", + " dtype=torch.float32\n", + " batch_size, text_total_length, *_ = cross_attention_mask.shape\n", + " cross_attention_mask = cross_attention_mask.repeat_interleave(num_vision_tokens, dim=3)\n", + " cross_attention_mask = cross_attention_mask.view(batch_size, text_total_length, -1)\n", + " cross_attention_mask = cross_attention_mask.unsqueeze(1)\n", + "\n", + " inverted_cross_attn_mask = (1.0 - cross_attention_mask).to(dtype)\n", + " cross_attention_mask = inverted_cross_attn_mask.masked_fill(inverted_cross_attn_mask.to(torch.bool), torch.finfo(dtype).min)\n", + "\n", + " # apply full-row bias, which return 4D tensor of shape [B, H, S1, 1] where value is 0 if the a full row in cross attn mask's\n", + " # last dimension contains negative infinity values, otherwise it's 1\n", + " negative_inf_value = torch.finfo(dtype).min\n", + " full_text_row_masked_out_mask = (cross_attention_mask != negative_inf_value).any(dim=-1).type_as(cross_attention_mask)[..., None]\n", + " cross_attention_mask *= full_text_row_masked_out_mask\n", + "\n", + " # if first_pass > 0:\n", + " # past_cross_attn_kv_length = cross_attn_key_values[0].shape[-2]\n", + " past_cross_attn_mask = torch.zeros((*cross_attention_mask.shape[:-1], past_cross_attn_kv_length), dtype=dtype)\n", + " # concatenate both on image-seq-length dimension\n", + " cross_attention_mask_second_pass = torch.cat([past_cross_attn_mask, cross_attention_mask], dim=-1)\n", + " cache_position = (attention_mask.long().cumsum(-1) - 1)[:, -current_input_ids.shape[1] :][0]\n", + "\n", + " cross_attention_mask_second_pass = cross_attention_mask_second_pass[:, :, cache_position]\n", + "\n", + " cross_attention_mask = cross_attention_mask[:, :, cache_position]\n", + " full_text_row_masked_out_mask = full_text_row_masked_out_mask[:, :, cache_position]\n", + "\n", + " return {\n", + " \"cache_position\": cache_position.to(torch.int32),\n", + " \"cross_attention_mask_first_pass\": cross_attention_mask.to(dtype),\n", + " \"cross_attention_mask_second_pass\": cross_attention_mask_second_pass.to(dtype),\n", + " \"full_text_row_masked_out_mask\": full_text_row_masked_out_mask.to(dtype),\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "preprocessing_masks = PreprocessingMasks()\n", + "cross_attention_mask = inputs[\"cross_attention_mask\"]\n", + "attention_mask = inputs[\"attention_mask\"]\n", + "current_input_ids = inputs[\"input_ids\"]\n", + "first_pass = torch.tensor(1)\n", + "num_vision_tokens = torch.tensor((config.vision_config.image_size // config.vision_config.patch_size) ** 2 + 1)\n", + "past_cross_attn_kv_length = torch.tensor(cross_attn_key_values[0].shape[-2])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import openvino as ov\n", + "\n", + "ov_model_preprocessing_masks = ov.convert_model(\n", + " preprocessing_masks,\n", + " example_input={\n", + " \"cross_attention_mask\": cross_attention_mask,\n", + " \"attention_mask\": attention_mask,\n", + " \"current_input_ids\": current_input_ids,\n", + " \"num_vision_tokens\": num_vision_tokens,\n", + " \"past_cross_attn_kv_length\": past_cross_attn_kv_length,\n", + " }\n", + ")\n", + "\n", + "ov.save_model(ov_model_preprocessing_masks,model_path/\"openvino_reshape_model.xml\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Load openvino models" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "LANGUAGE_MODEL_NAME = \"llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.xml\"\n", + "LANGUAGE_MODEL_NAME_1 = \"openvino_language_model.xml\"\n", + "IMAGE_ENCODER_NAME = \"openvino_vision_encoder.xml\"\n", + "PREPROCESSING_MASKS_NAME = \"openvino_reshape_model.xml\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import openvino as ov\n", + "import gc\n", + "\n", + "core = ov.Core()\n", + "model_path = model_dir\n", + "\n", + "language_model = core.read_model(model_path / LANGUAGE_MODEL_NAME)\n", + "compiled_language_model = core.compile_model(language_model, \"CPU\")\n", + "request = compiled_language_model.create_infer_request()\n", + "\n", + "image_encoder = core.compile_model(model_path / IMAGE_ENCODER_NAME,\"CPU\")\n", + "preprocessing_masks = core.compile_model(model_path / PREPROCESSING_MASKS_NAME,\"CPU\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "model_dir = Path(\"/mnt/research/Projects/ModelZoo/LLAMA-3.2-VI/Llama-3.2-11B-Vision-Instruct/OV\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โŒ› Check if all models are converted\n", + "โœ… All models are converted. You can find results in /mnt/research/Projects/ModelZoo/LLAMA-3.2-VI/Llama-3.2-11B-Vision-Instruct/OV\n" + ] + } + ], + "source": [ + "# check if all the models are converted\n", + "\n", + "print(\"โŒ› Check if all models are converted\")\n", + "language_model_path = model_dir / LANGUAGE_MODEL_NAME\n", + "# language_model_path_1 = model_dir / LANGUAGE_MODEL_NAME_1\n", + "image_encoder_path = model_dir / IMAGE_ENCODER_NAME\n", + "preprocessing_masks_path = model_dir / PREPROCESSING_MASKS_NAME\n", + "\n", + "if all(\n", + " [\n", + " language_model_path.exists(),\n", + " # language_model_path_1.exists(),\n", + " image_encoder_path.exists(),\n", + " preprocessing_masks_path.exists(),\n", + " ]\n", + "):\n", + " print(f\"โœ… All models are converted. You can find results in {model_dir}\")\n", + "else:\n", + " print(\"โŒ Not all models are converted. Please check the conversion process\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Copy assets to the assets folder" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "assets_dir = model_dir / \"assets\"\n", + "assets_dir.mkdir(exist_ok=True)\n", + "\n", + "# copy all the assets to the assets directory (json files, vocab files, etc.)\n", + "\n", + "import shutil\n", + "\n", + "# copy all json files\n", + "\n", + "for file in model_dir.glob(\"*.json\"):\n", + " shutil.copy(file, assets_dir)\n", + "\n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 31G\n", + "drwxrwxr-x 2 prabod prabod 4.0K Jan 15 03:09 assets\n", + "-rw-rw-r-- 1 prabod prabod 5.0K Dec 12 01:53 chat_template.json\n", + "-rw-rw-r-- 1 prabod prabod 5.0K Jan 15 03:06 config.json\n", + "-rw-rw-r-- 1 prabod prabod 210 Dec 12 01:53 generation_config.json\n", + "-rw-rw-r-- 1 prabod prabod 4.9G Jan 23 01:10 llm_int4_asym_r10_gs64_max_activation_variance_all_layers.bin\n", + "-rw-rw-r-- 1 prabod prabod 3.9M Jan 23 01:10 llm_int4_asym_r10_gs64_max_activation_variance_all_layers.xml\n", + "-rw-rw-r-- 1 prabod prabod 4.9G Dec 12 04:28 llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.bin\n", + "-rw-rw-r-- 1 prabod prabod 3.9M Dec 12 04:28 llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.xml\n", + "-rw-rw-r-- 1 prabod prabod 19G Dec 12 01:55 openvino_language_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 3.0M Dec 12 01:55 openvino_language_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 92 Jan 22 05:14 openvino_reshape_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 37K Jan 22 05:14 openvino_reshape_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 1.8G Dec 12 01:54 openvino_vision_encoder.bin\n", + "-rw-rw-r-- 1 prabod prabod 924M Dec 12 08:15 openvino_vision_encoder_int8.bin\n", + "-rw-rw-r-- 1 prabod prabod 2.5M Dec 12 08:15 openvino_vision_encoder_int8.xml\n", + "-rw-rw-r-- 1 prabod prabod 1.6M Dec 12 01:54 openvino_vision_encoder.xml\n", + "-rw-rw-r-- 1 prabod prabod 92 Jan 13 07:25 preprocessing_masks.bin\n", + "-rw-rw-r-- 1 prabod prabod 37K Jan 13 07:25 preprocessing_masks.xml\n", + "-rw-rw-r-- 1 prabod prabod 477 Dec 12 01:53 preprocessor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 454 Dec 12 01:53 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 55K Dec 12 01:53 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 17M Dec 12 01:53 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -lh {model_dir}" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 17M\n", + "-rw-rw-r-- 1 prabod prabod 5.0K Jan 14 08:10 chat_template.json\n", + "-rw-rw-r-- 1 prabod prabod 5.0K Jan 15 03:09 'config copy.json'\n", + "-rw-rw-r-- 1 prabod prabod 5.0K Jan 15 03:09 config.json\n", + "-rw-rw-r-- 1 prabod prabod 210 Jan 14 08:10 generation_config.json\n", + "-rw-rw-r-- 1 prabod prabod 477 Jan 14 08:10 preprocessor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 454 Jan 14 08:10 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 55K Jan 14 08:10 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 17M Jan 14 08:10 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -lh {model_dir / \"assets\"}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Import and Save MLLama in Spark NLP\n", + "\n", + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing /home/prabod/Projects/spark-nlp/python/dist/spark_nlp-5.5.3-py2.py3-none-any.whl\n", + "Collecting pyspark==3.2.3\n", + " Using cached pyspark-3.2.3-py2.py3-none-any.whl\n", + "Collecting py4j==0.10.9.5 (from pyspark==3.2.3)\n", + " Downloading py4j-0.10.9.5-py2.py3-none-any.whl.metadata (1.5 kB)\n", + "Downloading py4j-0.10.9.5-py2.py3-none-any.whl (199 kB)\n", + "Installing collected packages: spark-nlp, py4j, pyspark\n", + "Successfully installed py4j-0.10.9.5 pyspark-3.2.3 spark-nlp-5.5.3\n" + ] + } + ], + "source": [ + "! pip install /home/prabod/Projects/spark-nlp/python/dist/spark_nlp-5.5.3-py2.py3-none-any.whl pyspark==3.2.3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25/02/14 02:48:12 WARN Utils: Your hostname, minotaur resolves to a loopback address: 127.0.1.1; using 192.168.1.4 instead (on interface eno1)\n", + "25/02/14 02:48:12 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n", + "25/02/14 02:48:12 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Setting default log level to \"WARN\".\n", + "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25/02/14 02:48:13 WARN Utils: Service 'SparkUI' could not bind on port 4040. Attempting port 4041.\n" + ] + } + ], + "source": [ + "\n", + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "\n", + "from sparknlp.pretrained import PretrainedPipeline\n", + "import sparknlp\n", + "\n", + "from pyspark.sql import SparkSession\n", + "from pyspark.ml import Pipeline, PipelineModel\n", + "\n", + "SPARKNLP_JAR = \"/home/prabod/Projects/spark-nlp/python/lib/sparknlp.jar\"\n", + "spark = SparkSession.builder \\\n", + " .master('local[*]') \\\n", + " .appName('Spark NLP') \\\n", + " .config(\"spark.driver.memory\", \"60g\") \\\n", + " .config(\"spark.driver.maxResultSize\", \"0G\") \\\n", + " .config(\"spark.serializer\", \"org.apache.spark.serializer.KryoSerializer\") \\\n", + " .config(\"spark.kryoserializer.buffer.max\", \"2000M\") \\\n", + " .config(\"spark.jars\", SPARKNLP_JAR) \\\n", + " .getOrCreate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start Spark with Spark NLP included via our simple `start()` function" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "24/11/07 09:56:55 WARN Utils: Your hostname, minotaur resolves to a loopback address: 127.0.1.1; using 192.168.1.4 instead (on interface eno1)\n", + "24/11/07 09:56:55 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n", + "24/11/07 09:56:55 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Setting default log level to \"WARN\".\n", + "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25/02/14 02:49:23 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native8030791226413631526/libtbb.so.2\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING: An illegal reflective access operation has occurred\n", + "WARNING: Illegal reflective access by org.apache.spark.util.SizeEstimator$ (file:/home/prabod/spark/jars/spark-core_2.12-3.3.2.jar) to field java.util.regex.Pattern.pattern\n", + "WARNING: Please consider reporting this to the maintainers of org.apache.spark.util.SizeEstimator$\n", + "WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n", + "WARNING: All illegal access operations will be denied in a future release\n" + ] + } + ], + "source": [ + "imageClassifier = MLLamaForMultimodal.loadSavedModel(str(model_path),spark) \\\n", + " .setInputCols(\"image_assembler\") \\\n", + " .setOutputCol(\"answer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "imageClassifier.write().overwrite().save(\"file:///mnt/research/MLLama_spark_nlp\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 6.8G\n", + "drwxr-xr-x 4 prabod prabod 4.0K Feb 14 02:51 .\n", + "drwxr-xr-x 13 prabod root 4.0K Feb 14 02:50 ..\n", + "drwxr-xr-x 6 prabod prabod 4.0K Feb 14 02:50 fields\n", + "-rw-r--r-- 1 prabod prabod 4.9G Feb 14 02:51 llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.xml\n", + "-rw-r--r-- 1 prabod prabod 40M Feb 14 02:51 .llm_int4_asym_r10_gs64_max_activation_variance_awq_scale_all_layers.xml.crc\n", + "drwxr-xr-x 2 prabod prabod 4.0K Feb 14 02:50 metadata\n", + "-rw-r--r-- 1 prabod prabod 37K Feb 14 02:51 openvino_reshape_model.xml\n", + "-rw-r--r-- 1 prabod prabod 304 Feb 14 02:51 .openvino_reshape_model.xml.crc\n", + "-rw-r--r-- 1 prabod prabod 1.8G Feb 14 02:51 openvino_vision_encoder.xml\n", + "-rw-r--r-- 1 prabod prabod 15M Feb 14 02:51 .openvino_vision_encoder.xml.crc\n" + ] + } + ], + "source": [ + "!ls -lah /mnt/research/MLLama_spark_nlp" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "import sparknlp\n", + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.sql.functions import lit\n", + "from pyspark.ml import Pipeline\n", + "from pathlib import Path\n", + "import os\n", + "\n", + "# download two images to test into ./images folder\n", + "\n", + "url1 = \"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\"\n", + "url2 = \"http://images.cocodataset.org/val2017/000000039769.jpg\"\n", + "\n", + "Path(\"images\").mkdir(exist_ok=True)\n", + "\n", + "!wget -q -O images/image1.jpg {url1}\n", + "!wget -q -O images/image2.jpg {url2}\n", + "\n", + "\n", + "\n", + "images_path = \"file://\" + os.getcwd() + \"/images/\"\n", + "image_df = spark.read.format(\"image\").load(\n", + " path=images_path\n", + ")\n", + "\n", + "test_df = image_df.withColumn(\"text\", lit(\"<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\\n\\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\\n\\n\"))\n", + "\n", + "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n", + "\n", + "imageClassifier = MLLamaForMultimodal.load(\"file:///mnt/research/MLLama_spark_nlp\")\\\n", + " .setMaxOutputLength(50) \\\n", + " .setInputCols(\"image_assembler\") \\\n", + " .setOutputCol(\"answer\")\n", + "\n", + "pipeline = Pipeline(\n", + " stages=[\n", + " image_assembler,\n", + " imageClassifier,\n", + " ]\n", + " )\n", + "\n", + "model = pipeline.fit(test_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "image_path: /home/prabod/Projects/spark-nlp/examples/python/transformers/openvino/images/image1.jpg\n", + "[Annotation(document, 0, 208, This image depicts a cat lying in a box, on a carpet. The image features a cat lying in a box placed on a carpet. The image features a cat lying in a box placed on a carpet. The image features a cat lying in a, Map(), [])]\n" + ] + } + ], + "source": [ + "light_pipeline = LightPipeline(model)\n", + "image_path = os.getcwd() + \"/images/\" + \"image1.jpg\"\n", + "print(\"image_path: \" + image_path)\n", + "annotations_result = light_pipeline.fullAnnotateImage(\n", + " image_path,\n", + " \"<|begin_of_text|><|begin_of_text|><|start_header_id|>user<|end_header_id|>\\n\\n<|image|>What is unusual on this image?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\\n\\n\"\n", + ")\n", + "\n", + "for result in annotations_result:\n", + " print(result[\"answer\"])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mllama", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.21" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/python/sparknlp/annotator/cv/mllama_for_multimodal.py b/python/sparknlp/annotator/cv/mllama_for_multimodal.py index a62e2ee99ab264..1a4939b739d957 100644 --- a/python/sparknlp/annotator/cv/mllama_for_multimodal.py +++ b/python/sparknlp/annotator/cv/mllama_for_multimodal.py @@ -38,7 +38,7 @@ class MLLamaForMultimodal(AnnotatorModel, ... .setInputCols(["image_assembler"]) \\ ... .setOutputCol("answer") -The default model is `"mllama"`, if no name is provided. +The default model is `"llama_3_2_11b_vision_instruct_int4"`, if no name is provided. For available pretrained models, refer to the `Models Hub `__. @@ -317,14 +317,14 @@ def loadSavedModel(folder, spark_session, use_openvino=False): return MLLamaForMultimodal(java_model=jModel) @staticmethod - def pretrained(name="mllama", lang="en", remote_loc=None): + def pretrained(name="llama_3_2_11b_vision_instruct_int4", lang="en", remote_loc=None): """Downloads and loads a pretrained model. Parameters ---------- name : str, optional Name of the pretrained model, by default - "phi3v" + "llama_3_2_11b_vision_instruct_int4" lang : str, optional Language of the pretrained model, by default "en" remote_loc : str, optional @@ -333,7 +333,7 @@ def pretrained(name="mllama", lang="en", remote_loc=None): Returns ------- - CLIPForZeroShotClassification + MLLamaForMultimodal The restored model """ from sparknlp.pretrained import ResourceDownloader diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala index 9d8c5fa9c86932..b3ba4cee841098 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/MLLamaForMultimodal.scala @@ -57,7 +57,7 @@ import org.apache.spark.sql.SparkSession * .setInputCols("image_assembler") * .setOutputCol("answer") * }}} - * The default model is `"mllama"`, if no name is provided. + * The default model is `"llama_3_2_11b_vision_instruct_int4"`, if no name is provided. * * For available pretrained models please see the * [[https://sparknlp.org/models?task=Question+Answering Models Hub]]. @@ -398,7 +398,7 @@ trait ReadablePretrainedMLLamaForMultimodal extends ParamsAndFeaturesReadable[MLLamaForMultimodal] with HasPretrained[MLLamaForMultimodal] { - override val defaultModelName: Some[String] = Some("mllama") + override val defaultModelName: Some[String] = Some("llama_3_2_11b_vision_instruct_int4") /** Java compliant-overrides */ override def pretrained(): MLLamaForMultimodal = super.pretrained() @@ -591,6 +591,10 @@ trait ReadMLLamaForMultimodalDLModel extends ReadOpenvinoModel { .setImageToken(imageToken) .setMaxImageTiles(maxImageTiles) .setNumVisionTokens(numVisionTokens) + .setSize(preprocessorConfig.size) + .setImageMean(preprocessorConfig.image_mean) + .setImageStd(preprocessorConfig.image_std) + .setResample(preprocessorConfig.resample) val modelEngine = if (useOpenvino) diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala index 0e457d4d6e20df..db19af8289c9a0 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala @@ -697,7 +697,8 @@ object PythonResourceDownloader { "NLLBTransformer" -> NLLBTransformer, "Phi3Transformer" -> Phi3Transformer, "QwenTransformer" -> QwenTransformer, - "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings) + "AutoGGUFEmbeddings" -> AutoGGUFEmbeddings, + "MLLamaForMultimodal" -> MLLamaForMultimodal) // List pairs of types such as the one with key type can load a pretrained model from the value type val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering") From 0f9d4d923254e5bf13cc8c39508e82f5440e7e84 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Fri, 21 Feb 2025 17:19:49 -0500 Subject: [PATCH 066/108] [SPARKNLP-1098] Enabling getStoreSplittedPdf parameter to PDF reader --- .../reader/SparkNLP_PDF_Reader_Demo.ipynb | 257 ++++++++++++++---- .../com/johnsnowlabs/reader/PdfToText.scala | 2 +- .../johnsnowlabs/reader/SparkNLPReader.scala | 13 +- .../johnsnowlabs/reader/PdfToTextTest.scala | 15 + .../reader/SparkNLPReaderTest.scala | 11 + 5 files changed, 250 insertions(+), 48 deletions(-) diff --git a/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb index 34e96c227cd328..9e53b7bb9bcad8 100644 --- a/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb @@ -12,6 +12,17 @@ { "cell_type": "markdown", "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "9179255b-6bfd-415f-9f0a-54b6a3512617", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, "id": "tzcU5p2gdak9" }, "source": [ @@ -19,36 +30,20 @@ "This notebook showcases the newly added `sparknlp.read().pdf()` method in Spark NLP that parses PDF content from both local files and distributed file systems into a Spark DataFrame." ] }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "DczWop6QeE8F", - "outputId": "ceb0e598-4c62-475d-fe65-74eb7d737652" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Apache Spark version: 3.5.3\n" - ] - } - ], - "source": [ - "import sparknlp\n", - "# let's start Spark with Spark NLP\n", - "spark = sparknlp.start()\n", - "\n", - "print(\"Apache Spark version: {}\".format(spark.version))" - ] - }, { "cell_type": "markdown", "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "c3f8f91e-aee4-4e63-bfc7-89d93ac079cb", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, "id": "RFOFhaEedalB" }, "source": [ @@ -62,14 +57,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "- Let's install and setup Spark NLP in Google Colab\n", - "- This part is pretty easy via our simple script" + "Let's install and setup Spark NLP in Google Colab. This part is pretty easy via our simple script" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "73d60304-095c-4068-ac38-614c0163f4ac", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "ya8qZe00dalC" + }, "outputs": [], "source": [ "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" @@ -84,10 +91,8 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": { - "id": "ya8qZe00dalC" - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "!mkdir pdf-files\n", @@ -98,6 +103,17 @@ { "cell_type": "markdown", "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "e58c3483-2505-4f72-bf7d-617a96c4fbf0", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, "id": "EoFI66NAdalE" }, "source": [ @@ -107,13 +123,24 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 0, "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "ee51b499-e008-4861-b425-8450076e2d2e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "bAkMjJ1vdalE", - "outputId": "db995ee4-16fc-483a-eb89-c05b7cb5c863" + "outputId": "0fb33993-97b0-471a-c9e0-002a830b61d0" }, "outputs": [ { @@ -121,32 +148,44 @@ "output_type": "stream", "text": [ "Warning::Spark Session already created, some configs may not take.\n", - "+--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", - "| path| modificationTime|length| text|height_dimension|width_dimension| content|exception|pagenum|\n", - "+--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", - "|file:/content/pdf...|2025-01-15 20:48:...| 25803|This is a Title \\...| 842| 596|[25 50 44 46 2D 3...| NULL| 0|\n", - "|file:/content/pdf...|2025-01-15 20:48:...| 9487|This is a page.\\n...| 841| 595|[25 50 44 46 2D 3...| NULL| 0|\n", - "+--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", + "+--------------------+-------------------+------+--------------------+----------------+---------------+-------+---------+-------+\n", + "| path| modificationTime|length| text|height_dimension|width_dimension|content|exception|pagenum|\n", + "+--------------------+-------------------+------+--------------------+----------------+---------------+-------+---------+-------+\n", + "|dbfs:/danilo/data...|2025-02-21 21:33:00| 25803|This is a Title \\...| 842| 596| NULL| NULL| 0|\n", + "|dbfs:/danilo/data...|2025-02-21 21:33:01| 15629| \\n\\n\\n| 841| 595| NULL| NULL| 0|\n", + "|dbfs:/danilo/data...|2025-02-21 21:33:01| 9487|This is a page.\\n...| 841| 595| NULL| NULL| 0|\n", + "+--------------------+-------------------+------+--------------------+----------------+---------------+-------+---------+-------+\n", "\n" ] } ], "source": [ "import sparknlp\n", - "pdf_df = sparknlp.read().pdf(\"./pdf-examples\")\n", "\n", + "pdf_df = sparknlp.read().pdf(\"./pdf-examples\")\n", "pdf_df.show()" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 0, "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "65091263-01a0-4af3-aa0e-988761e9ba52", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, "colab": { "base_uri": "https://localhost:8080/" }, "id": "VWbUgoVQrO8m", - "outputId": "7bbc1f6e-9188-4c42-c3fb-126198e812a5" + "outputId": "cc6b55d7-aa86-4d2f-b43b-be7ec5797c2b" }, "outputs": [ { @@ -174,6 +213,17 @@ { "cell_type": "markdown", "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "b7119184-b033-4197-88c9-f8fa50b42be3", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, "id": "BB2FEfegGuxl" }, "source": [ @@ -182,9 +232,124 @@ "- HDFS: `hdfs://`\n", "- Microsoft Fabric OneLake: `abfss://`" ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "60bfae5d-21a0-4932-8acb-30d19529e4cf", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "C1KhfLcCPizR" + }, + "source": [ + "### Configuration Parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "d046ddfa-f097-41db-84a9-80d93d4c2693", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "OUSSGmjrPnPY" + }, + "source": [ + "You can customize the behavior of PDF reader with some parameters." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "9f8ae4b7-a73a-4e70-bbb9-afe69dd74d95", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "7jefzVyEP8f_" + }, + "source": [ + "- `storeSplittedPdf`: By default, it's `false`. When it's `true` it stores bytes content of splitted pdf in `content` column" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "643c4165-0b12-429c-9a75-3c2fd5207a72", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "gDJyUi_9R4fr", + "outputId": "d4ac184d-dc46-4ced-87ff-f42f23f52cd2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+-------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", + "| path| modificationTime|length| text|height_dimension|width_dimension| content|exception|pagenum|\n", + "+--------------------+-------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", + "|dbfs:/danilo/data...|2025-02-21 21:33:00| 25803|This is a Title \\...| 842| 596|[25 50 44 46 2D 3...| NULL| 0|\n", + "|dbfs:/danilo/data...|2025-02-21 21:33:01| 15629| \\n\\n\\n| 841| 595|[25 50 44 46 2D 3...| NULL| 0|\n", + "|dbfs:/danilo/data...|2025-02-21 21:33:01| 9487|This is a page.\\n...| 841| 595|[25 50 44 46 2D 3...| NULL| 0|\n", + "+--------------------+-------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"storeSplittedPdf\": \"true\"}\n", + "pdf_df = sparknlp.read(params).pdf(\"./pdf-examples\")\n", + "pdf_df.show()" + ] } ], "metadata": { + "application/vnd.databricks.v1+notebook": { + "computePreferences": null, + "dashboards": [], + "environmentMetadata": null, + "language": "python", + "notebookMetadata": { + "pythonIndentUnit": 4 + }, + "notebookName": "SparkNLP_PDF_Reader_Demo", + "widgets": {} + }, "colab": { "provenance": [] }, diff --git a/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala b/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala index 102ccebd8e628f..5e8ca8f13692e1 100644 --- a/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala +++ b/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala @@ -66,7 +66,7 @@ class PdfToText(override val uid: String) new Param[String](this, "originCol", "Input column name with original path of file.") final val partitionNum = new IntParam(this, "partitionNum", "Number of partitions.") final val storeSplittedPdf = - new BooleanParam(this, "storeSplittedPdf", "Force to store splitted pdf.") + new BooleanParam(this, "storeSplittedPdf", "Force to store bytes content of splitted pdf.") /** @group getParam */ def setOriginCol(value: String): this.type = set(originCol, value) diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index ab376eb36dad03..f371031094bb68 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -204,7 +204,8 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM def pdf(pdfPath: String): DataFrame = { val spark = ResourceHelper.spark spark.conf.set("spark.sql.legacy.allowUntypedScalaUDF", "true") - val pdfToText = new PdfToText().setStoreSplittedPdf(true) + val pdfToText = new PdfToText() + .setStoreSplittedPdf(getStoreSplittedPdf) val binaryPdfDF = spark.read.format("binaryFile").load(pdfPath) val pipelineModel = new Pipeline() .setStages(Array(pdfToText)) @@ -213,4 +214,14 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM pipelineModel.transform(binaryPdfDF) } + private def getStoreSplittedPdf: Boolean = { + val splitPage = + try { + params.asScala.getOrElse("storeSplittedPdf", "false").toBoolean + } catch { + case _: IllegalArgumentException => false + } + splitPage + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/PdfToTextTest.scala b/src/test/scala/com/johnsnowlabs/reader/PdfToTextTest.scala index c9d142d93b48cd..ba00940d5c5a5d 100644 --- a/src/test/scala/com/johnsnowlabs/reader/PdfToTextTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/PdfToTextTest.scala @@ -18,6 +18,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper import com.johnsnowlabs.tags.FastTest import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.functions.col import org.scalatest.flatspec.AnyFlatSpec class PdfToTextTest extends AnyFlatSpec { @@ -39,4 +40,18 @@ class PdfToTextTest extends AnyFlatSpec { assert(pdfDf.count() > 0) } + it should "not include content data when setStoreSplittedPdf is false" in { + val pdfToText = new PdfToText().setStoreSplittedPdf(false) + val dummyDataFrame = spark.read.format("binaryFile").load("src/test/resources/reader/pdf") + + val pipelineModel = new Pipeline() + .setStages(Array(pdfToText)) + .fit(dummyDataFrame) + + val pdfDf = pipelineModel.transform(dummyDataFrame) + pdfDf.show() + + assert(pdfDf.filter(col("content").isNotNull).count() == 0) + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala index 5bb09acc4519f7..6733186bd8643a 100644 --- a/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/SparkNLPReaderTest.scala @@ -28,4 +28,15 @@ class SparkNLPReaderTest extends AnyFlatSpec { assert(pdfDf.count() > 0) } + it should "read a PDF file with params" taggedAs FastTest in { + val pdfPath = "src/test/resources/reader/pdf" + val params = new java.util.HashMap[String, String]() + params.put("storeSplittedPdf", "true") + val sparkNLPReader = new SparkNLPReader(params) + val pdfDf = sparkNLPReader.pdf(pdfPath) + pdfDf.show() + + assert(pdfDf.count() > 0) + } + } From 638d26b67b8978dcf54daad6f68d8d2dd91bc49d Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 24 Feb 2025 17:50:54 -0500 Subject: [PATCH 067/108] [SPARKNLP-1098] Adding PdfToText notebook example --- .../SparkNLP_PDFToText_Annotator_Demo.ipynb | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb diff --git a/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb b/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb new file mode 100644 index 00000000000000..9cb6ca62ab6cfc --- /dev/null +++ b/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb @@ -0,0 +1,305 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Introducing PDFToText annotator in SparkNLP\n", + "This notebook showcases the newly added `PDFToText` method in Spark NLP that parses PDF content from both local files and distributed file systems into a Spark DataFrame." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading pdf files was introduced in Spark NLP 5.6.0 Please make sure you have upgraded to the latest Spark NLP release." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's install and setup Spark NLP in Google Colab. This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For local files example we will download a couple of PDF files from Spark NLP Github repo:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ya8qZe00dalC", + "outputId": "a54d8f71-be37-43eb-b7e7-bc4c05848358" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2025-02-24 21:31:17-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1098-Adding-a-PDF-Reader-to-Spark-NLP/src/test/resources/reader/pdf/pdf-title.pdf\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 25803 (25K) [application/octet-stream]\n", + "Saving to: โ€˜pdf-files/pdf-title.pdfโ€™\n", + "\n", + "pdf-title.pdf 100%[===================>] 25.20K --.-KB/s in 0.001s \n", + "\n", + "2025-02-24 21:31:18 (31.4 MB/s) - โ€˜pdf-files/pdf-title.pdfโ€™ saved [25803/25803]\n", + "\n", + "--2025-02-24 21:31:18-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1098-Adding-a-PDF-Reader-to-Spark-NLP/src/test/resources/reader/pdf/text_3_pages.pdf\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.110.133, 185.199.111.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 9487 (9.3K) [application/octet-stream]\n", + "Saving to: โ€˜pdf-files/text_3_pages.pdfโ€™\n", + "\n", + "text_3_pages.pdf 100%[===================>] 9.26K --.-KB/s in 0s \n", + "\n", + "2025-02-24 21:31:18 (45.7 MB/s) - โ€˜pdf-files/text_3_pages.pdfโ€™ saved [9487/9487]\n", + "\n" + ] + } + ], + "source": [ + "!mkdir pdf-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1098-Adding-a-PDF-Reader-to-Spark-NLP/src/test/resources/reader/pdf/pdf-title.pdf -P pdf-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1098-Adding-a-PDF-Reader-to-Spark-NLP/src/test/resources/reader/pdf/text_3_pages.pdf -P pdf-files" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "DczWop6QeE8F", + "outputId": "54505203-58ac-4d89-a757-4853a6832d83" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Apache Spark version: 3.5.4\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n", + "\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EoFI66NAdalE" + }, + "source": [ + "## Parsing PDFs from Local Files\n", + "Use the `PdfToText()` annotator to parse Excel content from local directories." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bAkMjJ1vdalE", + "outputId": "aabe0859-ec33-4830-e052-d3bc3a58f7e0" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Apache Spark version: 3.5.4\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "acetAKBOHbif" + }, + "source": [ + "We need to set the configuraiton below. This setting is primarily included for backward compatibility with older versions of Spark." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "id": "6SSkLxHp4Ayq" + }, + "outputs": [], + "source": [ + "spark.conf.set(\"spark.sql.legacy.allowUntypedScalaUDF\", \"true\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "id": "HHxmco4D17RB" + }, + "outputs": [], + "source": [ + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from sparknlp.reader.pdf_to_text import *\n", + "\n", + "pdf_to_text = PdfToText().setStoreSplittedPdf(True)\n", + "test_df = spark.read.format(\"binaryFile\").load(\"./pdf-examples\")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "M3peSmKx2Rt-", + "outputId": "2b6ae1d8-485e-4ed1-a2c0-728b423d46c2" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+--------------------+--------------------+------+------------------------------+----------------+---------------+--------------------+---------+-------+\n", + "| path| modificationTime|length|PdfToText_d08a2552221b__output|height_dimension|width_dimension| content|exception|pagenum|\n", + "+--------------------+--------------------+------+------------------------------+----------------+---------------+--------------------+---------+-------+\n", + "|file:/content/pdf...|2025-02-24 21:26:...| 25803| This is a Title \\...| 842| 596|[25 50 44 46 2D 3...| NULL| 0|\n", + "|file:/content/pdf...|2025-02-24 21:26:...| 9487| This is a page.\\n| 841| 595|[25 50 44 46 2D 3...| NULL| 0|\n", + "|file:/content/pdf...|2025-02-24 21:26:...| 9487| This is another p...| 841| 595|[25 50 44 46 2D 3...| NULL| 1|\n", + "|file:/content/pdf...|2025-02-24 21:26:...| 9487| Yet another page.\\n| 841| 595|[25 50 44 46 2D 3...| NULL| 2|\n", + "+--------------------+--------------------+------+------------------------------+----------------+---------------+--------------------+---------+-------+\n", + "\n" + ] + } + ], + "source": [ + "pipeline = Pipeline(stages=[pdf_to_text])\n", + "pipeline_model = pipeline.fit(test_df)\n", + "pdf_df = pipeline_model.transform(test_df)\n", + "pdf_df.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "VWbUgoVQrO8m", + "outputId": "e89dc2fd-3051-40ee-d4af-8cddccb60a91" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- path: string (nullable = true)\n", + " |-- modificationTime: timestamp (nullable = true)\n", + " |-- length: long (nullable = true)\n", + " |-- PdfToText_d08a2552221b__output: string (nullable = true)\n", + " |-- height_dimension: integer (nullable = true)\n", + " |-- width_dimension: integer (nullable = true)\n", + " |-- content: binary (nullable = true)\n", + " |-- exception: string (nullable = true)\n", + " |-- pagenum: integer (nullable = true)\n", + "\n" + ] + } + ], + "source": [ + "pdf_df.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BB2FEfegGuxl" + }, + "source": [ + "You can also use DFS file systems like:\n", + "- Databricks: `dbfs://`\n", + "- HDFS: `hdfs://`\n", + "- Microsoft Fabric OneLake: `abfss://`" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} From ffe4e2182f7a9cc636acb2cdeb7335dbfd4df560 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 26 Feb 2025 05:30:18 +0000 Subject: [PATCH 068/108] added image generation scala API Signed-off-by: Prabod Rathnayaka --- .../scala/com/johnsnowlabs/ml/ai/Janus.scala | 498 ++++++++++++++++-- .../annotators/cv/JanusforMultiModal.scala | 37 +- .../annotators/cv/util/io/ImageIOUtils.scala | 17 + .../cv/JanusForMultiModalTestSpec.scala | 92 +++- 4 files changed, 594 insertions(+), 50 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala b/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala index a123b833a943ee..a94ceadba949e1 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala @@ -25,14 +25,17 @@ import com.johnsnowlabs.nlp.AnnotatorType.DOCUMENT import com.johnsnowlabs.nlp._ import com.johnsnowlabs.nlp.annotators.common.SentenceSplit import com.johnsnowlabs.nlp.annotators.cv.util.transform.ImageResizeUtils - import com.johnsnowlabs.nlp.annotators.cv.feature_extractor.Preprocessor import com.johnsnowlabs.nlp.annotators.cv.util.io.ImageIOUtils import com.johnsnowlabs.nlp.annotators.tokenizer.bpe.{BpeTokenizer, JanusTokenizer, SpecialTokens} -import org.intel.openvino.InferRequest +import org.intel.openvino.{InferRequest, Tensor} + +import javax.imageio.ImageIO +import scala.util.Random +import scala.reflect.ClassTag import java.awt.{Color, Graphics2D} import java.awt.image.BufferedImage - +import java.io.ByteArrayOutputStream import scala.collection.JavaConverters._ private[johnsnowlabs] class Janus( @@ -78,7 +81,7 @@ private[johnsnowlabs] class Janus( merges = merges, vocab = vocabulary, specialTokens = Some(specialTokens), - addPrefixSpaceToSentence = false, + addPrefixSpaceToSentence = true, alwaysAddPrefix = false) .asInstanceOf[JanusTokenizer] @@ -92,6 +95,35 @@ private[johnsnowlabs] class Janus( sentences.map(s => bpeTokenizer.decodeTokens(s.map(_.toInt))) } + /** Encode a sequence of sentences for generation + * @param sentences + * Sequence of sentences + * @return + * Sequence of encoded sentences + */ + private def encodeTextForGeneration(sentences: Seq[Annotation]): Seq[Array[Int]] = { + val startOfImage = "" + val endOfImage = "" + val startOfImageToken = vocabulary.getOrElse(startOfImage, 100016) + val endOfImageToken = vocabulary.getOrElse(endOfImage, 100593) + + // encode text and add beginning of image token + + val tokens = SentenceSplit + .unpack(sentences) + .map(s => { + val sentWithTask = s + bpeTokenizer + .tokenize(sentWithTask) + .map(bpeTokenizer.encode) + .flatMap(_.map(_.pieceId)) + }) + .map(s => Array(bosTokenId) ++ s ++ Array(startOfImageToken)) + + tokens + + } + /** Encode a sequence of sentences * @param sentences * Sequence of sentences @@ -285,6 +317,7 @@ private[johnsnowlabs] class Janus( def predict( sentences: Seq[Annotation], imageAnnotations: Seq[AnnotationImage], + imageGenerateMode: Boolean, batchSize: Int, minOutputLength: Int, maxOutputLength: Int, @@ -297,40 +330,97 @@ private[johnsnowlabs] class Janus( randomSeed: Option[Long] = None, ignoreTokenIds: Array[Int] = Array(), beamSize: Int, - maxInputLength: Int): Seq[Annotation] = { - - val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences, preprocessor) - val tagged = tag( - encodedText, - preprocessedImages, - minOutputLength, - maxOutputLength, - doSample, - temperature, - topK, - topP, - repetitionPenalty, - noRepeatNgramSize, - randomSeed, - ignoreTokenIds, - beamSize, - maxInputLength, - Array(eosTokenId)) - val decoded = decode(tagged) - - var sentBegin, nextSentEnd = 0 - val annotations = decoded.map { content => - nextSentEnd += content.length - 1 - val annots = new Annotation( - annotatorType = DOCUMENT, - begin = sentBegin, - end = nextSentEnd, - result = content, - metadata = Map()) - sentBegin += nextSentEnd + 1 - annots + maxInputLength: Int, + numOfParallelImages: Int): Seq[Annotation] = { + + if (imageGenerateMode) { + val encodedText: Array[Array[Int]] = encodeTextForGeneration(sentences).toArray + val parallelSize = numOfParallelImages + val tokens = Array.ofDim[Int](parallelSize * 2, encodedText.head.length) + for (i <- 0 until parallelSize * 2) { + if (i % 2 != 0) { + tokens(i) = Array.fill(encodedText.head.length)(paddingTokenId) + // update the first and last token to bos and eos respectively + tokens(i)(0) = encodedText.head.head + tokens(i)(encodedText.head.length - 1) = encodedText.head.last + } else { + tokens(i) = encodedText.head + } + } + val generatedImages = generateImage( + tokens, + tokens, + parallelSize = parallelSize, + patchSize = 16, + imageSize = preprocessor.size, + randomSeed = randomSeed, + inferRequestTextEmbeddingsModel = + openvinoWrapper.get.textEmbeddingsModel.getCompiledModel().create_infer_request(), + inferRequestGenEmbeddingsModel = + openvinoWrapper.get.genEmbeddingsModel.getCompiledModel().create_infer_request(), + inferRequestGenHeadModel = + openvinoWrapper.get.genHeadModel.getCompiledModel().create_infer_request(), + inferRequestLanguageModel = + openvinoWrapper.get.languageModel.getCompiledModel().create_infer_request(), + inferRequestGenDecoderModel = + openvinoWrapper.get.genDecoderModel.getCompiledModel().create_infer_request()) + + // group generated images into ( batch_size, parallel_size) and convert them to annotations + val parallelSizeBatchedImages: Array[Array[BufferedImage]] = + generatedImages.grouped(parallelSize).toArray + + val annotations = parallelSizeBatchedImages.zip(sentences).map { case (imgs, sent) => + var metadata = Map[String, String]() + // add each image to the metadata + imgs.zipWithIndex.foreach { case (img, i) => + val bos = new ByteArrayOutputStream() + ImageIO.write(img, "png", bos) + val base64EncodedImage = java.util.Base64.getEncoder.encodeToString(bos.toByteArray) + metadata += (s"generated_image_$i" -> base64EncodedImage) + } + val annots = new Annotation( + annotatorType = DOCUMENT, + begin = 0, + end = 0, + result = sent.result, + metadata = metadata) + annots + } + annotations + } else { + val (encodedText, preprocessedImages) = encode(imageAnnotations, sentences, preprocessor) + val tagged = tag( + encodedText, + preprocessedImages, + minOutputLength, + maxOutputLength, + doSample, + temperature, + topK, + topP, + repetitionPenalty, + noRepeatNgramSize, + randomSeed, + ignoreTokenIds, + beamSize, + maxInputLength, + Array(eosTokenId)) + val decoded = decode(tagged) + + var sentBegin, nextSentEnd = 0 + val annotations = decoded.map { content => + nextSentEnd += content.length - 1 + val annots = new Annotation( + annotatorType = DOCUMENT, + begin = sentBegin, + end = nextSentEnd, + result = content, + metadata = Map()) + sentBegin += nextSentEnd + 1 + annots + } + annotations } - annotations } def getModelOutputs( @@ -411,6 +501,263 @@ private[johnsnowlabs] class Janus( decoderOutputs.toArray } + def generateImage( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + parallelSize: Int = 1, + patchSize: Int = 16, + imageSize: Int = preprocessor.size, + randomSeed: Option[Long] = None, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestGenEmbeddingsModel: InferRequest, + inferRequestGenHeadModel: InferRequest, + inferRequestLanguageModel: InferRequest, + inferRequestGenDecoderModel: InferRequest): Array[BufferedImage] = { + + val generatedTokens = getImageModelOutputs( + encoderInputIds, + decoderInputIds, + randomSeed, + inferRequestTextEmbeddingsModel, + inferRequestGenEmbeddingsModel, + inferRequestGenHeadModel, + inferRequestLanguageModel) + + inferRequestGenDecoderModel.set_tensor( + "code_b", + new org.intel.openvino.Tensor( + Array(generatedTokens.length, generatedTokens.head.length), + generatedTokens.flatten.map(_.toLong))) + + inferRequestGenDecoderModel.set_tensor( + "shape", + new org.intel.openvino.Tensor( + Array(4), + Array(parallelSize, 8, imageSize / patchSize, imageSize / patchSize).map(_.toLong))) + + inferRequestGenDecoderModel.infer() + + val dec = inferRequestGenDecoderModel.get_output_tensor() + + val decShape = dec.get_shape() + val decChannelsLast = transposeArray(dec.data(), decShape, Array(0, 2, 3, 1)) + + val decChannelsLastReshaped = + reshape4D(decChannelsLast, decShape(0), decShape(2), decShape(3), decShape(1)) + + val decClipped: Array[Array[Array[Array[Int]]]] = decChannelsLastReshaped.map { x => + x.map { y => + y.map { z => + z.map { w => + Math.min(Math.max(((w + 1) / 2) * 255, 0), 255).toInt + } + } + } + } + + // convert each image to a BufferedImage + val bufferedImages = decClipped.map { img => + ImageIOUtils.arrayToBufferedImage(img) + } + bufferedImages + } + + def getImageModelOutputs( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + randomSeed: Option[Long] = None, + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestGenEmbeddingsModel: InferRequest, + inferRequestGenHeadModel: InferRequest, + inferRequestLanguageModel: InferRequest): Array[Array[Int]] = { + + var generatedTokens: Array[Array[Int]] = Array() + var nextInputEmbedsTensor: Option[org.intel.openvino.Tensor] = None + var decoderInputIdsCopied = decoderInputIds.clone() + // run the model for imageTokenLength times + for (i <- 0 until imageTokenLength) { + val nextTokenIds = getNextImageTokens( + encoderInputIds, + decoderInputIdsCopied, + cfgWeight = 5.0f, + temperature = 1.0f, + randomSeed = randomSeed, + inputEmbeds = nextInputEmbedsTensor, + inferRequestTextEmbeddingsModel, + inferRequestGenHeadModel, + inferRequestLanguageModel) + + val nextTokenIdsTensor = new org.intel.openvino.Tensor( + Array(nextTokenIds.length * 2), + nextTokenIds.flatMap(x => Array(x, x)).map(_.toLong)) + + inferRequestGenEmbeddingsModel.set_input_tensor(nextTokenIdsTensor) + inferRequestGenEmbeddingsModel.infer() + + val imageEmbeddings = inferRequestGenEmbeddingsModel.get_output_tensor() + + nextInputEmbedsTensor = Some( + new org.intel.openvino.Tensor( + Array(imageEmbeddings.get_shape()(0), 1, imageEmbeddings.get_shape()(1)), + imageEmbeddings.data())) + + if (generatedTokens.isEmpty) { + generatedTokens = nextTokenIds.map(Array(_)) + } else { + generatedTokens = + generatedTokens.zip(nextTokenIds).map { case (currentIds: Array[Int], nextId: Int) => + currentIds ++ Array(nextId) + } + } + + // repeat the nextTokenIds twice and add them to the decoder input ids + val repeatedNextTokenIds = nextTokenIds.flatMap(x => Array(x, x)) + + // extend decoder input ids to include the generated tokens. Decoder input ids are duplicated for each image + decoderInputIdsCopied = + decoderInputIdsCopied.zip(repeatedNextTokenIds).map { case (currentIds, nextId) => + currentIds ++ Array(nextId) + } + } + generatedTokens + } + + private def getNextImageTokens( + encoderInputIds: Array[Array[Int]], + decoderInputIds: Array[Array[Int]], + cfgWeight: Float = 5.0f, + temperature: Float = 1.0f, + randomSeed: Option[Long] = None, + inputEmbeds: Option[Tensor], + inferRequestTextEmbeddingsModel: InferRequest, + inferRequestGenHeadModel: InferRequest, + inferRequestLanguageModel: InferRequest): Array[Int] = { + + val (inputIdsLong, inputPositionIDsLong): (Array[Long], Array[Long]) = + if (encoderInputIds.head.length == decoderInputIds.head.length) { + // First pass + val inpIdsLong = decoderInputIds.flatMap { tokenIds => tokenIds.map(_.toLong) } + val posIdsLong = decoderInputIds.flatMap { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + } + } + (inpIdsLong, posIdsLong) + } else { + // Subsequent passes + val inpIdsLong = decoderInputIds.map { tokenIds => tokenIds.last.toLong } + val posIdsLong = decoderInputIds.map { tokenIds => + tokenIds.zipWithIndex.map { case (_, i) => + i.toLong + }.last + } + (inpIdsLong, posIdsLong) + } + val attentionMask: Array[Long] = + decoderInputIds.flatMap { tokenIds => tokenIds.map(_ => 1L) } + + val batchSize: Int = decoderInputIds.length + val beamIdx: Array[Int] = new Array[Int](batchSize) + val shape: Array[Int] = Array(batchSize, inputIdsLong.length / batchSize) + + val decoderAttentionMask: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize, decoderInputIds.head.length), attentionMask) + val decoderPositionIDs: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputPositionIDsLong) + val beamIdxTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(Array(batchSize), beamIdx) + + val inputEmbedsTensor: org.intel.openvino.Tensor = if (inputEmbeds.isDefined) { + inputEmbeds.get + } else { + val inputIdsLongTensor: org.intel.openvino.Tensor = + new org.intel.openvino.Tensor(shape, inputIdsLong) + inferRequestTextEmbeddingsModel.set_input_tensor(inputIdsLongTensor) + inferRequestTextEmbeddingsModel.infer() + + val textEmbeddings = inferRequestTextEmbeddingsModel.get_output_tensor() + textEmbeddings + } + + inferRequestLanguageModel.set_tensor("inputs_embeds", inputEmbedsTensor) + inferRequestLanguageModel.set_tensor("attention_mask", decoderAttentionMask) + inferRequestLanguageModel.set_tensor("position_ids", decoderPositionIDs) + inferRequestLanguageModel.set_tensor("beam_idx", beamIdxTensor) + + inferRequestLanguageModel.infer() + + val result = inferRequestLanguageModel.get_tensor("last_hidden_state") + val resultShape = result.get_shape() + // select the last hidden state + // (2*parallel_images, sequence_length, hidden_size) + // Reshape the tensor + val reshapedArray: Array[Array[Array[Float]]] = + reshape3D(result.data(), resultShape(0), resultShape(1), resultShape(2)) + val lastResult = reshapedArray.map { x => + x(resultShape(1) - 1) + }.toArray + val lastResultTensor = + new org.intel.openvino.Tensor(Array(resultShape(0), resultShape(2)), lastResult.flatten) + + inferRequestGenHeadModel.set_input_tensor(lastResultTensor) + inferRequestGenHeadModel.infer() + + val logits = inferRequestGenHeadModel.get_output_tensor() + val logitsShape = logits.get_shape() + + val logitsRaw = logits.data() + val reshapedLogits: Array[Array[Float]] = + reshape2D(logitsRaw, logitsShape(0), logitsShape(1)) + + // every second element starting from 0 to the end will be the conditional logits + val logitCond = reshapedLogits.zipWithIndex.collect { + case (logit, i) if i % 2 == 0 => logit + } + // every second element starting from 1 to the end will be the unconditional logits + val logitUncond = reshapedLogits.zipWithIndex.collect { + case (logit, i) if i % 2 == 1 => logit + } + + val logitDiff = logitCond.zip(logitUncond).map { case (cond, uncond) => + cond.zip(uncond).map { case (c, u) => + ((u + cfgWeight * (c - u)) / temperature).toFloat + } + } + val probs = logitDiff.map(softmax) + val nextTokenIds = multinomial(probs, seed = randomSeed) + nextTokenIds.map(_.head) + } + + private def multinomial( + probs: Array[Array[Float]], + numSamples: Int = 1, + seed: Option[Long] = None): Array[Array[Int]] = { + val random = seed.map(s => new Random(s)).getOrElse(new Random()) + + probs.map { p => + require(p.forall(_ >= 0.0f), "Probabilities must be non-negative") + require(p.sum >= 0.99f && p.sum <= 1.01f, "Probabilities must sum to approximately 1.0") + + if (p.isEmpty) { + throw new IllegalArgumentException("Probability array cannot be empty") + } + + if (p.forall(_ == 0.0f)) { + throw new IllegalArgumentException("Probability array cannot contain all zeros") + } + + val cumSum = p.scanLeft(0.0f)(_ + _).tail + + (0 until numSamples).map { _ => + val rand = random.nextFloat() + cumSum.zipWithIndex + .find { case (c, _) => c > rand } + .map(_._2) + .getOrElse(throw new IllegalStateException("Could not find a valid category")) + }.toArray + }.toArray + } + private def argmax(scores: Array[Float]): Int = scores.zipWithIndex.maxBy { case (score, _) => score @@ -582,4 +929,81 @@ private[johnsnowlabs] class Janus( imageEmbeddings } + def softmax(logitValues: Array[Float]): Array[Float] = { + val maxLogit = logitValues.max + val logitsExp = logitValues.map(l => Math.exp(l - maxLogit)) + val expSum = logitsExp.sum + logitsExp.map(exp => (exp / expSum).toFloat) + } + + // Function to reshape the flattened array + def reshapeArray(flatArray: Array[Float], shape: Array[Int]): Any = { + require(flatArray.length == shape.product, "Shape does not match data length") + + def recursiveReshape(data: Array[Float], shape: List[Int]): Any = shape match { + case Nil => data.head // Base case: return a single element + case head :: Nil => data.grouped(head).toArray.asInstanceOf[Array[Any]] // 1D array + case head :: tail => + data + .grouped(head) + .map(subArr => recursiveReshape(subArr, tail)) + .toArray + .asInstanceOf[Array[Any]] // Cast to Array[Any] to preserve structure + } + + recursiveReshape(flatArray, shape.toList).asInstanceOf[Array[Any]] + } + + def reshape2D(data: Array[Float], rows: Int, cols: Int): Array[Array[Float]] = { + data.grouped(cols).toArray.map(_.toArray) + } + + def reshape3D( + data: Array[Float], + depth: Int, + rows: Int, + cols: Int): Array[Array[Array[Float]]] = { + data.grouped(rows * cols).toArray.map { slice => + reshape2D(slice, rows, cols) + } + } + + def reshape4D( + data: Array[Float], + batch: Int, + depth: Int, + rows: Int, + cols: Int): Array[Array[Array[Array[Float]]]] = { + data.grouped(depth * rows * cols).toArray.map { slice => + reshape3D(slice, depth, rows, cols) + } + } + + def transposeArray[T: ClassTag]( + inputArray: Array[T], + inputArrayShape: Array[Int], + axes: Array[Int]): Array[T] = { + require( + inputArrayShape.length == axes.length, + "Axes must have the same length as the shape dimensions") + + val outputShape = axes.map(inputArrayShape(_)) + val size = inputArray.length + val inputStrides = inputArrayShape.scanRight(1)(_ * _).tail + val outputStrides = outputShape.scanRight(1)(_ * _).tail + + def getTransposedIndex(index: Int): Int = { + val originalIndices = + inputArrayShape.indices.map(i => (index / inputStrides(i)) % inputArrayShape(i)) + val transposedIndices = axes.map(originalIndices) + transposedIndices.zip(outputStrides).map { case (idx, stride) => idx * stride }.sum + } + + val outputArray = new Array[T](size) + for (i <- inputArray.indices) { + outputArray(getTransposedIndex(i)) = inputArray(i) + } + outputArray + } + } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala index 6c404e1b983346..defa124fecf5cc 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala @@ -35,7 +35,7 @@ import com.johnsnowlabs.ml.openvino.{OpenvinoWrapper, ReadOpenvinoModel, WriteOp import com.johnsnowlabs.ml.openvino.OpenvinoWrapper.JanusWrappers import com.johnsnowlabs.nlp.serialization.{MapFeature, StructFeature} import org.apache.spark.broadcast.Broadcast -import org.apache.spark.ml.param.{IntArrayParam, IntParam} +import org.apache.spark.ml.param.{IntArrayParam, IntParam, BooleanParam} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.SparkSession @@ -134,14 +134,6 @@ class JanusForMultiModal(override val uid: String) override val inputAnnotatorTypes: Array[AnnotatorType] = Array(IMAGE) override val outputAnnotatorType: AnnotatorType = DOCUMENT - /** @group setParam */ - def setRandomSeed(value: Int): JanusForMultiModal.this.type = { - if (randomSeed.isEmpty) { - this.randomSeed = Some(value) - } - this - } - /** A list of token ids which are ignored in the decoder's output (Default: `Array()`) * * @group param @@ -228,6 +220,24 @@ class JanusForMultiModal(override val uid: String) /** @group getParam */ def getImageTokenLength: Int = $(imageTokenLength) + val imageGenerateMode: BooleanParam = + new BooleanParam(this, "imageGenerateMode", "Image generation mode") + + /** @group setParam */ + def setImageGenerateMode(value: Boolean): this.type = set(imageGenerateMode, value) + + /** @group getParam */ + def getImageGenerateMode: Boolean = $(imageGenerateMode) + + val numOfParallelImages: IntParam = + new IntParam(this, "numOfParallelImages", "Number of parallel images to Generate") + + /** @group setParam */ + def setNumOfParallelImages(value: Int): this.type = set(numOfParallelImages, value) + + /** @group getParam */ + def getNumOfParallelImages: Int = $(numOfParallelImages) + /** @group setParam */ def setModelIfNotSet( spark: SparkSession, @@ -269,7 +279,9 @@ class JanusForMultiModal(override val uid: String) maxInputLength -> 4096, stopTokenIds -> Array(2), imageToken -> 100594, - imageTokenLength -> 576) + imageTokenLength -> 576, + imageGenerateMode -> false, + numOfParallelImages -> 1) /** takes a document and annotations and produces new annotations of this annotator's annotation * type @@ -295,6 +307,7 @@ class JanusForMultiModal(override val uid: String) getModelIfNotSet.predict( questionAnnotations, validImages.toSeq, + imageGenerateMode = $(imageGenerateMode), batchSize = $(batchSize), minOutputLength = $(minOutputLength), maxOutputLength = $(maxOutputLength), @@ -307,7 +320,8 @@ class JanusForMultiModal(override val uid: String) randomSeed = this.randomSeed, ignoreTokenIds = $(ignoreTokenIds), beamSize = $(beamSize), - maxInputLength = $(maxInputLength)) + maxInputLength = $(maxInputLength), + numOfParallelImages = $(numOfParallelImages)) } } @@ -593,6 +607,7 @@ trait ReadJanusForMultiModalDLModel extends ReadOpenvinoModel { .setImageMean(preprocessorConfig.image_mean) .setImageStd(preprocessorConfig.image_std) .setResample(preprocessorConfig.resample) + .setRandomSeed(214567L) val modelEngine = if (useOpenvino) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala index ca5be6ba37dfdb..7c3374295d065d 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala @@ -203,4 +203,21 @@ private[johnsnowlabs] object ImageIOUtils { } + def arrayToBufferedImage(pixelArray: Array[Array[Array[Int]]]): BufferedImage = { + val height = pixelArray.length + val width = pixelArray.head.length + val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) + + for (x <- pixelArray.indices; y <- pixelArray(x).indices) { + val rgb = pixelArray(y)(x) match { + case Array(r, g, b) => new Color(r, g, b).getRGB + case _ => + throw new IllegalArgumentException( + "Each pixel must have exactly 3 color channels (RGB)") + } + image.setRGB(x, y, rgb) + } + image + } + } diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala index 3cb5f1e9a5f68b..c3493e9edaec59 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala @@ -18,15 +18,43 @@ package com.johnsnowlabs.nlp.annotators.cv import com.johnsnowlabs.nlp.base.LightPipeline import com.johnsnowlabs.nlp.util.io.ResourceHelper -import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, ImageAssembler} +import com.johnsnowlabs.nlp.{Annotation, AnnotationImage, AssertAnnotations, ImageAssembler} import com.johnsnowlabs.tags.{FastTest, SlowTest} import org.apache.spark.ml.Pipeline import org.apache.spark.sql.DataFrame import org.apache.spark.sql.functions.lit import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers._ +import java.nio.file.{Files, Paths} +import java.nio.charset.StandardCharsets +import java.io.{File, FileOutputStream} class JanusForMultiModalTestSpec extends AnyFlatSpec { + def reshape2D(data: Array[Float], rows: Int, cols: Int): Array[Array[Float]] = { + data.grouped(cols).toArray.map(_.toArray) + } + + def reshape3D( + data: Array[Float], + depth: Int, + rows: Int, + cols: Int): Array[Array[Array[Float]]] = { + data.grouped(rows * cols).toArray.map { slice => + reshape2D(slice, rows, cols) + } + } + + def reshape4D( + data: Array[Float], + batch: Int, + depth: Int, + rows: Int, + cols: Int): Array[Array[Array[Array[Float]]]] = { + data.grouped(depth * rows * cols).toArray.map { slice => + reshape3D(slice, depth, rows, cols) + } + } lazy val model = getJanusForMultiModalPipelineModel "JanusForMultiModal" should "answer a question for a given image" taggedAs SlowTest in { @@ -34,6 +62,7 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { val testDF = getTestDF val result = model.transform(testDF) + result.printSchema() val answerAnnotation = AssertAnnotations.getActualResult(result, "answer") answerAnnotation.foreach { annotation => @@ -45,6 +74,65 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { } } + "reshape2D" should "reshape a 1D array into a 2D array" taggedAs FastTest in { + val data = Array(1f, 2f, 3f, 4f, 5f, 6f) + val rows = 2 + val cols = 3 + val expected = Array(Array(1f, 2f, 3f), Array(4f, 5f, 6f)) + reshape2D(data, rows, cols) shouldEqual expected + } + + "reshape3D" should "reshape a 1D array into a 3D array" taggedAs FastTest in { + val data = Array(1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f) + val depth = 2 + val rows = 2 + val cols = 3 + val expected = Array( + Array(Array(1f, 2f, 3f), Array(4f, 5f, 6f)), + Array(Array(7f, 8f, 9f), Array(10f, 11f, 12f))) + reshape3D(data, depth, rows, cols) shouldBe expected + } + + it should "generate images when generate image mode is set to true" taggedAs FastTest in { + model.stages.last.asInstanceOf[JanusForMultiModal].setImageGenerateMode(true) + val lightPipeline = new LightPipeline(model) + val imagePath = "src/test/resources/images/image1.jpg" + val resultAnnotate = + lightPipeline.fullAnnotateImage( + imagePath, + "User: A close-up professional photo of Yorkshire Terrier on beach, extremely detailed, hyper realistic, full hd resolution, with a blurred background. The dog is looking at the camera, with a curious expression, and its fur is shiny and well-groomed. The beach is sandy, with gentle waves lapping at the shore, and a clear blue sky overhead. The lighting is soft and natural, casting a warm glow over the scene. The overall mood is peaceful and serene, capturing a moment of quiet contemplation and connection with nature.\n\nAssistant:") +// "User: Create a detailed image of a whimsical forest filled with vibrant, oversized mushrooms, glowing flowers, and towering, twisted trees with bioluminescent vines. The atmosphere is magical, with soft, ethereal light filtering through a misty canopy. Small floating orbs of light hover among the branches, and tiny fairy-like creatures flit through the air. A winding, moss-covered path leads to a mysterious glowing portal hidden within the trees. The scene should feel enchanting, otherworldly, and full of wonder, like a dreamlike fantasy realm.\n\nAssistant:") + + val answerAnnotation = resultAnnotate("answer").head.asInstanceOf[Annotation] + println(s"imageName.result: ${answerAnnotation.result}") + + // generated image should be in the metadata as a base64 string with the keys "generated_image_0", "generated_image_1", etc. + // find the keys that contain the generated images + val generatedImageKeys = answerAnnotation.metadata.keys.filter(_.contains("generated_image")) + + assert(generatedImageKeys.nonEmpty) +// println(s"generated_image: ${answerAnnotation.metadata("generated_image")}") + + for (key <- generatedImageKeys) { + val generatedImage = answerAnnotation.metadata(key).asInstanceOf[String] + val decodedImage = + java.util.Base64.getDecoder.decode(generatedImage) + // save the image to the disk + val fos = + new FileOutputStream(new File(s"src/test/resources/images/generated_image_$key.jpg")) + fos.write(decodedImage) + fos.close() + } +// // save the generated image by decoding the base64 string +// val generatedImage = answerAnnotation.metadata("generated_image_0").asInstanceOf[String] +// val decodedImage = +// java.util.Base64.getDecoder.decode(generatedImage) +// // save the image to the disk +// val fos = +// new FileOutputStream(new File("src/test/resources/images/generated_image.jpg")) +// fos.write(decodedImage) +// fos.close() + } it should "work with light pipeline annotate" taggedAs SlowTest in { val lightPipeline = new LightPipeline(model) @@ -160,7 +248,7 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { .setOutputCol("image_assembler") val loadModel = JanusForMultiModal - .pretrained() + .loadSavedModel("/mnt/research/Projects/ModelZoo/Janus/Janus-1.3B-ov", ResourceHelper.spark) .setInputCols("image_assembler") .setOutputCol("answer") .setMaxOutputLength(50) From dd2a4009cb39b663b7025977d6e7d31b8b2429f2 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 26 Feb 2025 18:08:23 +1100 Subject: [PATCH 069/108] Update HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb --- ...ingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb index f9f0f3e406cbb4..f884487e043d5e 100644 --- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb @@ -608,30 +608,6 @@ "- This part is pretty easy via our simple script" ] }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processing /home/prabod/Projects/spark-nlp/python/dist/spark_nlp-5.5.3-py2.py3-none-any.whl\n", - "Collecting pyspark==3.2.3\n", - " Using cached pyspark-3.2.3-py2.py3-none-any.whl\n", - "Collecting py4j==0.10.9.5 (from pyspark==3.2.3)\n", - " Downloading py4j-0.10.9.5-py2.py3-none-any.whl.metadata (1.5 kB)\n", - "Downloading py4j-0.10.9.5-py2.py3-none-any.whl (199 kB)\n", - "Installing collected packages: spark-nlp, py4j, pyspark\n", - "Successfully installed py4j-0.10.9.5 pyspark-3.2.3 spark-nlp-5.5.3\n" - ] - } - ], - "source": [ - "! pip install /home/prabod/Projects/spark-nlp/python/dist/spark_nlp-5.5.3-py2.py3-none-any.whl pyspark==3.2.3" - ] - }, { "cell_type": "code", "execution_count": null, From b4dc46244c9422381c1111ec65dc9c05b75665ce Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 26 Feb 2025 18:12:13 +1100 Subject: [PATCH 070/108] Update HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb --- .../HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb index f884487e043d5e..ec06c05c8267f9 100644 --- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb @@ -461,15 +461,6 @@ "preprocessing_masks = core.compile_model(model_path / PREPROCESSING_MASKS_NAME,\"CPU\")\n" ] }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "model_dir = Path(\"/mnt/research/Projects/ModelZoo/LLAMA-3.2-VI/Llama-3.2-11B-Vision-Instruct/OV\")\n" - ] - }, { "cell_type": "code", "execution_count": null, From c92a544da17ccdaeb72654c8a9eadd7f81ac3b44 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 26 Feb 2025 18:14:36 +1100 Subject: [PATCH 071/108] Update HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb --- ...HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb index ec06c05c8267f9..fed93f08de9242 100644 --- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb @@ -298,15 +298,6 @@ "}\n" ] }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "model_path = Path(\"/mnt/research/Projects/ModelZoo/LLAMA-3.2-VI/Llama-3.2-11B-Vision-Instruct/OV\")\n" - ] - }, { "cell_type": "code", "execution_count": null, @@ -742,7 +733,7 @@ } ], "source": [ - "imageClassifier.write().overwrite().save(\"file:///mnt/research/MLLama_spark_nlp\")" + "imageClassifier.write().overwrite().save(\"file:///tmp/MLLama_spark_nlp\")" ] }, { @@ -769,7 +760,7 @@ } ], "source": [ - "!ls -lah /mnt/research/MLLama_spark_nlp" + "!ls -lah /tmp/MLLama_spark_nlp" ] }, { @@ -807,7 +798,7 @@ "\n", "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n", "\n", - "imageClassifier = MLLamaForMultimodal.load(\"file:///mnt/research/MLLama_spark_nlp\")\\\n", + "imageClassifier = MLLamaForMultimodal.load(\"file:///tmp/MLLama_spark_nlp\")\\\n", " .setMaxOutputLength(50) \\\n", " .setInputCols(\"image_assembler\") \\\n", " .setOutputCol(\"answer\")\n", From f7d5893f0d36c2f1f09b0382eca2a91a1b921698 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Wed, 26 Feb 2025 18:16:15 +1100 Subject: [PATCH 072/108] Update HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb --- ...ingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb index fed93f08de9242..f31513353542a1 100644 --- a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_MLLama.ipynb @@ -599,59 +599,6 @@ "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" ] }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "25/02/14 02:48:12 WARN Utils: Your hostname, minotaur resolves to a loopback address: 127.0.1.1; using 192.168.1.4 instead (on interface eno1)\n", - "25/02/14 02:48:12 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n", - "25/02/14 02:48:12 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Setting default log level to \"WARN\".\n", - "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "25/02/14 02:48:13 WARN Utils: Service 'SparkUI' could not bind on port 4040. Attempting port 4041.\n" - ] - } - ], - "source": [ - "\n", - "from sparknlp.base import *\n", - "from sparknlp.annotator import *\n", - "\n", - "from sparknlp.pretrained import PretrainedPipeline\n", - "import sparknlp\n", - "\n", - "from pyspark.sql import SparkSession\n", - "from pyspark.ml import Pipeline, PipelineModel\n", - "\n", - "SPARKNLP_JAR = \"/home/prabod/Projects/spark-nlp/python/lib/sparknlp.jar\"\n", - "spark = SparkSession.builder \\\n", - " .master('local[*]') \\\n", - " .appName('Spark NLP') \\\n", - " .config(\"spark.driver.memory\", \"60g\") \\\n", - " .config(\"spark.driver.maxResultSize\", \"0G\") \\\n", - " .config(\"spark.serializer\", \"org.apache.spark.serializer.KryoSerializer\") \\\n", - " .config(\"spark.kryoserializer.buffer.max\", \"2000M\") \\\n", - " .config(\"spark.jars\", SPARKNLP_JAR) \\\n", - " .getOrCreate()" - ] - }, { "cell_type": "markdown", "metadata": {}, From afd7338aa0c8846f86dc83ce14ac84f4fa23e027 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 4 Mar 2025 01:09:51 +0000 Subject: [PATCH 073/108] added image generation python API and tests Signed-off-by: Prabod Rathnayaka --- .../annotator/cv/janus_for_multimodal.py | 27 ++++++ .../scala/com/johnsnowlabs/ml/ai/Janus.scala | 84 ++++++++++++------- .../annotators/cv/JanusforMultiModal.scala | 1 - .../annotators/cv/util/io/ImageIOUtils.scala | 2 +- .../cv/JanusForMultiModalTestSpec.scala | 20 ++--- 5 files changed, 87 insertions(+), 47 deletions(-) diff --git a/python/sparknlp/annotator/cv/janus_for_multimodal.py b/python/sparknlp/annotator/cv/janus_for_multimodal.py index b88a75f6948d9d..5646815368808b 100644 --- a/python/sparknlp/annotator/cv/janus_for_multimodal.py +++ b/python/sparknlp/annotator/cv/janus_for_multimodal.py @@ -151,6 +151,12 @@ class JanusForMultiModal(AnnotatorModel, beamSize = Param(Params._dummy(), "beamSize", "The Number of beams for beam search.", typeConverter=TypeConverters.toInt) + imageGenerateMode = Param(Params._dummy(), "imageGenerateMode", + "Image generation mode", + typeConverter=TypeConverters.toBoolean) + numOfParallelImages = Param(Params._dummy(), "numOfParallelImages", + "Number of parallel images to Generate", + typeConverter=TypeConverters.toInt) def setMaxSentenceSize(self, value): """Sets Maximum sentence length that the annotator will process, by @@ -268,6 +274,25 @@ def setBeamSize(self, value): Number of beam size for beam search """ return self._set(beamSize=value) + + def setImageGenerateMode(self, value): + """Sets the image generation mode. + Parameters + ---------- + value : bool + Image generation mode + """ + return self._set(imageGenerateMode=value) + + def setNumOfParallelImages(self, value): + """Sets the number of parallel images to generate. + Parameters + ---------- + value : int + Number of parallel images to generate + """ + return self._set(numOfParallelImages=value) + @keyword_only def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cv.JanusForMultiModal", java_model=None): @@ -287,6 +312,8 @@ def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cv.JanusForMultiMo noRepeatNgramSize=0, ignoreTokenIds=[], beamSize=1, + imageGenerateMode=False, + numOfParallelImages=1 ) @staticmethod diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala b/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala index a94ceadba949e1..5e374edbd71313 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/Janus.scala @@ -15,6 +15,7 @@ */ package com.johnsnowlabs.ml.ai +import java.lang.Math import com.johnsnowlabs.ml.ai.util.Generation.GenerationConfig import com.johnsnowlabs.ml.onnx.OnnxWrapper.DecoderWrappers @@ -85,6 +86,8 @@ private[johnsnowlabs] class Janus( alwaysAddPrefix = false) .asInstanceOf[JanusTokenizer] + var randomSeedGenerator = new Random() + /** Decode a sequence of sentences * @param sentences * Sequence of sentences @@ -334,6 +337,7 @@ private[johnsnowlabs] class Janus( numOfParallelImages: Int): Seq[Annotation] = { if (imageGenerateMode) { + randomSeedGenerator = randomSeed.map(s => new Random(s)).getOrElse(new Random()) val encodedText: Array[Array[Int]] = encodeTextForGeneration(sentences).toArray val parallelSize = numOfParallelImages val tokens = Array.ofDim[Int](parallelSize * 2, encodedText.head.length) @@ -586,7 +590,6 @@ private[johnsnowlabs] class Janus( inferRequestTextEmbeddingsModel, inferRequestGenHeadModel, inferRequestLanguageModel) - val nextTokenIdsTensor = new org.intel.openvino.Tensor( Array(nextTokenIds.length * 2), nextTokenIds.flatMap(x => Array(x, x)).map(_.toLong)) @@ -596,6 +599,7 @@ private[johnsnowlabs] class Janus( val imageEmbeddings = inferRequestGenEmbeddingsModel.get_output_tensor() + nextInputEmbedsTensor = None nextInputEmbedsTensor = Some( new org.intel.openvino.Tensor( Array(imageEmbeddings.get_shape()(0), 1, imageEmbeddings.get_shape()(1)), @@ -708,24 +712,24 @@ private[johnsnowlabs] class Janus( val logitsRaw = logits.data() val reshapedLogits: Array[Array[Float]] = reshape2D(logitsRaw, logitsShape(0), logitsShape(1)) - - // every second element starting from 0 to the end will be the conditional logits - val logitCond = reshapedLogits.zipWithIndex.collect { - case (logit, i) if i % 2 == 0 => logit - } + // every second element starting from 0 to the end will be the conditional logits\ + val logitCond = reshapedLogits.zipWithIndex.filter(_._2 % 2 == 0).map(_._1) // every second element starting from 1 to the end will be the unconditional logits - val logitUncond = reshapedLogits.zipWithIndex.collect { - case (logit, i) if i % 2 == 1 => logit - } + val logitUncond = reshapedLogits.zipWithIndex.filter(_._2 % 2 == 1).map(_._1) val logitDiff = logitCond.zip(logitUncond).map { case (cond, uncond) => cond.zip(uncond).map { case (c, u) => - ((u + cfgWeight * (c - u)) / temperature).toFloat + u + cfgWeight * (c - u) } } + val probs = logitDiff.map(softmax) - val nextTokenIds = multinomial(probs, seed = randomSeed) + val nextTokenIds = multinomial(probs, numSamples = 1, seed = randomSeed) + // pick a random token from the nextTokenIds +// val randomIndex = new Random() +// nextTokenIds.map(x => x(randomIndex.nextInt(x.length))) nextTokenIds.map(_.head) + } private def multinomial( @@ -733,27 +737,20 @@ private[johnsnowlabs] class Janus( numSamples: Int = 1, seed: Option[Long] = None): Array[Array[Int]] = { val random = seed.map(s => new Random(s)).getOrElse(new Random()) - probs.map { p => + require(p.nonEmpty, "Probability array cannot be empty") require(p.forall(_ >= 0.0f), "Probabilities must be non-negative") - require(p.sum >= 0.99f && p.sum <= 1.01f, "Probabilities must sum to approximately 1.0") - - if (p.isEmpty) { - throw new IllegalArgumentException("Probability array cannot be empty") - } - - if (p.forall(_ == 0.0f)) { - throw new IllegalArgumentException("Probability array cannot contain all zeros") - } + require(Math.abs(p.sum - 1.0f) < 1e-3, "Probabilities must sum to approximately 1.0") + require(p.exists(_ > 0.0f), "Probability array cannot contain all zeros") - val cumSum = p.scanLeft(0.0f)(_ + _).tail + val cumSum = p.scanLeft(0.0f)(_ + _).drop(1) (0 until numSamples).map { _ => - val rand = random.nextFloat() - cumSum.zipWithIndex - .find { case (c, _) => c > rand } - .map(_._2) - .getOrElse(throw new IllegalStateException("Could not find a valid category")) + val rand = Math.nextAfter(random.nextFloat(), Float.PositiveInfinity) + cumSum.indexWhere(_ > rand) match { + case -1 => cumSum.length - 1 // Ensure a valid index is always chosen + case idx => idx + } }.toArray }.toArray } @@ -936,6 +933,15 @@ private[johnsnowlabs] class Janus( logitsExp.map(exp => (exp / expSum).toFloat) } + // logSoftmax + def logSoftmax(logitValues: Array[Float]): Array[Float] = { + val maxLogit = logitValues.max + val logitsExp = logitValues.map(l => Math.exp(l - maxLogit)) + val expSum = logitsExp.sum + val logSumExp = Math.log(expSum) + logitValues.map(l => l - maxLogit - logSumExp).map(_.toFloat) + } + // Function to reshape the flattened array def reshapeArray(flatArray: Array[Float], shape: Array[Int]): Any = { require(flatArray.length == shape.product, "Shape does not match data length") @@ -955,7 +961,14 @@ private[johnsnowlabs] class Janus( } def reshape2D(data: Array[Float], rows: Int, cols: Int): Array[Array[Float]] = { - data.grouped(cols).toArray.map(_.toArray) +// data.grouped(cols).toArray.map(_.toArray) +// i * sequenceLength * vocabSize + (sequenceLength - 1) * vocabSize, +// i * sequenceLength * vocabSize + sequenceLength * vocabSize) + 0.until(rows) + .map { i => + data.slice(i * cols, (i + 1) * cols) + } + .toArray } def reshape3D( @@ -963,9 +976,18 @@ private[johnsnowlabs] class Janus( depth: Int, rows: Int, cols: Int): Array[Array[Array[Float]]] = { - data.grouped(rows * cols).toArray.map { slice => - reshape2D(slice, rows, cols) - } +// data.grouped(rows * cols).toArray.map { slice => +// reshape2D(slice, rows, cols) +// } + // use the depth to slice the data + 0.until(depth) + .map { i => + data.slice(i * rows * cols, (i + 1) * rows * cols) + } + .map { slice => + reshape2D(slice, rows, cols) + } + .toArray } def reshape4D( diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala index defa124fecf5cc..793fa7bc549cfe 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/JanusforMultiModal.scala @@ -607,7 +607,6 @@ trait ReadJanusForMultiModalDLModel extends ReadOpenvinoModel { .setImageMean(preprocessorConfig.image_mean) .setImageStd(preprocessorConfig.image_std) .setResample(preprocessorConfig.resample) - .setRandomSeed(214567L) val modelEngine = if (useOpenvino) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala index 7c3374295d065d..532bb7253630aa 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cv/util/io/ImageIOUtils.scala @@ -208,7 +208,7 @@ private[johnsnowlabs] object ImageIOUtils { val width = pixelArray.head.length val image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) - for (x <- pixelArray.indices; y <- pixelArray(x).indices) { + for (y <- pixelArray.indices; x <- pixelArray(y).indices) { val rgb = pixelArray(y)(x) match { case Array(r, g, b) => new Color(r, g, b).getRGB case _ => diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala index c3493e9edaec59..e0735a5d77b0c2 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cv/JanusForMultiModalTestSpec.scala @@ -74,7 +74,7 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { } } - "reshape2D" should "reshape a 1D array into a 2D array" taggedAs FastTest in { + "reshape2D" should "reshape a 1D array into a 2D array" taggedAs SlowTest in { val data = Array(1f, 2f, 3f, 4f, 5f, 6f) val rows = 2 val cols = 3 @@ -82,7 +82,7 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { reshape2D(data, rows, cols) shouldEqual expected } - "reshape3D" should "reshape a 1D array into a 3D array" taggedAs FastTest in { + "reshape3D" should "reshape a 1D array into a 3D array" taggedAs SlowTest in { val data = Array(1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f) val depth = 2 val rows = 2 @@ -93,8 +93,10 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { reshape3D(data, depth, rows, cols) shouldBe expected } - it should "generate images when generate image mode is set to true" taggedAs FastTest in { + it should "generate images when generate image mode is set to true" taggedAs SlowTest in { model.stages.last.asInstanceOf[JanusForMultiModal].setImageGenerateMode(true) + model.stages.last.asInstanceOf[JanusForMultiModal].setRandomSeed(123467L) + model.stages.last.asInstanceOf[JanusForMultiModal].setNumOfParallelImages(1) val lightPipeline = new LightPipeline(model) val imagePath = "src/test/resources/images/image1.jpg" val resultAnnotate = @@ -111,7 +113,6 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { val generatedImageKeys = answerAnnotation.metadata.keys.filter(_.contains("generated_image")) assert(generatedImageKeys.nonEmpty) -// println(s"generated_image: ${answerAnnotation.metadata("generated_image")}") for (key <- generatedImageKeys) { val generatedImage = answerAnnotation.metadata(key).asInstanceOf[String] @@ -123,15 +124,6 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { fos.write(decodedImage) fos.close() } -// // save the generated image by decoding the base64 string -// val generatedImage = answerAnnotation.metadata("generated_image_0").asInstanceOf[String] -// val decodedImage = -// java.util.Base64.getDecoder.decode(generatedImage) -// // save the image to the disk -// val fos = -// new FileOutputStream(new File("src/test/resources/images/generated_image.jpg")) -// fos.write(decodedImage) -// fos.close() } it should "work with light pipeline annotate" taggedAs SlowTest in { @@ -248,7 +240,7 @@ class JanusForMultiModalTestSpec extends AnyFlatSpec { .setOutputCol("image_assembler") val loadModel = JanusForMultiModal - .loadSavedModel("/mnt/research/Projects/ModelZoo/Janus/Janus-1.3B-ov", ResourceHelper.spark) + .pretrained() .setInputCols("image_assembler") .setOutputCol("answer") .setMaxOutputLength(50) From 8eeccf7c078e8e6b994fd4de2b217b52afb30f52 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 5 Mar 2025 20:06:15 -0500 Subject: [PATCH 074/108] [SPARKNLP-1117] Adding storeContent to HTML, Word and Email readers --- .../reader/SparkNLP_Email_Reader_Demo.ipynb | 320 +++++++++++++++--- .../reader/SparkNLP_HTML_Reader_Demo.ipynb | 285 +++++++++++++--- .../reader/SparkNLP_Word_Reader_Demo.ipynb | 121 +++++-- .../com/johnsnowlabs/reader/EmailReader.scala | 7 +- .../com/johnsnowlabs/reader/HTMLReader.scala | 13 +- .../johnsnowlabs/reader/SparkNLPReader.scala | 20 +- .../com/johnsnowlabs/reader/WordReader.scala | 5 +- .../johnsnowlabs/reader/EmailReaderTest.scala | 14 +- .../johnsnowlabs/reader/HTMLReaderTest.scala | 19 +- .../johnsnowlabs/reader/WordReaderTest.scala | 13 + 10 files changed, 679 insertions(+), 138 deletions(-) diff --git a/examples/python/reader/SparkNLP_Email_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_Email_Reader_Demo.ipynb index 1e35592f81f748..1574e3d6f202bc 100644 --- a/examples/python/reader/SparkNLP_Email_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_Email_Reader_Demo.ipynb @@ -48,25 +48,6 @@ "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Additional Configuration for Databricks" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When running on Databricks, it is necessary to include the following Spark configurations to avoid dependency conflicts:\n", - "\n", - "- `spark.driver.userClassPathFirst true`\n", - "- `spark.executor.userClassPathFirst true`\n", - "\n", - "These configurations are required because the Databricks runtime environment includes a bundled version of the `com.sun.mail:jakarta.mail` library, which conflicts with `jakarta.activation`. By setting these properties, the application ensures that the user-provided libraries take precedence over those bundled in the Databricks environment, resolving the dependency conflict." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -76,41 +57,42 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ya8qZe00dalC", - "outputId": "a9916407-f76d-4c59-fdad-ea17ca0a4326" + "outputId": "d5d30ba3-710f-481a-c68a-b97f8a808db6" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "mkdir: cannot create directory โ€˜email-filesโ€™: File exists\n", - "--2024-11-13 21:01:15-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1093-Adding-support-to-read-Email-files/src/test/resources/reader/email/email-text-attachments.eml\n", + "--2025-03-06 00:20:35-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/email/email-text-attachments.eml\n", "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 3175 (3.1K) [text/plain]\n", "Saving to: โ€˜email-files/email-text-attachments.emlโ€™\n", "\n", + "\r", + " email-tex 0%[ ] 0 --.-KB/s \r", "email-text-attachme 100%[===================>] 3.10K --.-KB/s in 0s \n", "\n", - "2024-11-13 21:01:15 (29.9 MB/s) - โ€˜email-files/email-text-attachments.emlโ€™ saved [3175/3175]\n", + "2025-03-06 00:20:35 (34.6 MB/s) - โ€˜email-files/email-text-attachments.emlโ€™ saved [3175/3175]\n", "\n", - "--2024-11-13 21:01:15-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1093-Adding-support-to-read-Email-files/src/test/resources/reader/email/test-several-attachments.eml\n", - "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", - "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "--2025-03-06 00:20:35-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/email/test-several-attachments.eml\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.111.133, 185.199.110.133, 185.199.108.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.111.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 1324361 (1.3M) [text/plain]\n", "Saving to: โ€˜email-files/test-several-attachments.emlโ€™\n", "\n", - "test-several-attach 100%[===================>] 1.26M --.-KB/s in 0.05s \n", + "test-several-attach 100%[===================>] 1.26M --.-KB/s in 0.01s \n", "\n", - "2024-11-13 21:01:16 (26.7 MB/s) - โ€˜email-files/test-several-attachments.emlโ€™ saved [1324361/1324361]\n", + "2025-03-06 00:20:35 (126 MB/s) - โ€˜email-files/test-several-attachments.emlโ€™ saved [1324361/1324361]\n", "\n" ] } @@ -123,13 +105,13 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "3xgGItNbU2DZ", - "outputId": "12f8a7be-f9b4-49ce-a9ab-222142f28293" + "outputId": "ddb35b87-76e6-41d3-ffda-9cf00108b2c3" }, "outputs": [ { @@ -137,8 +119,8 @@ "output_type": "stream", "text": [ "total 1.3M\n", - "-rw-r--r-- 1 root root 3.2K Nov 13 21:01 email-text-attachments.eml\n", - "-rw-r--r-- 1 root root 1.3M Nov 13 21:01 test-several-attachments.eml\n" + "-rw-r--r-- 1 root root 3.2K Mar 6 00:20 email-text-attachments.eml\n", + "-rw-r--r-- 1 root root 1.3M Mar 6 00:20 test-several-attachments.eml\n" ] } ], @@ -158,13 +140,13 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "bAkMjJ1vdalE", - "outputId": "4b360b6c-5049-4f10-bb52-60e0e0e52e52" + "outputId": "d1f3f6e0-b8d8-4d2e-c83b-e41be1e8767e" }, "outputs": [ { @@ -175,8 +157,8 @@ "+--------------------+\n", "| email|\n", "+--------------------+\n", - "|[{Title, Email Te...|\n", "|[{Title, Test Sev...|\n", + "|[{Title, Email Te...|\n", "+--------------------+\n", "\n" ] @@ -191,13 +173,13 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "7CMPPubFTeHj", - "outputId": "48ee68cf-0f7f-408a-a855-2fd2eb2e8bd1" + "outputId": "360f6051-718f-48fe-9650-55886141b383" }, "outputs": [ { @@ -206,7 +188,6 @@ "text": [ "root\n", " |-- path: string (nullable = true)\n", - " |-- content: binary (nullable = true)\n", " |-- email: array (nullable = true)\n", " | |-- element: struct (containsNull = true)\n", " | | |-- elementType: string (nullable = true)\n", @@ -228,10 +209,263 @@ "id": "Qooecm9VTeus" }, "source": [ - "You can also use DFS file systems like:\n", - "- Databricks: `dbfs://`\n", - "- HDFS: `hdfs://`\n", - "- Microsoft Fabric OneLake: `abfss://`" + "You can also use DFS like Databricks `dbfs://` or HDFS directories `hdfs://`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "a5uyqiYQo9Xe" + }, + "source": [ + "### Configuration Parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9Lcd5fvozko6" + }, + "source": [ + "Let's add an email file for this example." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "S2ub8DT5pEq2", + "outputId": "03967d94-7e83-424a-ee3b-d01e1e913d29" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2025-03-06 00:20:57-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/email/email-text-attachments.eml\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.108.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 3175 (3.1K) [text/plain]\n", + "Saving to: โ€˜email-files/email-text-attachments.eml.1โ€™\n", + "\n", + "\r", + " email-tex 0%[ ] 0 --.-KB/s \r", + "email-text-attachme 100%[===================>] 3.10K --.-KB/s in 0s \n", + "\n", + "2025-03-06 00:20:57 (27.2 MB/s) - โ€˜email-files/email-text-attachments.eml.1โ€™ saved [3175/3175]\n", + "\n" + ] + } + ], + "source": [ + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/email/email-text-attachments.eml -P email-files" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jg3XfrrnpBm3" + }, + "source": [ + "- `addAttachmentContent`: By default, this is set to `false`. When enabled, the output will include the content of attachments." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cBNdYiRfq7kr", + "outputId": "45cc42f2-ef11-4f25-f1bb-ae641fb9d9b6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n" + ] + } + ], + "source": [ + "params = {\"addAttachmentContent\": \"true\"}\n", + "email_df = sparknlp.read(params).email(\"./email-files/email-text-attachments.eml\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "UzkzDBXHrtcc", + "outputId": "0a362808-43d8-4566-f185-e174ab98c42a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|elementType |content |\n", + "+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|NarrativeText|Email test with two text attachments\\r\\n\\r\\nCheers,\\r\\n\\r\\n |\n", + "|NarrativeText|\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\nEmail  test with two text attachments\\r\\n

    \\r\\n
    \\r\\nCheers,
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n\\r\\n\\r\\n|\n", + "|NarrativeText|This is the content of the file.\\n |\n", + "|NarrativeText|This is an additional content file.\\n |\n", + "+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from pyspark.sql.functions import explode, col\n", + "\n", + "narrative_text_df = (\n", + " email_df\n", + " .select(\n", + " explode(col(\"email\")).alias(\"email_element\")\n", + " )\n", + " .filter(col(\"email_element.elementType\") == \"NarrativeText\")\n", + " .select(\n", + " col(\"email_element.elementType\"),\n", + " col(\"email_element.content\")\n", + " )\n", + ")\n", + "\n", + "narrative_text_df.show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "k-AUDev1zraM" + }, + "source": [ + "As you can see in the dataframe above the NarrativeText include the data from the attached text files." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OKotsvSspORA", + "outputId": "303d5057-6812-4900-e0f6-d4a7686298b9" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "email_df = sparknlp.read().email(\"./email-files/email-text-attachments.eml\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Ut_K1uidvaAu", + "outputId": "09c7e16f-f513-46a1-9e57-6e429bd993b4" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|elementType |content |\n", + "+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|NarrativeText|Email test with two text attachments\\r\\n\\r\\nCheers,\\r\\n\\r\\n |\n", + "|NarrativeText|\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\nEmail  test with two text attachments\\r\\n
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\nCheers,
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n\\r\\n\\r\\n|\n", + "+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from pyspark.sql.functions import explode, col\n", + "\n", + "narrative_text_df = (\n", + " email_df\n", + " .select(\n", + " explode(col(\"email\")).alias(\"email_element\")\n", + " )\n", + " .filter(col(\"email_element.elementType\") == \"NarrativeText\")\n", + " .select(\n", + " col(\"email_element.elementType\"),\n", + " col(\"email_element.content\")\n", + " )\n", + ")\n", + "\n", + "narrative_text_df.show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wusCNu2oz1Jq" + }, + "source": [ + "As you can see in the dataframe above the NarrativeText does not include the data from the attached text files." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GtUESNPQq4rz" + }, + "source": [ + "- `storeContent`: By default, this is set to `false`. When enabled, the output will include the byte content of the file." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "4OmCy_S4pXeC", + "outputId": "caa62b94-94bf-4b00-ad48-6a690ecf740a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| email| content|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/ema...|[{Title, Email Te...|[46 72 6F 6D 3A 2...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"storeContent\": \"true\"}\n", + "email_df = sparknlp.read(params).email(\"./email-files/email-text-attachments.eml\")\n", + "email_df.show()" ] } ], diff --git a/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb index 99782a9e04683c..703630fb9d6fb9 100644 --- a/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb @@ -23,6 +23,131 @@ "- Versatile support for varied data ingestion scenarios." ] }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "xrvHhiTAdfGd", + "outputId": "4641f9f8-bcaf-4804-b909-7583c76f880d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mounted at /content/drive\n" + ] + } + ], + "source": [ + "from google.colab import drive\n", + "drive.mount('/content/drive')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "mjV3NcQ8eA52" + }, + "outputs": [], + "source": [ + "!cp drive/MyDrive/JSL/sparknlp/sparknlp.jar .\n", + "!cp drive/MyDrive/JSL/sparknlp/spark_nlp-5.5.3-py2.py3-none-any.whl ." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "pEmutNjReCgc", + "outputId": "999d0e5a-a849-4ff7-e03d-86ed753ec53d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pyspark in /usr/local/lib/python3.11/dist-packages (3.5.5)\n", + "Requirement already satisfied: py4j==0.10.9.7 in /usr/local/lib/python3.11/dist-packages (from pyspark) (0.10.9.7)\n" + ] + } + ], + "source": [ + "!pip install pyspark" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3qjPeDjvfCpA", + "outputId": "d6ada02d-57f1-4a73-dbcc-f4e1691224f8" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing ./spark_nlp-5.5.3-py2.py3-none-any.whl\n", + "Installing collected packages: spark-nlp\n", + "Successfully installed spark-nlp-5.5.3\n" + ] + } + ], + "source": [ + "!pip install spark_nlp-5.5.3-py2.py3-none-any.whl" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "DczWop6QeE8F", + "outputId": "c267cdef-37a3-43a2-9198-f8fe74100f97" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Apache Spark version: 3.5.5\n" + ] + } + ], + "source": [ + "# import sparknlp\n", + "# # let's start Spark with Spark NLP\n", + "# spark = sparknlp.start()\n", + "\n", + "from pyspark.sql import SparkSession\n", + "\n", + "spark = SparkSession.builder \\\n", + " .appName(\"SparkNLP\") \\\n", + " .master(\"local[*]\") \\\n", + " .config(\"spark.driver.memory\", \"12G\") \\\n", + " .config(\"spark.serializer\", \"org.apache.spark.serializer.KryoSerializer\") \\\n", + " .config(\"spark.kryoserializer.buffer.max\", \"2000M\") \\\n", + " .config(\"spark.driver.maxResultSize\", \"0\") \\\n", + " .config(\"spark.jars\", \"./sparknlp.jar\") \\\n", + " .getOrCreate()\n", + "\n", + "\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, { "cell_type": "markdown", "metadata": { @@ -32,12 +157,14 @@ "## Setup and Initialization\n", "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", - "Support for reading html files was introduced in `Spark NLP 5.5.2`. Please make sure you have upgraded to the latest Spark NLP release." + "Support for reading html files was introduced in Spark NLP 5.5.2. Please make sure you have upgraded to the latest Spark NLP release." ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "Y3hWfT5q-npM" + }, "source": [ "- Let's install and setup Spark NLP in Google Colab\n", "- This part is pretty easy via our simple script" @@ -46,7 +173,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "id": "u3ORYVyb-pRI" + }, "outputs": [], "source": [ "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" @@ -54,7 +183,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "oIbFQyEo-tat" + }, "source": [ "For local files example we will download a couple of HTML files from Spark NLP Github repo:" ] @@ -67,27 +198,25 @@ "base_uri": "https://localhost:8080/" }, "id": "ya8qZe00dalC", - "outputId": "4399cc35-31d4-459c-bee8-d7eeba3d40cd" + "outputId": "96efd082-1c63-414b-d07c-6d41074bd397" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "--2024-11-05 20:02:19-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1089-Support-more-file-types-in-SparkNLP/src/test/resources/reader/html/example-10k.html\n", + "--2025-03-05 23:21:42-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/html/example-10k.html\n", "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 2456707 (2.3M) [text/plain]\n", "Saving to: โ€˜html-files/example-10k.htmlโ€™\n", "\n", - "\r", - "example-10k.html 0%[ ] 0 --.-KB/s \r", - "example-10k.html 100%[===================>] 2.34M --.-KB/s in 0.01s \n", + "example-10k.html 100%[===================>] 2.34M --.-KB/s in 0.08s \n", "\n", - "2024-11-05 20:02:19 (157 MB/s) - โ€˜html-files/example-10k.htmlโ€™ saved [2456707/2456707]\n", + "2025-03-05 23:21:43 (30.6 MB/s) - โ€˜html-files/example-10k.htmlโ€™ saved [2456707/2456707]\n", "\n", - "--2024-11-05 20:02:20-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1089-Support-more-file-types-in-SparkNLP/src/test/resources/reader/html/fake-html.html\n", + "--2025-03-05 23:21:43-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/html/fake-html.html\n", "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", @@ -96,7 +225,7 @@ "\n", "fake-html.html 100%[===================>] 665 --.-KB/s in 0s \n", "\n", - "2024-11-05 20:02:20 (41.9 MB/s) - โ€˜html-files/fake-html.htmlโ€™ saved [665/665]\n", + "2025-03-05 23:21:43 (40.3 MB/s) - โ€˜html-files/fake-html.htmlโ€™ saved [665/665]\n", "\n" ] } @@ -125,7 +254,7 @@ "base_uri": "https://localhost:8080/" }, "id": "bAkMjJ1vdalE", - "outputId": "c4bb38d4-963d-465b-e222-604dc6b617aa" + "outputId": "ff94b96a-71b3-44e2-af0b-86b83d6f604f" }, "outputs": [ { @@ -133,12 +262,12 @@ "output_type": "stream", "text": [ "Warning::Spark Session already created, some configs may not take.\n", - "+--------------------+--------------------+--------------------+\n", - "| path| content| html|\n", - "+--------------------+--------------------+--------------------+\n", - "|file:/content/htm...|\\n...|[{Title, 0, My Fi...|\n", - "|file:/content/htm...| 1}}, {NarrativeText, This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission., {pageNumber -> 1}}, {NarrativeText, More information... More information..., {pageNumber -> 1}}]|\n", + "+--------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", "\n" ] } ], "source": [ "html_df = sparknlp.read().html(\"https://example.com/\")\n", - "html_df.select(\"html\").show()" + "html_df.show(truncate=False)" ] }, { "cell_type": "code", "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "oBj0cHPXSD1m", + "outputId": "995f2dfc-9491-4b83-965f-c97b415ef524" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- url: string (nullable = true)\n", + " |-- html: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- elementType: string (nullable = true)\n", + " | | |-- content: string (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + "\n" + ] + } + ], + "source": [ + "html_df.printSchema()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "-psYdzWodalG", - "outputId": "544cd7e3-93a6-465a-8b9a-52d487d63b21" + "outputId": "04c0fb8a-73c6-4002-baa9-7655ce8f9239" }, "outputs": [ { @@ -219,8 +370,8 @@ "+--------------------+--------------------+\n", "| url| html|\n", "+--------------------+--------------------+\n", - "|https://www.wikip...|[{Title, 0, Wikip...|\n", - "|https://example.com/|[{Title, 0, Examp...|\n", + "|https://www.wikip...|[{Title, Wikipedi...|\n", + "|https://example.com/|[{Title, Example ...|\n", "+--------------------+--------------------+\n", "\n" ] @@ -251,13 +402,13 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "aNfN0fQC0Vzz", - "outputId": "0b849a86-2d59-4415-981a-dcd9a9f7a14a" + "outputId": "8e9c511b-e1fb-41df-affd-47dfacf3d4c9" }, "outputs": [ { @@ -265,11 +416,11 @@ "output_type": "stream", "text": [ "Warning::Spark Session already created, some configs may not take.\n", - "+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", - "|html |\n", - "+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", - "|[{Title, 0, My First Heading, {pageNumber -> 1}}, {Title, 0, My Second Heading, {pageNumber -> 1}}, {NarrativeText, 0, My first paragraph. lorem ipsum dolor set amet. if the cow comes home under the sun how do you fault the cow for it's worn hooves?, {pageNumber -> 1}}, {Title, 0, A Third Heading, {pageNumber -> 1}}, {Table, 0, Column 1 Column 2 Row 1, Cell 1 Row 1, Cell 2 Row 2, Cell 1 Row 2, Cell 2, {pageNumber -> 1}}]|\n", - "+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|html |\n", + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{Title, My First Heading, {pageNumber -> 1}}, {Title, My Second Heading, {pageNumber -> 1}}, {NarrativeText, My first paragraph. lorem ipsum dolor set amet. if the cow comes home under the sun how do you fault the cow for it's worn hooves?, {pageNumber -> 1}}, {Title, A Third Heading, {pageNumber -> 1}}, {Table, Column 1 Column 2 Row 1, Cell 1 Row 1, Cell 2 Row 2, Cell 1 Row 2, Cell 2, {pageNumber -> 1}}]|\n", + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", "\n" ] } @@ -279,6 +430,46 @@ "html_df = sparknlp.read(params).html(\"./html-files/fake-html.html\")\n", "html_df.select(\"html\").show(truncate=False)" ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "O8DePUq8nkYm" + }, + "source": [ + "You can access the raw content of the file using the `storeContent` parameter" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "jTM1btqNntUL", + "outputId": "aa96124f-4ea6-4d33-b04e-47fa4af0bbe6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| content| html|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/htm...|\\n...|[{Title, My First...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"storeContent\": \"true\"}\n", + "html_df = sparknlp.read(params).html(\"./html-files/fake-html.html\")\n", + "html_df.show()" + ] } ], "metadata": { diff --git a/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb index 15f4f99a2ca33a..e48ca628b56a4a 100644 --- a/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb @@ -2,7 +2,9 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "fVCTDXvj23JY" + }, "source": [ "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", "\n", @@ -33,7 +35,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "_lK9PBpd23Je" + }, "source": [ "- Let's install and setup Spark NLP in Google Colab\n", "- This part is pretty easy via our simple script" @@ -41,8 +45,10 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 1, + "metadata": { + "id": "diBT0PwL23Je" + }, "outputs": [], "source": [ "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" @@ -50,38 +56,42 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "HWjx-reJ23Jf" + }, "source": [ "For local files example we will download a couple of Word files from Spark NLP Github repo:" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ya8qZe00dalC", - "outputId": "f6800bce-c101-47e3-8030-cf1a0b758183" + "outputId": "d4ac0a0d-edd7-4126-cf01-9ad5ed0500a3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "--2024-12-11 02:43:35-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1094-Adding-support-to-read-Word-files-v2/src/test/resources/reader/doc/contains-pictures.docx\n", - "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.111.133, 185.199.109.133, 185.199.108.133, ...\n", - "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.111.133|:443... connected.\n", + "--2025-03-06 00:33:05-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/doc/contains-pictures.docx\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.111.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 95087 (93K) [application/octet-stream]\n", "Saving to: โ€˜word-files/contains-pictures.docxโ€™\n", "\n", - "contains-pictures.d 100%[===================>] 92.86K --.-KB/s in 0.04s \n", + "\r", + "contains-pictures.d 0%[ ] 0 --.-KB/s \r", + "contains-pictures.d 100%[===================>] 92.86K --.-KB/s in 0.02s \n", "\n", - "2024-12-11 02:43:35 (2.47 MB/s) - โ€˜word-files/contains-pictures.docxโ€™ saved [95087/95087]\n", + "2025-03-06 00:33:06 (3.86 MB/s) - โ€˜word-files/contains-pictures.docxโ€™ saved [95087/95087]\n", "\n", - "--2024-12-11 02:43:36-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1094-Adding-support-to-read-Word-files-v2/src/test/resources/reader/doc/fake_table.docx\n", + "--2025-03-06 00:33:06-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/doc/fake_table.docx\n", "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.110.133, ...\n", "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", @@ -90,7 +100,7 @@ "\n", "fake_table.docx 100%[===================>] 12.10K --.-KB/s in 0s \n", "\n", - "2024-12-11 02:43:36 (24.7 MB/s) - โ€˜word-files/fake_table.docxโ€™ saved [12392/12392]\n", + "2025-03-06 00:33:06 (99.2 MB/s) - โ€˜word-files/fake_table.docxโ€™ saved [12392/12392]\n", "\n" ] } @@ -103,13 +113,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "oZLpFt7qcWoC", - "outputId": "6e5ce0b8-383a-481c-9b7b-d4250d385f25" + "outputId": "4a0b4ef5-40e8-4020-e5f4-3a2002a0fc61" }, "outputs": [ { @@ -117,8 +127,8 @@ "output_type": "stream", "text": [ "total 112K\n", - "-rw-r--r-- 1 root root 93K Dec 11 02:43 contains-pictures.docx\n", - "-rw-r--r-- 1 root root 13K Dec 11 02:43 fake_table.docx\n" + "-rw-r--r-- 1 root root 93K Mar 6 00:33 contains-pictures.docx\n", + "-rw-r--r-- 1 root root 13K Mar 6 00:33 fake_table.docx\n" ] } ], @@ -138,13 +148,13 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 15, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "_3GKYbmScehR", - "outputId": "24941880-c772-4b4e-dd0d-349fe8ea31c9" + "outputId": "8a0cba04-4db8-4705-ccb4-4c7b8f74fc99" }, "outputs": [ { @@ -163,31 +173,31 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 16, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "eKOYqIigmlmh", - "outputId": "1a3ec3b7-b49d-420b-cdaf-e4682b4f66e1" + "outputId": "f437fcf7-247e-4fda-d8cf-855c7fd6e6c3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "+--------------------+\n", - "| doc|\n", - "+--------------------+\n", - "|[{Table, Header C...|\n", - "|[{Header, An inli...|\n", - "+--------------------+\n", + "+--------------------+--------------------+\n", + "| path| doc|\n", + "+--------------------+--------------------+\n", + "|file:/content/wor...|[{Header, An inli...|\n", + "|file:/content/wor...|[{Table, Header C...|\n", + "+--------------------+--------------------+\n", "\n" ] } ], "source": [ - "doc_df.select(\"doc\").show()" + "doc_df.show()" ] }, { @@ -198,7 +208,7 @@ "base_uri": "https://localhost:8080/" }, "id": "IoC1eqPPcmqN", - "outputId": "b994396c-b670-49af-8bb9-b5e6ff44e8fe" + "outputId": "73acbe65-0844-446a-f59a-6549dddfdd47" }, "outputs": [ { @@ -207,7 +217,6 @@ "text": [ "root\n", " |-- path: string (nullable = true)\n", - " |-- content: binary (nullable = true)\n", " |-- doc: array (nullable = true)\n", " | |-- element: struct (containsNull = true)\n", " | | |-- elementType: string (nullable = true)\n", @@ -234,6 +243,56 @@ "- HDFS: `hdfs://`\n", "- Microsoft Fabric OneLake: `abfss://`" ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1DHIwRe13Ko7" + }, + "source": [ + "### Configuration Parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FFnRYtys3Tv6" + }, + "source": [ + "- `storeContent`: By default, this is set to `false`. When enabled, the output will include the byte content of the file." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "EY9qzmZu3NC8", + "outputId": "0d0916b1-b0ca-4c58-b723-dcad794cd3e3" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| doc| content|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/wor...|[{Header, An inli...|[50 4B 03 04 14 0...|\n", + "|file:/content/wor...|[{Table, Header C...|[50 4B 03 04 14 0...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"storeContent\": \"true\"}\n", + "doc_df = sparknlp.read(params).doc(\"./word-files\")\n", + "doc_df.show()" + ] } ], "metadata": { diff --git a/src/main/scala/com/johnsnowlabs/reader/EmailReader.scala b/src/main/scala/com/johnsnowlabs/reader/EmailReader.scala index 76ed05e6b5815e..993058a0a7c5b3 100644 --- a/src/main/scala/com/johnsnowlabs/reader/EmailReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/EmailReader.scala @@ -26,7 +26,8 @@ import java.util.Properties import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -class EmailReader(addAttachmentContent: Boolean = false) extends Serializable { +class EmailReader(addAttachmentContent: Boolean = false, storeContent: Boolean = false) + extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -38,9 +39,11 @@ class EmailReader(addAttachmentContent: Boolean = false) extends Serializable { val byteArray = portableDataStream.toArray() (path, byteArray) } - byteArrayRDD + val emailDf = byteArrayRDD .toDF("path", "content") .withColumn("email", parseEmailUDF(col("content"))) + if (storeContent) emailDf.select("path", "email", "content") + else emailDf.select("path", "email") } else throw new IllegalArgumentException(s"Invalid filePath: $filePath") } diff --git a/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala b/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala index 01640ef04ce660..bc64128e664d3a 100644 --- a/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala @@ -18,7 +18,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper import com.johnsnowlabs.nlp.util.io.ResourceHelper.{isValidURL, validFile} import org.apache.spark.sql.DataFrame -import org.apache.spark.sql.functions.{col, udf} +import org.apache.spark.sql.functions.{col, lit, udf} import org.jsoup.Jsoup import org.jsoup.nodes.{Document, Element, Node, TextNode} @@ -26,7 +26,7 @@ import scala.collection.JavaConverters._ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -class HTMLReader(titleFontSize: Int = 16) extends Serializable { +class HTMLReader(titleFontSize: Int = 16, storeContent: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -35,16 +35,19 @@ class HTMLReader(titleFontSize: Int = 16) extends Serializable { ResourceHelper match { case _ if validFile(inputSource) && !inputSource.startsWith("http") => - spark.sparkContext + val htmlDf = spark.sparkContext .wholeTextFiles(inputSource) .toDF("path", "content") .withColumn("html", parseHtmlUDF(col("content"))) - + if (storeContent) htmlDf.select("path", "content", "html") + else htmlDf.select("path", "html") case _ if isValidURL(inputSource) => - spark + val htmlDf = spark .createDataset(Seq(inputSource)) .toDF("url") .withColumn("html", parseURLUDF(col("url"))) + if (storeContent) htmlDf.select("url", "content", "html") + else htmlDf.select("url", "html") case _ => throw new IllegalArgumentException(s"Invalid inputSource: $inputSource") } diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 713949a4cd701a..89474c5ed9f7b1 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -68,17 +68,17 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM */ def html(htmlPath: String): DataFrame = { - val htmlReader = new HTMLReader(getTitleFontSize) + val htmlReader = new HTMLReader(getTitleFontSize, getStoreContent) htmlReader.read(htmlPath) } def html(urls: Array[String]): DataFrame = { - val htmlReader = new HTMLReader(getTitleFontSize) + val htmlReader = new HTMLReader(getTitleFontSize, getStoreContent) htmlReader.read(urls) } def html(urls: java.util.List[String]): DataFrame = { - val htmlReader = new HTMLReader(getTitleFontSize) + val htmlReader = new HTMLReader(getTitleFontSize, getStoreContent) htmlReader.read(urls.asScala.toArray) } @@ -93,6 +93,16 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM titleFontSize } + private def getStoreContent: Boolean = { + val storeContent = + try { + params.asScala.getOrElse("storeContent", "false").toBoolean + } catch { + case _: IllegalArgumentException => false + } + storeContent + } + /** Instantiates class to read email files. * * emailPath: this is a path to a directory of HTML files or a path to an HTML file E.g. @@ -137,7 +147,7 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM */ def email(emailPath: String): DataFrame = { - val emailReader = new EmailReader(getAddAttachmentContent) + val emailReader = new EmailReader(getAddAttachmentContent, getStoreContent) emailReader.read(emailPath) } @@ -152,7 +162,7 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM } def doc(docPath: String): DataFrame = { - val wordReader = new WordReader() + val wordReader = new WordReader(getStoreContent) wordReader.doc(docPath) } diff --git a/src/main/scala/com/johnsnowlabs/reader/WordReader.scala b/src/main/scala/com/johnsnowlabs/reader/WordReader.scala index 208a88d4073c86..36c5e2a64fee91 100644 --- a/src/main/scala/com/johnsnowlabs/reader/WordReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/WordReader.scala @@ -28,7 +28,7 @@ import java.io.{ByteArrayInputStream, IOException} import scala.collection.JavaConverters._ import scala.collection.mutable -class WordReader extends Serializable { +class WordReader(storeContent: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -40,9 +40,10 @@ class WordReader extends Serializable { val byteArray = portableDataStream.toArray() (path, byteArray) } - byteArrayRDD + val wordDf = byteArrayRDD .toDF("path", "content") .withColumn("doc", parseWordUDF(col("content"))) + if (storeContent) wordDf.select("path", "doc", "content") else wordDf.select("path", "doc") } else throw new IllegalArgumentException(s"Invalid filePath: $filePath") } diff --git a/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala index cb04b68d5948be..ab5ac27bb2cea5 100644 --- a/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala @@ -34,6 +34,7 @@ class EmailReaderTest extends AnyFlatSpec { emailDf.printSchema() assert(!emailDf.select(col("email").getItem(0)).isEmpty) + assert(!emailDf.columns.contains("content")) } it should "read email file with attachments" taggedAs FastTest in { @@ -56,11 +57,11 @@ class EmailReaderTest extends AnyFlatSpec { .filter($"elementType" === ElementType.NARRATIVE_TEXT) .count() - println(s"textCount = $textCount") assert(!emailDf.select(col("email").getItem(0)).isEmpty) assert(attachmentCount == 3) assert(titleCount == 1) assert(textCount == 2) + assert(!emailDf.columns.contains("content")) } it should "read email file with two text attachments" taggedAs FastTest in { @@ -88,6 +89,7 @@ class EmailReaderTest extends AnyFlatSpec { assert(attachmentCount == 2) assert(titleCount == 1) assert(textCount == 2) + assert(!emailDf.columns.contains("content")) } it should "read attachment content when addAttachmentContent = true" taggedAs FastTest in { @@ -115,6 +117,16 @@ class EmailReaderTest extends AnyFlatSpec { assert(attachmentCount == 2) assert(titleCount == 1) assert(textCount == 4) + assert(!emailDf.columns.contains("content")) + } + + it should "store content" taggedAs FastTest in { + val emailReader = new EmailReader(storeContent = true) + val emailDf = emailReader.read(emailDirectory) + emailDf.show() + + assert(!emailDf.select(col("email").getItem(0)).isEmpty) + assert(emailDf.columns.contains("content")) } } diff --git a/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala index 8c43f0ac996066..8bcac1eb90b0e8 100644 --- a/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala @@ -25,8 +25,11 @@ class HTMLReaderTest extends AnyFlatSpec { it should "read html as dataframe" taggedAs FastTest in { val HTMLReader = new HTMLReader() - val result = HTMLReader.read(htmlFilesDirectory) - result.show() + val htmlDF = HTMLReader.read(htmlFilesDirectory) + htmlDF.show() + + assert(!htmlDF.select(col("html").getItem(0)).isEmpty) + assert(!htmlDF.columns.contains("content")) } it should "read html as dataframe with params" taggedAs FastTest in { @@ -35,6 +38,7 @@ class HTMLReaderTest extends AnyFlatSpec { htmlDF.show() assert(!htmlDF.select(col("html").getItem(0)).isEmpty) + assert(!htmlDF.columns.contains("content")) } it should "parse an html in real time" taggedAs FastTest in { @@ -43,6 +47,7 @@ class HTMLReaderTest extends AnyFlatSpec { htmlDF.show() assert(!htmlDF.select(col("html").getItem(0)).isEmpty) + assert(!htmlDF.columns.contains("content")) } it should "parse URLS in real time" taggedAs FastTest in { @@ -51,6 +56,16 @@ class HTMLReaderTest extends AnyFlatSpec { htmlDF.show() assert(!htmlDF.select(col("html").getItem(0)).isEmpty) + assert(!htmlDF.columns.contains("content")) + } + + it should "store content" taggedAs FastTest in { + val HTMLReader = new HTMLReader(storeContent = true) + val htmlDF = HTMLReader.read(htmlFilesDirectory) + htmlDF.show() + + assert(!htmlDF.select(col("html").getItem(0)).isEmpty) + assert(htmlDF.columns.contains("content")) } } diff --git a/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala index d98293cf595833..4b56c0cbd2ba7b 100644 --- a/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala @@ -33,6 +33,7 @@ class WordReaderTest extends AnyFlatSpec { wordDf.select("doc").show(false) assert(!wordDf.select(col("doc").getItem(0)).isEmpty) + assert(!wordDf.columns.contains("content")) } "WordReader" should "read a docx file with page breaks" taggedAs FastTest in { @@ -46,6 +47,7 @@ class WordReaderTest extends AnyFlatSpec { .count() assert(pageBreakCount == 5) + assert(!wordDf.columns.contains("content")) } "WordReader" should "read a docx file with tables" taggedAs FastTest in { @@ -54,6 +56,7 @@ class WordReaderTest extends AnyFlatSpec { wordDf.select("doc").show(false) assert(!wordDf.select(col("doc").getItem(0)).isEmpty) + assert(!wordDf.columns.contains("content")) } "WordReader" should "read a docx file with images on it" taggedAs FastTest in { @@ -62,6 +65,16 @@ class WordReaderTest extends AnyFlatSpec { wordDf.select("doc").show(false) assert(!wordDf.select(col("doc").getItem(0)).isEmpty) + assert(!wordDf.columns.contains("content")) + } + + "WordReader" should "store content" taggedAs FastTest in { + val wordReader = new WordReader(storeContent = true) + val wordDf = wordReader.doc(s"$docDirectory") + wordDf.select("doc").show(false) + + assert(!wordDf.select(col("doc").getItem(0)).isEmpty) + assert(wordDf.columns.contains("content")) } } From 9386b44e1113cfa8d6a96c72d27baf55dd743526 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Thu, 6 Mar 2025 09:36:52 -0500 Subject: [PATCH 075/108] [SPARKNLP-1117] Refactoring documentation for readers --- python/sparknlp/reader/sparknlp_reader.py | 83 ++++++++++++++++--- .../johnsnowlabs/reader/SparkNLPReader.scala | 45 +++++++++- 2 files changed, 116 insertions(+), 12 deletions(-) diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 06b8309bd45258..da9baa8ddaed70 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -17,11 +17,6 @@ class SparkNLPReader(ExtendedJavaWrapper): """Instantiates class to read HTML, email, and document files. - Two types of input paths are supported: - - - `htmlPath`: A path to a directory of HTML files or a single HTML file (e.g., `"path/html/files"`). - - `url`: A single URL or a set of URLs (e.g., `"https://www.wikipedia.org"`). - Parameters ---------- spark : SparkSession @@ -59,11 +54,29 @@ def html(self, htmlPath): >>> import sparknlp >>> html_df = sparknlp.read().html("https://www.wikipedia.org") >>> html_df.show(truncate=False) + + +--------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |url |html | + +--------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |https://example.com/|[{Title, Example Domain, {pageNumber -> 1}}, {NarrativeText, 0, This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission., {pageNumber -> 1}}, {NarrativeText, 0, More information... More information..., {pageNumber -> 1}}] | + +--------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + >>> html_df.printSchema() + + root + |-- url: string (nullable = true) + |-- html: array (nullable = true) + | |-- element: struct (containsNull = true) + | | |-- elementType: string (nullable = true) + | | |-- content: string (nullable = true) + | | |-- metadata: map (nullable = true) + | | | |-- key: string + | | | |-- value: string (valueContainsNull = true) """ if not isinstance(htmlPath, (str, list)) or (isinstance(htmlPath, list) and not all(isinstance(item, str) for item in htmlPath)): raise TypeError("htmlPath must be a string or a list of strings") jdf = self._java_obj.html(htmlPath) - return self.getDataFrame(self.spark, jdf) + dataframe = self.getDataFrame(self.spark, jdf) + return dataframe def email(self, filePath): """Reads email files and returns a Spark DataFrame. @@ -83,31 +96,79 @@ def email(self, filePath): >>> from sparknlp.reader import SparkNLPReader >>> email_df = SparkNLPReader(spark).email("home/user/emails-directory") - Using SparkNLP: + You can also use SparkNLP to simplify the process: >>> import sparknlp >>> email_df = sparknlp.read().email("home/user/emails-directory") >>> email_df.show(truncate=False) + + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |email | + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |[{Title, Email Text Attachments, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano }}, {NarrativeText, Email test with two text attachments\r\n\r\nCheers,\r\n\r\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}, {NarrativeText, \r\n\r\n\r\n\r\n\r\n\r\nEmail  test with two text attachments\r\n
    \r\n
    \r\n
    \r\n
    \r\nCheers,
    \r\n
    \r\n
    \r\n
    \r\n\r\n\r\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/html}}, {Attachment, filename.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name="filename.txt"}}, {NarrativeText, This is the content of the file.\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}, {Attachment, filename2.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name="filename2.txt"}}, {NarrativeText, This is an additional content file.\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}]| + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + >>> email_df.printSchema() + root + |-- path: string (nullable = true) + |-- content: array (nullable = true) + |-- email: array (nullable = true) + | |-- element: struct (containsNull = true) + | | |-- elementType: string (nullable = true) + | | |-- content: string (nullable = true) + | | |-- metadata: map (nullable = true) + | | | |-- key: string + | | | |-- value: string (valueContainsNull = true) + """ if not isinstance(filePath, str): raise TypeError("filePath must be a string") jdf = self._java_obj.email(filePath) - return self.getDataFrame(self.spark, jdf) + dataframe = self.getDataFrame(self.spark, jdf) + return dataframe def doc(self, docPath): - """Reads document files and returns a Spark DataFrame. + """Reads word document files and returns a Spark DataFrame. Parameters ---------- docPath : str - Path to a document file. + Path to a word document file. Returns ------- pyspark.sql.DataFrame A DataFrame containing parsed document content. + + Examples + -------- + >>> from sparknlp.reader import SparkNLPReader + >>> doc_df = SparkNLPReader().doc(spark, "home/user/word-directory") + + You can use SparkNLP for one line of code + >>> import sparknlp + >>> doc_df = sparknlp.read().doc("home/user/word-directory") + >>> doc_df.show(truncate=False) + + +----------------------------------------------------------------------------------------------------------------------------------------------------+ + |doc | | + +----------------------------------------------------------------------------------------------------------------------------------------------------+ + |[{Table, Header Col 1, {}}, {Table, Header Col 2, {}}, {Table, Lorem ipsum, {}}, {Table, A Link example, {}}, {NarrativeText, Dolor sit amet, {}}] | + +----------------------------------------------------------------------------------------------------------------------------------------------------+ + >>> docsDf.printSchema() + root + |-- path: string (nullable = true) + |-- content: array (nullable = true) + |-- doc: array (nullable = true) + | |-- element: struct (containsNull = true) + | | |-- elementType: string (nullable = true) + | | |-- content: string (nullable = true) + | | |-- metadata: map (nullable = true) + | | | |-- key: string + | | | |-- value: string (valueContainsNull = true) + """ if not isinstance(docPath, str): raise TypeError("docPath must be a string") jdf = self._java_obj.doc(docPath) - return self.getDataFrame(self.spark, jdf) \ No newline at end of file + dataframe = self.getDataFrame(self.spark, jdf) + return dataframe \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 89474c5ed9f7b1..de6afcb4983bed 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -106,7 +106,7 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM /** Instantiates class to read email files. * * emailPath: this is a path to a directory of HTML files or a path to an HTML file E.g. - * "path/html/emails" + * "path/email/files" * * ==Example== * {{{ @@ -161,6 +161,49 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM addAttachmentContent } + /** Instantiates class to read Word files. + * + * docPath: this is a path to a directory of Word files or a path to an HTML file E.g. + * "path/word/files" + * + * ==Example== + * {{{ + * val docsPath = "home/user/word-directory" + * val sparkNLPReader = new SparkNLPReader() + * val docsDf = sparkNLPReader.email(docsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val docsDf = SparkNLP.read.doc(docsPath) + * }}} + * + * {{{ + * docsDf.select("doc").show(false) + * +----------------------------------------------------------------------------------------------------------------------------------------------------+ + * |doc | | + * +----------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[{Table, Header Col 1, {}}, {Table, Header Col 2, {}}, {Table, Lorem ipsum, {}}, {Table, A Link example, {}}, {NarrativeText, Dolor sit amet, {}}] | + * +----------------------------------------------------------------------------------------------------------------------------------------------------+ + * + * docsDf.printSchema() + * root + * |-- path: string (nullable = true) + * |-- content: binary (nullable = true) + * |-- doc: array (nullable = true) + * | |-- element: struct (containsNull = true) + * | | |-- elementType: string (nullable = true) + * | | |-- content: string (nullable = true) + * | | |-- metadata: map (nullable = true) + * | | | |-- key: string + * | | | |-- value: string (valueContainsNull = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ + def doc(docPath: String): DataFrame = { val wordReader = new WordReader(getStoreContent) wordReader.doc(docPath) From 649c862897b83fc4c2ee189a01101d7023e5c155 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Tue, 17 Dec 2024 18:58:18 -0500 Subject: [PATCH 076/108] [SPARKNLP-1102] Adding support to read Excel files --- python/sparknlp/reader/sparknlp_reader.py | 7 ++ python/test/sparknlp_test.py | 15 ++- .../com/johnsnowlabs/reader/ExcelReader.scala | 90 ++++++++++++++++++ .../johnsnowlabs/reader/SparkNLPReader.scala | 52 ++++++++++ .../johnsnowlabs/reader/util/XlsxParser.scala | 49 ++++++++++ .../2023-half-year-analyses-by-segment.xlsx | Bin 0 -> 38442 bytes src/test/resources/reader/xls/vodafone.xlsx | Bin 0 -> 12541 bytes .../johnsnowlabs/reader/ExcelReaderTest.scala | 35 +++++++ .../johnsnowlabs/reader/WordReaderTest.scala | 2 +- 9 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala create mode 100755 src/test/resources/reader/xls/2023-half-year-analyses-by-segment.xlsx create mode 100755 src/test/resources/reader/xls/vodafone.xlsx create mode 100644 src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index da9baa8ddaed70..5483481b1a5681 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -171,4 +171,11 @@ def doc(self, docPath): raise TypeError("docPath must be a string") jdf = self._java_obj.doc(docPath) dataframe = self.getDataFrame(self.spark, jdf) + return dataframe + + def xls(self, docPath): + if not isinstance(docPath, str): + raise TypeError("docPath must be a string") + jdf = self._java_obj.xls(docPath) + dataframe = self.getDataFrame(self.spark, jdf) return dataframe \ No newline at end of file diff --git a/python/test/sparknlp_test.py b/python/test/sparknlp_test.py index 3b2ee58e22bfce..74c2f625572edb 100644 --- a/python/test/sparknlp_test.py +++ b/python/test/sparknlp_test.py @@ -86,4 +86,17 @@ def runTest(self): word_df = sparknlp.read().doc(self.word_file) word_df.show() - self.assertTrue(word_df.select("doc").count() > 0) \ No newline at end of file + self.assertTrue(word_df.select("doc").count() > 0) + +@pytest.mark.fast +class SparkNLPTestExcelFilesSpec(unittest.TestCase): + + def setUp(self): + self.data = SparkContextForTest.data + self.excel_file = f"file:///{os.getcwd()}/../src/test/resources/reader/xls/vodafone.xlsx" + + def runTest(self): + excel_df = sparknlp.read().xls(self.excel_file) + excel_df.show() + + self.assertTrue(excel_df.select("xls").count() > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala new file mode 100644 index 00000000000000..5082e913488a8e --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala @@ -0,0 +1,90 @@ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.reader.util.XlsxParser.{RichCell, RichRow} +import org.apache.poi.hssf.usermodel.HSSFWorkbook +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.{col, udf} + +import java.io.ByteArrayInputStream +import scala.collection.JavaConverters._ +import scala.collection.mutable + +class ExcelReader(titleFontSize: Int = 9, cellSeparator: String = "\t") extends Serializable { + + private val spark = ResourceHelper.spark + import spark.implicits._ + + def xls(filePath: String): DataFrame = { + if (ResourceHelper.validFile(filePath)) { + val binaryFilesRDD = spark.sparkContext.binaryFiles(filePath) + val byteArrayRDD = binaryFilesRDD.map { case (path, portableDataStream) => + val byteArray = portableDataStream.toArray() + (path, byteArray) + } + byteArrayRDD + .toDF("path", "content") + .withColumn("xls", parseExcelUDF(col("content"))) + } else throw new IllegalArgumentException(s"Invalid filePath: $filePath") + } + + private val parseExcelUDF = udf((data: Array[Byte]) => { + parseExcel(data) + }) + + // Constants for file type identification + private val ZipMagicNumberFirstByte: Byte = 0x50.toByte // First byte of ZIP files + private val ZipMagicNumberSecondByte: Byte = 0x4b.toByte // Second byte of ZIP files + private val OleMagicNumber: Array[Byte] = + Array(0xd0.toByte, 0xcf.toByte, 0x11.toByte, 0xe0.toByte) // OLE file header + + private def isXlsxFile(content: Array[Byte]): Boolean = { + content.length > 1 && + content(0) == ZipMagicNumberFirstByte && + content(1) == ZipMagicNumberSecondByte + } + + private def isXlsFile(content: Array[Byte]): Boolean = { + content.length >= 4 && content.slice(0, 4).sameElements(OleMagicNumber) + } + + private def parseExcel(content: Array[Byte]): Seq[HTMLElement] = { + val workbookInputStream = new ByteArrayInputStream(content) + val workbook: Workbook = + if (isXlsxFile(content)) new XSSFWorkbook(workbookInputStream) + else if (isXlsFile(content)) new HSSFWorkbook(workbookInputStream) + else throw new IllegalArgumentException("Unsupported file format: must be .xls or .xlsx") + + val elementsBuffer = mutable.ArrayBuffer[HTMLElement]() + + for (sheetIndex <- 0 until workbook.getNumberOfSheets) { + val sheet = workbook.getSheetAt(sheetIndex) + val sheetName = sheet.getSheetName + + val rowIterator = sheet.iterator() + while (rowIterator.hasNext) { + val row = rowIterator.next() + val elementType = + if (row.isTitle(titleFontSize)) ElementType.TITLE else ElementType.NARRATIVE_TEXT + + val cellValues = row.cellIterator().asScala.map(_.getCellValue).toSeq + val content = cellValues.mkString(cellSeparator).trim + + if (content.nonEmpty) { + val element = HTMLElement( + elementType = elementType, + content = content, + metadata = mutable.Map("SheetName" -> sheetName)) + elementsBuffer += element + } + } + } + + workbook.close() + + elementsBuffer + } + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index de6afcb4983bed..41a1c03d435d8e 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -209,4 +209,56 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM wordReader.doc(docPath) } + /** Instantiates class to read Excel files. + * + * docPath: this is a path to a directory of Excel files or a path to an HTML file E.g. + * "path/excel/files" + * + * ==Example== + * {{{ + * val docsPath = "home/user/excel-directory" + * val sparkNLPReader = new SparkNLPReader() + * val xlsDf = sparkNLPReader.xls(docsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val xlsDf = SparkNLP.read.xls(docsPath) + * }}} + * + * {{{ + * xlsDf.select("xls").show(false) + * +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |xls | + * +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[{Title, Financial performance, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Quarterly revenue\tNine quarters to 30 June 2023\t\t\t1.0, {SheetName -> Index}}, {NarrativeText, Group financial performance\tFY 22\tFY 23\t\t2.0, {SheetName -> Index}}, {NarrativeText, Segmental results\tFY 22\tFY 23\t\t3.0, {SheetName -> Index}}, {NarrativeText, Segmental analysis\tFY 22\tFY 23\t\t4.0, {SheetName -> Index}}, {NarrativeText, Cash flow\tFY 22\tFY 23\t\t5.0, {SheetName -> Index}}, {Title, Operational metrics, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Mobile customers\tNine quarters to 30 June 2023\t\t\t6.0, {SheetName -> Index}}, {NarrativeText, Fixed broadband customers\tNine quarters to 30 June 2023\t\t\t7.0, {SheetName -> Index}}, {NarrativeText, Marketable homes passed\tNine quarters to 30 June 2023\t\t\t8.0, {SheetName -> Index}}, {NarrativeText, TV customers\tNine quarters to 30 June 2023\t\t\t9.0, {SheetName -> Index}}, {NarrativeText, Converged customers\tNine quarters to 30 June 2023\t\t\t10.0, {SheetName -> Index}}, {NarrativeText, Mobile churn\tNine quarters to 30 June 2023\t\t\t11.0, {SheetName -> Index}}, {NarrativeText, Mobile data usage\tNine quarters to 30 June 2023\t\t\t12.0, {SheetName -> Index}}, {NarrativeText, Mobile ARPU\tNine quarters to 30 June 2023\t\t\t13.0, {SheetName -> Index}}, {Title, Other, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Average foreign exchange rates\tNine quarters to 30 June 2023\t\t\t14.0, {SheetName -> Index}}, {NarrativeText, Guidance rates\tFY 23/24\t\t\t14.0, {SheetName -> Index}}]| + * +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * + * xlsDf.printSchema() + * root + * |-- path: string (nullable = true) + * |-- content: binary (nullable = true) + * |-- xls: array (nullable = true) + * | |-- element: struct (containsNull = true) + * | | |-- elementType: string (nullable = true) + * | | |-- content: string (nullable = true) + * | | |-- metadata: map (nullable = true) + * | | | |-- key: string + * | | | |-- value: string (valueContainsNull = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ + + def xls(docPath: String): DataFrame = { + val excelReader = new ExcelReader(getTitleFontSize, getCellSeparator) + excelReader.xls(docPath) + } + + private def getCellSeparator: String = { + params.asScala.getOrElse("cellSeparator", "\t") + } + } diff --git a/src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala new file mode 100644 index 00000000000000..3aa4a507214aa3 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala @@ -0,0 +1,49 @@ +package com.johnsnowlabs.reader.util + +import org.apache.poi.ss.usermodel.{Cell, CellType, DateUtil, HorizontalAlignment, Row} + +import scala.collection.JavaConverters._ + +object XlsxParser { + + implicit class RichRow(row: Row) { + + def isTitle(titleFontSizeThreshold: Int): Boolean = { + row.cellIterator().asScala.exists { cell => + val cellStyle = cell.getCellStyle + val font = row.getSheet.getWorkbook.getFontAt(cellStyle.getFontIndexAsInt) + + val isBold = font.getBold + val isCentered = cellStyle.getAlignment == HorizontalAlignment.CENTER + + val text = cell.getCellValue.trim + val isUppercaseOrCapitalized = + text.nonEmpty && (text == text.toUpperCase || text.headOption.exists(_.isUpper)) + + val fontSize = font.getFontHeightInPoints + val isLargeFont = fontSize >= titleFontSizeThreshold + + (isBold && isCentered) || (isBold && isUppercaseOrCapitalized) || (isBold && isLargeFont) + } + } + } + + implicit class RichCell(cell: Cell) { + + def getCellValue: String = { + cell.getCellType match { + case CellType.STRING => cell.getStringCellValue + case CellType.NUMERIC => + if (DateUtil.isCellDateFormatted(cell)) + cell.getDateCellValue.toString + else + cell.getNumericCellValue.toString + case CellType.BOOLEAN => cell.getBooleanCellValue.toString + case CellType.FORMULA => cell.getCellFormula + case _ => "" + } + } + + } + +} diff --git a/src/test/resources/reader/xls/2023-half-year-analyses-by-segment.xlsx b/src/test/resources/reader/xls/2023-half-year-analyses-by-segment.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..d0ce5e673c7c26f1239eafe4af50aca0a4af9b96 GIT binary patch literal 38442 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/reader/xls/vodafone.xlsx b/src/test/resources/reader/xls/vodafone.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..4467a9301d55f503ffb6a5f61a86a89158198a43 GIT binary patch literal 12541 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 literal 0 HcmV?d00001 diff --git a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala new file mode 100644 index 00000000000000..adc15b9732543d --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala @@ -0,0 +1,35 @@ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.sql.functions.col +import org.scalatest.flatspec.AnyFlatSpec + +class ExcelReaderTest extends AnyFlatSpec { + + val docDirectory = "src/test/resources/reader/xls" + + "ExcelReader" should "read an excel file" taggedAs FastTest in { + val excelReader = new ExcelReader() + val excelDf = excelReader.xls(s"$docDirectory/2023-half-year-analyses-by-segment.xlsx") + excelDf.select("xls").show(false) + + assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + } + + "ExcelReader" should "read a directory of excel files" taggedAs FastTest in { + val excelReader = new ExcelReader() + val excelDf = excelReader.xls(docDirectory) + excelDf.select("xls") show (false) + + assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + } + + "ExcelReader" should "read a directory of excel files with custom cell separator" taggedAs FastTest in { + val excelReader = new ExcelReader(cellSeparator = "\t") + val excelDf = excelReader.xls(s"$docDirectory/vodafone.xlsx") + excelDf.select("xls").show(false) + + assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + } + +} diff --git a/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala index 4b56c0cbd2ba7b..eac70a695b07ef 100644 --- a/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala @@ -31,7 +31,7 @@ class WordReaderTest extends AnyFlatSpec { val wordReader = new WordReader() val wordDf = wordReader.doc(docDirectory) wordDf.select("doc").show(false) - + wordDf.printSchema() assert(!wordDf.select(col("doc").getItem(0)).isEmpty) assert(!wordDf.columns.contains("content")) } From 60a8521bb5b705109404ec61b34fcf76f570ce84 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Thu, 19 Dec 2024 14:18:13 -0500 Subject: [PATCH 077/108] [SPARKNLP-1102] Adding notebook example to read Excel files --- .../reader/SparkNLP_Excel_Reader_Demo.ipynb | 408 ++++++++++++++++++ .../johnsnowlabs/reader/ExcelReaderTest.scala | 2 +- 2 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb diff --git a/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb new file mode 100644 index 00000000000000..6bd5c714dbc971 --- /dev/null +++ b/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb @@ -0,0 +1,408 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Introducing Excel reader in SparkNLP\n", + "This notebook showcases the newly added `sparknlp.read().xls()` method in Spark NLP that parses Excel content from both local files and both local and distributed file systems into a Spark DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "xrvHhiTAdfGd", + "outputId": "77803c7f-1033-4f0c-dda4-a818986367e5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mounted at /content/drive\n" + ] + } + ], + "source": [ + "from google.colab import drive\n", + "drive.mount('/content/drive')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "mjV3NcQ8eA52" + }, + "outputs": [], + "source": [ + "!cp drive/MyDrive/JSL/sparknlp/sparknlp.jar .\n", + "!cp drive/MyDrive/JSL/sparknlp/spark_nlp-5.5.1-py2.py3-none-any.whl ." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "FuWVW6HPXRQw", + "outputId": "fd3b80c5-4bf9-4d74-ac2e-8100937b71e9" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "env: PYSPARK=3.4.0\n" + ] + } + ], + "source": [ + "%env PYSPARK=3.4.0" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "pEmutNjReCgc", + "outputId": "7cb8d345-719d-4a71-d91d-57f9eb1b2b85" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: PYSPARK in /usr/local/lib/python3.10/dist-packages (3.5.3)\n", + "Requirement already satisfied: py4j==0.10.9.7 in /usr/local/lib/python3.10/dist-packages (from PYSPARK) (0.10.9.7)\n" + ] + } + ], + "source": [ + "!pip install PYSPARK" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3qjPeDjvfCpA", + "outputId": "b7cb29be-3052-4be8-a94d-ad3b9777e926" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing ./spark_nlp-5.5.1-py2.py3-none-any.whl\n", + "Installing collected packages: spark-nlp\n", + "Successfully installed spark-nlp-5.5.1\n" + ] + } + ], + "source": [ + "!pip install spark_nlp-5.5.1-py2.py3-none-any.whl" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "DczWop6QeE8F", + "outputId": "610a531b-ad06-48c5-e868-7906ee6bef1d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Apache Spark version: 3.5.3\n" + ] + } + ], + "source": [ + "# import sparknlp\n", + "# # let's start Spark with Spark NLP\n", + "# spark = sparknlp.start()\n", + "\n", + "from pyspark.sql import SparkSession\n", + "\n", + "spark = SparkSession.builder \\\n", + " .appName(\"SparkNLP\") \\\n", + " .master(\"local[*]\") \\\n", + " .config(\"spark.driver.memory\", \"12G\") \\\n", + " .config(\"spark.serializer\", \"org.apache.spark.serializer.KryoSerializer\") \\\n", + " .config(\"spark.kryoserializer.buffer.max\", \"2000M\") \\\n", + " .config(\"spark.driver.maxResultSize\", \"0\") \\\n", + " .config(\"spark.jars\", \"./sparknlp.jar\") \\\n", + " .getOrCreate()\n", + "\n", + "\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading html files was introduced in Spark NLP 5.5.2. Please make sure you have upgraded to the latest Spark NLP release.\n", + "\n", + "For local files example we will download an Excel file from Spark NLP Github repo:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ya8qZe00dalC", + "outputId": "f74142d4-2686-44b3-9428-7aafb2daf2e5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mkdir: cannot create directory โ€˜excel-filesโ€™: File exists\n", + "--2024-12-19 18:05:41-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1102-Adding-support-to-read-Excel-files/src/test/resources/reader/xls/vodafone.xlsx\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 12541 (12K) [application/octet-stream]\n", + "Saving to: โ€˜excel-files/vodafone.xlsxโ€™\n", + "\n", + "vodafone.xlsx 100%[===================>] 12.25K --.-KB/s in 0s \n", + "\n", + "2024-12-19 18:05:41 (70.5 MB/s) - โ€˜excel-files/vodafone.xlsxโ€™ saved [12541/12541]\n", + "\n" + ] + } + ], + "source": [ + "!mkdir excel-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1102-Adding-support-to-read-Excel-files/src/test/resources/reader/xls/vodafone.xlsx -P excel-files" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EoFI66NAdalE" + }, + "source": [ + "## Parsing Excel sheets from Local Files\n", + "Use the `xls()` method to parse Excel content from local directories." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bAkMjJ1vdalE", + "outputId": "30b31d1d-9d53-4298-abe8-3d87dac4b569" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| content| xls|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/exc...|[50 4B 03 04 14 0...|[{Title, Financia...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "xls_df = sparknlp.read().xls(\"./excel-files\")\n", + "\n", + "xls_df.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "VWbUgoVQrO8m", + "outputId": "a48ba911-0058-495d-8c1e-758b9a116d4f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- path: string (nullable = true)\n", + " |-- content: binary (nullable = true)\n", + " |-- xls: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- elementType: string (nullable = true)\n", + " | | |-- content: string (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + "\n" + ] + } + ], + "source": [ + "xls_df.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VQD2k4E5dalF" + }, + "source": [ + "## Configuration Parameters\n", + "- Font Size: You can customize the font size used to identify paragraphs that should be treated as titles. By default, the font size is set to 9.\n", + "- Cell Separator: You can also customize the separator for each cell in the sheet. By defult, the separator is tab `\"\\t\"`\n", + "\n", + "However, if your Excel files require a different configuration, you can adjust this parameter accordingly. The example below demonstrates how to modify and work with this setting:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "MMTGmxLQdalG", + "outputId": "21ebe4e8-ac54-4bfc-8489-34ba447f39d6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|xls |\n", + "+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {Title, ;Financial performance;;;;;;;;;, {SheetName -> Index}}, {Title, ;Topic;Period;;;Page;;;;;, {SheetName -> Index}}, {NarrativeText, ;Quarterly revenue;Nine quarters to 30 June 2023;;;1.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Group financial performance;FY 22;FY 23;;2.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Segmental results;FY 22;FY 23;;3.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Segmental analysis;FY 22;FY 23;;4.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Cash flow;FY 22;FY 23;;5.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {Title, ;Operational metrics;;;;;;;;;, {SheetName -> Index}}, {Title, ;Topic;Period;;;Page;;;;;, {SheetName -> Index}}, {NarrativeText, ;Mobile customers;Nine quarters to 30 June 2023;;;6.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Fixed broadband customers;Nine quarters to 30 June 2023;;;7.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Marketable homes passed;Nine quarters to 30 June 2023;;;8.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;TV customers;Nine quarters to 30 June 2023;;;9.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Converged customers;Nine quarters to 30 June 2023;;;10.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Mobile churn;Nine quarters to 30 June 2023;;;11.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Mobile data usage;Nine quarters to 30 June 2023;;;12.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Mobile ARPU;Nine quarters to 30 June 2023;;;13.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {Title, ;Other;;;;;;;;;, {SheetName -> Index}}, {Title, ;Topic;Period;;;Page;;;;;, {SheetName -> Index}}, {NarrativeText, ;Average foreign exchange rates;Nine quarters to 30 June 2023;;;14.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;Guidance rates;FY 23/24;;;14.0;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}, {NarrativeText, ;;;;;;;;;;, {SheetName -> Index}}]|\n", + "+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"titleFontSize\": \"9\", \"cellSeparator\": \";\"}\n", + "xls_df = sparknlp.read(params).xls(\"./excel-files\")\n", + "xls_df.select(\"xls\").show(truncate=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "oBj0cHPXSD1m", + "outputId": "1b4543a6-e2ed-4e24-8e4e-56401da8e4df" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- path: string (nullable = true)\n", + " |-- content: binary (nullable = true)\n", + " |-- xls: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- elementType: string (nullable = true)\n", + " | | |-- content: string (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + "\n" + ] + } + ], + "source": [ + "xls_df.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BB2FEfegGuxl" + }, + "source": [ + "You can also use DFS file systems like:\n", + "- Databricks: `dbfs://`\n", + "- HDFS: `hdfs://`\n", + "- Microsoft Fabric OneLake: `abfss://`" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala index adc15b9732543d..a9e7498ee6a018 100644 --- a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala @@ -25,7 +25,7 @@ class ExcelReaderTest extends AnyFlatSpec { } "ExcelReader" should "read a directory of excel files with custom cell separator" taggedAs FastTest in { - val excelReader = new ExcelReader(cellSeparator = "\t") + val excelReader = new ExcelReader(cellSeparator = ";") val excelDf = excelReader.xls(s"$docDirectory/vodafone.xlsx") excelDf.select("xls").show(false) From 4bb3ad53197382d369e01179b2af8db57ea23b20 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Thu, 6 Mar 2025 09:46:13 -0500 Subject: [PATCH 078/108] [SPARKNLP-1102] Refactoring documentation for excel reader --- python/sparknlp/reader/sparknlp_reader.py | 42 ++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 5483481b1a5681..e77d4532009f2d 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -15,7 +15,7 @@ class SparkNLPReader(ExtendedJavaWrapper): - """Instantiates class to read HTML, email, and document files. + """Instantiates class to read HTML, email, MS Word and Excel files. Parameters ---------- @@ -174,6 +174,46 @@ def doc(self, docPath): return dataframe def xls(self, docPath): + """Reads excel document files and returns a Spark DataFrame. + + Parameters + ---------- + docPath : str + Path to an excel document file. + + Returns + ------- + pyspark.sql.DataFrame + A DataFrame containing parsed document content. + + Examples + -------- + >>> from sparknlp.reader import SparkNLPReader + >>> xlsDf = SparkNLPReader().xls(spark, "home/user/excel-directory") + + You can use SparkNLP for one line of code + >>> import sparknlp + >>> xlsDf = sparknlp.read().xls("home/user/excel-directory") + >>> xlsDf.show(truncate=False) + + +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |xls | + +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |[{Title, Financial performance, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Quarterly revenue\tNine quarters to 30 June 2023\t\t\t1.0, {SheetName -> Index}}, {NarrativeText, Group financial performance\tFY 22\tFY 23\t\t2.0, {SheetName -> Index}}, {NarrativeText, Segmental results\tFY 22\tFY 23\t\t3.0, {SheetName -> Index}}, {NarrativeText, Segmental analysis\tFY 22\tFY 23\t\t4.0, {SheetName -> Index}}, {NarrativeText, Cash flow\tFY 22\tFY 23\t\t5.0, {SheetName -> Index}}, {Title, Operational metrics, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Mobile customers\tNine quarters to 30 June 2023\t\t\t6.0, {SheetName -> Index}}, {NarrativeText, Fixed broadband customers\tNine quarters to 30 June 2023\t\t\t7.0, {SheetName -> Index}}, {NarrativeText, Marketable homes passed\tNine quarters to 30 June 2023\t\t\t8.0, {SheetName -> Index}}, {NarrativeText, TV customers\tNine quarters to 30 June 2023\t\t\t9.0, {SheetName -> Index}}, {NarrativeText, Converged customers\tNine quarters to 30 June 2023\t\t\t10.0, {SheetName -> Index}}, {NarrativeText, Mobile churn\tNine quarters to 30 June 2023\t\t\t11.0, {SheetName -> Index}}, {NarrativeText, Mobile data usage\tNine quarters to 30 June 2023\t\t\t12.0, {SheetName -> Index}}, {NarrativeText, Mobile ARPU\tNine quarters to 30 June 2023\t\t\t13.0, {SheetName -> Index}}, {Title, Other, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Average foreign exchange rates\tNine quarters to 30 June 2023\t\t\t14.0, {SheetName -> Index}}, {NarrativeText, Guidance rates\tFY 23/24\t\t\t14.0, {SheetName -> Index}}]| + +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + + xlsDf.printSchema() + root + |-- path: string (nullable = true) + |-- content: binary (nullable = true) + |-- xls: array (nullable = true) + | |-- element: struct (containsNull = true) + | | |-- elementType: string (nullable = true) + | | |-- content: string (nullable = true) + | | |-- metadata: map (nullable = true) + | | | |-- key: string + | | | |-- value: string (valueContainsNull = true) + """ if not isinstance(docPath, str): raise TypeError("docPath must be a string") jdf = self._java_obj.xls(docPath) From af7b13eea356fb486a7787ef401869f5f444f5ea Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Thu, 6 Mar 2025 10:51:46 -0500 Subject: [PATCH 079/108] [SPARKNLP-1117] Adding storeContent param --- .../reader/SparkNLP_Excel_Reader_Demo.ipynb | 239 ++++++------------ .../com/johnsnowlabs/reader/ExcelReader.scala | 10 +- .../johnsnowlabs/reader/SparkNLPReader.scala | 84 +++--- .../johnsnowlabs/reader/ExcelReaderTest.scala | 12 + 4 files changed, 142 insertions(+), 203 deletions(-) diff --git a/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb index 6bd5c714dbc971..11152adfb89909 100644 --- a/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb @@ -2,7 +2,9 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "9ehZGOlcBf98" + }, "source": [ "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", "\n", @@ -20,200 +22,81 @@ ] }, { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "xrvHhiTAdfGd", - "outputId": "77803c7f-1033-4f0c-dda4-a818986367e5" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mounted at /content/drive\n" - ] - } - ], - "source": [ - "from google.colab import drive\n", - "drive.mount('/content/drive')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "mjV3NcQ8eA52" - }, - "outputs": [], - "source": [ - "!cp drive/MyDrive/JSL/sparknlp/sparknlp.jar .\n", - "!cp drive/MyDrive/JSL/sparknlp/spark_nlp-5.5.1-py2.py3-none-any.whl ." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "FuWVW6HPXRQw", - "outputId": "fd3b80c5-4bf9-4d74-ac2e-8100937b71e9" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "env: PYSPARK=3.4.0\n" - ] - } - ], - "source": [ - "%env PYSPARK=3.4.0" - ] - }, - { - "cell_type": "code", - "execution_count": 4, + "cell_type": "markdown", "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "pEmutNjReCgc", - "outputId": "7cb8d345-719d-4a71-d91d-57f9eb1b2b85" + "id": "RFOFhaEedalB" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: PYSPARK in /usr/local/lib/python3.10/dist-packages (3.5.3)\n", - "Requirement already satisfied: py4j==0.10.9.7 in /usr/local/lib/python3.10/dist-packages (from PYSPARK) (0.10.9.7)\n" - ] - } - ], "source": [ - "!pip install PYSPARK" + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading html files was introduced in Spark NLP 5.5.2. Please make sure you have upgraded to the latest Spark NLP release." ] }, { - "cell_type": "code", - "execution_count": 5, + "cell_type": "markdown", "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "3qjPeDjvfCpA", - "outputId": "b7cb29be-3052-4be8-a94d-ad3b9777e926" + "id": "xFY30Xy8Brav" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processing ./spark_nlp-5.5.1-py2.py3-none-any.whl\n", - "Installing collected packages: spark-nlp\n", - "Successfully installed spark-nlp-5.5.1\n" - ] - } - ], "source": [ - "!pip install spark_nlp-5.5.1-py2.py3-none-any.whl" + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "DczWop6QeE8F", - "outputId": "610a531b-ad06-48c5-e868-7906ee6bef1d" + "id": "qEllqTAQBs61" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Apache Spark version: 3.5.3\n" - ] - } - ], + "outputs": [], "source": [ - "# import sparknlp\n", - "# # let's start Spark with Spark NLP\n", - "# spark = sparknlp.start()\n", - "\n", - "from pyspark.sql import SparkSession\n", - "\n", - "spark = SparkSession.builder \\\n", - " .appName(\"SparkNLP\") \\\n", - " .master(\"local[*]\") \\\n", - " .config(\"spark.driver.memory\", \"12G\") \\\n", - " .config(\"spark.serializer\", \"org.apache.spark.serializer.KryoSerializer\") \\\n", - " .config(\"spark.kryoserializer.buffer.max\", \"2000M\") \\\n", - " .config(\"spark.driver.maxResultSize\", \"0\") \\\n", - " .config(\"spark.jars\", \"./sparknlp.jar\") \\\n", - " .getOrCreate()\n", - "\n", - "\n", - "print(\"Apache Spark version: {}\".format(spark.version))" + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" ] }, { "cell_type": "markdown", "metadata": { - "id": "RFOFhaEedalB" + "id": "D02R4ZahBunE" }, "source": [ - "## Setup and Initialization\n", - "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", - "\n", - "Support for reading html files was introduced in Spark NLP 5.5.2. Please make sure you have upgraded to the latest Spark NLP release.\n", - "\n", "For local files example we will download an Excel file from Spark NLP Github repo:" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ya8qZe00dalC", - "outputId": "f74142d4-2686-44b3-9428-7aafb2daf2e5" + "outputId": "32108d19-0a00-4e59-c056-1839111aa56d" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "mkdir: cannot create directory โ€˜excel-filesโ€™: File exists\n", - "--2024-12-19 18:05:41-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1102-Adding-support-to-read-Excel-files/src/test/resources/reader/xls/vodafone.xlsx\n", + "--2025-03-06 15:41:14-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1102-Adding-support-to-read-Excel-files/src/test/resources/reader/xls/vodafone.xlsx\n", "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 12541 (12K) [application/octet-stream]\n", "Saving to: โ€˜excel-files/vodafone.xlsxโ€™\n", "\n", + "\r", + "vodafone.xlsx 0%[ ] 0 --.-KB/s \r", "vodafone.xlsx 100%[===================>] 12.25K --.-KB/s in 0s \n", "\n", - "2024-12-19 18:05:41 (70.5 MB/s) - โ€˜excel-files/vodafone.xlsxโ€™ saved [12541/12541]\n", + "2025-03-06 15:41:14 (61.1 MB/s) - โ€˜excel-files/vodafone.xlsxโ€™ saved [12541/12541]\n", "\n" ] } ], "source": [ "!mkdir excel-files\n", - "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1102-Adding-support-to-read-Excel-files/src/test/resources/reader/xls/vodafone.xlsx -P excel-files" + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/xls/vodafone.xlsx -P excel-files" ] }, { @@ -228,13 +111,13 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "bAkMjJ1vdalE", - "outputId": "30b31d1d-9d53-4298-abe8-3d87dac4b569" + "outputId": "24edd331-b503-4c1b-d174-60c5bd128b4a" }, "outputs": [ { @@ -242,11 +125,11 @@ "output_type": "stream", "text": [ "Warning::Spark Session already created, some configs may not take.\n", - "+--------------------+--------------------+--------------------+\n", - "| path| content| xls|\n", - "+--------------------+--------------------+--------------------+\n", - "|file:/content/exc...|[50 4B 03 04 14 0...|[{Title, Financia...|\n", - "+--------------------+--------------------+--------------------+\n", + "+--------------------+--------------------+\n", + "| path| xls|\n", + "+--------------------+--------------------+\n", + "|file:/content/exc...|[{Title, Financia...|\n", + "+--------------------+--------------------+\n", "\n" ] } @@ -260,13 +143,13 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 10, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VWbUgoVQrO8m", - "outputId": "a48ba911-0058-495d-8c1e-758b9a116d4f" + "outputId": "bd72bc6e-1e38-4b94-e063-49f827896fda" }, "outputs": [ { @@ -275,7 +158,6 @@ "text": [ "root\n", " |-- path: string (nullable = true)\n", - " |-- content: binary (nullable = true)\n", " |-- xls: array (nullable = true)\n", " | |-- element: struct (containsNull = true)\n", " | | |-- elementType: string (nullable = true)\n", @@ -298,21 +180,21 @@ }, "source": [ "## Configuration Parameters\n", - "- Font Size: You can customize the font size used to identify paragraphs that should be treated as titles. By default, the font size is set to 9.\n", - "- Cell Separator: You can also customize the separator for each cell in the sheet. By defult, the separator is tab `\"\\t\"`\n", + "- `titleFontSize`: You can customize the font size used to identify paragraphs that should be treated as titles. By default, the font size is set to 9.\n", + "- `cellSeparator`: You can also customize the separator for each cell in the sheet. By defult, the separator is tab `\"\\t\"`\n", "\n", "However, if your Excel files require a different configuration, you can adjust this parameter accordingly. The example below demonstrates how to modify and work with this setting:" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "MMTGmxLQdalG", - "outputId": "21ebe4e8-ac54-4bfc-8489-34ba447f39d6" + "outputId": "a436f4a9-68c3-473c-d04c-e46ed45198ab" }, "outputs": [ { @@ -337,13 +219,13 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 12, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "oBj0cHPXSD1m", - "outputId": "1b4543a6-e2ed-4e24-8e4e-56401da8e4df" + "outputId": "51d52150-8764-4b0b-8728-42cfaaa55800" }, "outputs": [ { @@ -352,7 +234,6 @@ "text": [ "root\n", " |-- path: string (nullable = true)\n", - " |-- content: binary (nullable = true)\n", " |-- xls: array (nullable = true)\n", " | |-- element: struct (containsNull = true)\n", " | | |-- elementType: string (nullable = true)\n", @@ -379,6 +260,46 @@ "- HDFS: `hdfs://`\n", "- Microsoft Fabric OneLake: `abfss://`" ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1oihvmD4B3v8" + }, + "source": [ + "- `storeContent`: By default, this is set to `false`. When enabled, the output will include the byte content of the file." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "uYGF7rQDB5Mc", + "outputId": "7e540660-5ab0-4e9a-a86e-8fee2158cee0" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| xls| content|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/exc...|[{Title, Financia...|[50 4B 03 04 14 0...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"storeContent\": \"true\"}\n", + "xls_df = sparknlp.read(params).xls(\"./excel-files\")\n", + "xls_df.show()" + ] } ], "metadata": { diff --git a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala index 5082e913488a8e..997378f1d6334b 100644 --- a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala @@ -12,7 +12,11 @@ import java.io.ByteArrayInputStream import scala.collection.JavaConverters._ import scala.collection.mutable -class ExcelReader(titleFontSize: Int = 9, cellSeparator: String = "\t") extends Serializable { +class ExcelReader( + titleFontSize: Int = 9, + cellSeparator: String = "\t", + storeContent: Boolean = false) + extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -24,9 +28,11 @@ class ExcelReader(titleFontSize: Int = 9, cellSeparator: String = "\t") extends val byteArray = portableDataStream.toArray() (path, byteArray) } - byteArrayRDD + val excelDf = byteArrayRDD .toDF("path", "content") .withColumn("xls", parseExcelUDF(col("content"))) + if (storeContent) excelDf.select("path", "xls", "content") + else excelDf.select("path", "xls") } else throw new IllegalArgumentException(s"Invalid filePath: $filePath") } diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 41a1c03d435d8e..46cb5b92832ab1 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -162,47 +162,47 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM } /** Instantiates class to read Word files. - * - * docPath: this is a path to a directory of Word files or a path to an HTML file E.g. - * "path/word/files" - * - * ==Example== - * {{{ - * val docsPath = "home/user/word-directory" - * val sparkNLPReader = new SparkNLPReader() - * val docsDf = sparkNLPReader.email(docsPath) - * }}} - * - * ==Example 2== - * You can use SparkNLP for one line of code - * {{{ - * val docsDf = SparkNLP.read.doc(docsPath) - * }}} - * - * {{{ - * docsDf.select("doc").show(false) - * +----------------------------------------------------------------------------------------------------------------------------------------------------+ - * |doc | | - * +----------------------------------------------------------------------------------------------------------------------------------------------------+ - * |[{Table, Header Col 1, {}}, {Table, Header Col 2, {}}, {Table, Lorem ipsum, {}}, {Table, A Link example, {}}, {NarrativeText, Dolor sit amet, {}}] | - * +----------------------------------------------------------------------------------------------------------------------------------------------------+ - * - * docsDf.printSchema() - * root - * |-- path: string (nullable = true) - * |-- content: binary (nullable = true) - * |-- doc: array (nullable = true) - * | |-- element: struct (containsNull = true) - * | | |-- elementType: string (nullable = true) - * | | |-- content: string (nullable = true) - * | | |-- metadata: map (nullable = true) - * | | | |-- key: string - * | | | |-- value: string (valueContainsNull = true) - * }}} - * - * @param params - * Parameter with custom configuration - */ + * + * docPath: this is a path to a directory of Word files or a path to an HTML file E.g. + * "path/word/files" + * + * ==Example== + * {{{ + * val docsPath = "home/user/word-directory" + * val sparkNLPReader = new SparkNLPReader() + * val docsDf = sparkNLPReader.email(docsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val docsDf = SparkNLP.read.doc(docsPath) + * }}} + * + * {{{ + * docsDf.select("doc").show(false) + * +----------------------------------------------------------------------------------------------------------------------------------------------------+ + * |doc | | + * +----------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[{Table, Header Col 1, {}}, {Table, Header Col 2, {}}, {Table, Lorem ipsum, {}}, {Table, A Link example, {}}, {NarrativeText, Dolor sit amet, {}}] | + * +----------------------------------------------------------------------------------------------------------------------------------------------------+ + * + * docsDf.printSchema() + * root + * |-- path: string (nullable = true) + * |-- content: binary (nullable = true) + * |-- doc: array (nullable = true) + * | |-- element: struct (containsNull = true) + * | | |-- elementType: string (nullable = true) + * | | |-- content: string (nullable = true) + * | | |-- metadata: map (nullable = true) + * | | | |-- key: string + * | | | |-- value: string (valueContainsNull = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ def doc(docPath: String): DataFrame = { val wordReader = new WordReader(getStoreContent) @@ -253,7 +253,7 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM */ def xls(docPath: String): DataFrame = { - val excelReader = new ExcelReader(getTitleFontSize, getCellSeparator) + val excelReader = new ExcelReader(getTitleFontSize, getCellSeparator, getStoreContent) excelReader.xls(docPath) } diff --git a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala index a9e7498ee6a018..b1c72ce97de59d 100644 --- a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala @@ -14,6 +14,7 @@ class ExcelReaderTest extends AnyFlatSpec { excelDf.select("xls").show(false) assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + assert(!excelDf.columns.contains("content")) } "ExcelReader" should "read a directory of excel files" taggedAs FastTest in { @@ -22,6 +23,7 @@ class ExcelReaderTest extends AnyFlatSpec { excelDf.select("xls") show (false) assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + assert(!excelDf.columns.contains("content")) } "ExcelReader" should "read a directory of excel files with custom cell separator" taggedAs FastTest in { @@ -30,6 +32,16 @@ class ExcelReaderTest extends AnyFlatSpec { excelDf.select("xls").show(false) assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + assert(!excelDf.columns.contains("content")) + } + + "ExcelReader" should "store content" taggedAs FastTest in { + val excelReader = new ExcelReader(storeContent = true) + val excelDf = excelReader.xls(docDirectory) + excelDf.select("xls").show(false) + + assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + assert(excelDf.columns.contains("content")) } } From 1999ae5428843254f673872d6819d33cf30e789a Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Tue, 24 Dec 2024 11:18:02 -0500 Subject: [PATCH 080/108] [SPARKNLP-1103] Adding support to read PowerPoint files and adds location metadata for tables to all readers --- python/sparknlp/reader/sparknlp_reader.py | 7 + python/test/sparknlp_test.py | 15 ++- .../com/johnsnowlabs/reader/ExcelReader.scala | 38 +++++- .../reader/PowerPointReader.scala | 104 +++++++++++++++ .../johnsnowlabs/reader/SparkNLPReader.scala | 5 + .../com/johnsnowlabs/reader/WordReader.scala | 23 ++-- .../johnsnowlabs/reader/util/PptParser.scala | 126 ++++++++++++++++++ .../reader/ppt/fake-power-point-table.pptx | Bin 0 -> 39894 bytes .../reader/ppt/fake-power-point.pptx | Bin 0 -> 38412 bytes .../johnsnowlabs/reader/ExcelReaderTest.scala | 18 ++- .../johnsnowlabs/reader/PowerPointTest.scala | 51 +++++++ 11 files changed, 373 insertions(+), 14 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala create mode 100755 src/test/resources/reader/ppt/fake-power-point-table.pptx create mode 100755 src/test/resources/reader/ppt/fake-power-point.pptx create mode 100644 src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index e77d4532009f2d..18aa19e87dfb86 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -218,4 +218,11 @@ def xls(self, docPath): raise TypeError("docPath must be a string") jdf = self._java_obj.xls(docPath) dataframe = self.getDataFrame(self.spark, jdf) + return dataframe + + def ppt(self, docPath): + if not isinstance(docPath, str): + raise TypeError("docPath must be a string") + jdf = self._java_obj.ppt(docPath) + dataframe = self.getDataFrame(self.spark, jdf) return dataframe \ No newline at end of file diff --git a/python/test/sparknlp_test.py b/python/test/sparknlp_test.py index 74c2f625572edb..8db39446d6d98f 100644 --- a/python/test/sparknlp_test.py +++ b/python/test/sparknlp_test.py @@ -99,4 +99,17 @@ def runTest(self): excel_df = sparknlp.read().xls(self.excel_file) excel_df.show() - self.assertTrue(excel_df.select("xls").count() > 0) \ No newline at end of file + self.assertTrue(excel_df.select("xls").count() > 0) + +@pytest.mark.fast +class SparkNLPTestPowerPointFilesSpec(unittest.TestCase): + + def setUp(self): + self.data = SparkContextForTest.data + self.excel_file = f"file:///{os.getcwd()}/../src/test/resources/reader/ppt" + + def runTest(self): + excel_df = sparknlp.read().ppt(self.excel_file) + excel_df.show() + + self.assertTrue(excel_df.select("ppt").count() > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala index 997378f1d6334b..11cbdbc75597a5 100644 --- a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper @@ -72,17 +88,33 @@ class ExcelReader( val rowIterator = sheet.iterator() while (rowIterator.hasNext) { val row = rowIterator.next() + val rowIndex = row.getRowNum + val elementType = if (row.isTitle(titleFontSize)) ElementType.TITLE else ElementType.NARRATIVE_TEXT - val cellValues = row.cellIterator().asScala.map(_.getCellValue).toSeq - val content = cellValues.mkString(cellSeparator).trim + val cellValuesWithMetadata = row + .cellIterator() + .asScala + .map { cell => + val cellIndex = cell.getColumnIndex + val cellValue = cell.getCellValue.trim + + val cellMetadata = mutable.Map( + "location" -> s"(${rowIndex.toString}, ${cellIndex.toString})", + "SheetName" -> sheetName) + (cellValue, cellMetadata) + } + .toSeq + + val content = cellValuesWithMetadata.map(_._1).mkString(cellSeparator).trim + val rowMetadata = cellValuesWithMetadata.flatMap(_._2).toMap if (content.nonEmpty) { val element = HTMLElement( elementType = elementType, content = content, - metadata = mutable.Map("SheetName" -> sheetName)) + metadata = mutable.Map(rowMetadata.toSeq: _*)) elementsBuffer += element } } diff --git a/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala new file mode 100644 index 00000000000000..2e3818bd8fb144 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala @@ -0,0 +1,104 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.reader + +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.reader.util.PptParser +import org.apache.poi.hslf.usermodel.HSLFSlideShow +import org.apache.poi.xslf.usermodel.XMLSlideShow +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.{col, udf} + +import java.io.ByteArrayInputStream +import scala.collection.JavaConverters._ + +class PowerPointReader extends Serializable { + + private val spark = ResourceHelper.spark + import spark.implicits._ + + def ppt(filePath: String): DataFrame = { + if (ResourceHelper.validFile(filePath)) { + val binaryFilesRDD = spark.sparkContext.binaryFiles(filePath) + val byteArrayRDD = binaryFilesRDD.map { case (path, portableDataStream) => + val byteArray = portableDataStream.toArray() + (path, byteArray) + } + byteArrayRDD + .toDF("path", "content") + .withColumn("ppt", parsePowerPointUDF(col("content"))) + } else throw new IllegalArgumentException(s"Invalid filePath: $filePath") + } + + private val parsePowerPointUDF = udf((data: Array[Byte]) => { + parsePowerPoint(data) + }) + + // Constants for file type identification + private val ZipMagicNumberFirstByte: Byte = 0x50.toByte // First byte of ZIP files + private val ZipMagicNumberSecondByte: Byte = 0x4b.toByte // Second byte of ZIP files + private val OleMagicNumber: Array[Byte] = + Array(0xd0.toByte, 0xcf.toByte, 0x11.toByte, 0xe0.toByte) // OLE file header + + // Method to check if the file is a .pptx file (ZIP-based) + private def isPptxFile(content: Array[Byte]): Boolean = { + content.length > 1 && + content(0) == ZipMagicNumberFirstByte && + content(1) == ZipMagicNumberSecondByte + } + + // Method to check if the file is a .ppt file (OLE Compound Document) + private def isPptFile(content: Array[Byte]): Boolean = { + content.length >= 4 && content.slice(0, 4).sameElements(OleMagicNumber) + } + + val titleFontSizeThreshold = 9 + + private def parsePowerPoint(content: Array[Byte]): Seq[HTMLElement] = { + val slideInputStream = new ByteArrayInputStream(content) + if (isPptxFile(content)) { + parsePptx(slideInputStream) + } else if (isPptFile(content)) { + parsePpt(slideInputStream) + } else { + throw new IllegalArgumentException("Unsupported PowerPoint file format") + } + } + + private def parsePpt(slideInputStream: ByteArrayInputStream): Seq[HTMLElement] = { + val ppt = new HSLFSlideShow(slideInputStream) + val slides = ppt.getSlides + + val elements = slides.asScala.flatMap { slide => + PptParser.extractHSLFSlideContent(slide) + } + ppt.close() + elements + } + + private def parsePptx(slideInputStream: ByteArrayInputStream): Seq[HTMLElement] = { + val pptx = new XMLSlideShow(slideInputStream) + val slides = pptx.getSlides + + val elements = slides.asScala.flatMap { slide => + PptParser.extractXSLFSlideContent(slide) + } + pptx.close() + elements + } + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 46cb5b92832ab1..daa9ceaa8036f6 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -261,4 +261,9 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM params.asScala.getOrElse("cellSeparator", "\t") } + def ppt(docPath: String): DataFrame = { + val powerPointReader = new PowerPointReader() + powerPointReader.ppt(docPath) + } + } diff --git a/src/main/scala/com/johnsnowlabs/reader/WordReader.scala b/src/main/scala/com/johnsnowlabs/reader/WordReader.scala index 36c5e2a64fee91..e1f939fcf2c3d7 100644 --- a/src/main/scala/com/johnsnowlabs/reader/WordReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/WordReader.scala @@ -103,10 +103,10 @@ class WordReader(storeContent: Boolean = false) extends Serializable { val elements = document.getBodyElements.asScala.flatMap { case paragraph: XWPFParagraph => - processParagraph(paragraph, document, "paragraph") + processParagraph(paragraph, "paragraph") case table: XWPFTable => - processTable(table, document) + processTable(table) case _ => None } @@ -116,8 +116,8 @@ class WordReader(storeContent: Boolean = false) extends Serializable { private def processParagraph( paragraph: XWPFParagraph, - document: XWPFDocument, - source: String): Option[HTMLElement] = { + source: String, + tableLocation: mutable.Map[String, String] = mutable.Map()): Option[HTMLElement] = { val text = paragraph.getText.trim if (text.isEmpty) None else { @@ -130,7 +130,11 @@ class WordReader(storeContent: Boolean = false) extends Serializable { if (paragraph.isSectionBreak) { pageBreak += 1 - metadata += "pageBreak" -> pageBreak.toString + metadata += ("pageBreak" -> pageBreak.toString) + } + + if (tableLocation.nonEmpty) { + metadata ++= tableLocation } val elementType = paragraph match { @@ -142,11 +146,12 @@ class WordReader(storeContent: Boolean = false) extends Serializable { } } - private def processTable(table: XWPFTable, document: XWPFDocument): Seq[HTMLElement] = { - table.getRows.asScala.flatMap { row => - row.getTableCells.asScala.flatMap { cell => + private def processTable(table: XWPFTable): Seq[HTMLElement] = { + table.getRows.asScala.zipWithIndex.flatMap { case (row, rowIndex) => + row.getTableCells.asScala.zipWithIndex.flatMap { case (cell, cellIndex) => + val tableLocation = mutable.Map("tableLocation" -> s"($rowIndex, $cellIndex)") cell.getParagraphs.asScala.flatMap { paragraph => - processParagraph(paragraph, document, "table") + processParagraph(paragraph, "table", tableLocation) } } } diff --git a/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala new file mode 100644 index 00000000000000..92fbb9491a188f --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala @@ -0,0 +1,126 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.reader.util + +import com.johnsnowlabs.reader.{ElementType, HTMLElement} +import org.apache.poi.hslf.usermodel.{HSLFSlide, HSLFTable, HSLFTextShape} +import org.apache.poi.xslf.usermodel.{XSLFSlide, XSLFTable, XSLFTextShape} + +import scala.collection.JavaConverters._ +import scala.collection.mutable + +object PptParser { + + // Extract content from legacy PowerPoint slides (.ppt) + def extractHSLFSlideContent(slide: HSLFSlide): Seq[HTMLElement] = { + val title = Option(slide.getTitle).getOrElse("") + val titleElement = if (title.nonEmpty) { + Seq(HTMLElement(elementType = ElementType.TITLE, content = title, metadata = mutable.Map())) + } else Seq() + + val content: Seq[HTMLElement] = slide.getShapes.asScala.flatMap { + case textShape: HSLFTextShape => + textShape.getTextParagraphs.asScala.flatMap { paragraph => + val isBullet = paragraph.isBullet + val bulletSymbol = Option(paragraph.getBulletChar).getOrElse("") + val paragraphText = paragraph.getTextRuns.asScala.map(_.getRawText).mkString("") + + if (isBullet) { + Some( + HTMLElement( + elementType = ElementType.LIST_ITEM, + content = s"$bulletSymbol $paragraphText", + metadata = mutable.Map())) + } else if (paragraphText.nonEmpty) { + Some( + HTMLElement( + elementType = ElementType.NARRATIVE_TEXT, + content = paragraphText, + metadata = mutable.Map())) + } else { + None + } + } + + case table: HSLFTable => + val cellElements = (0 until table.getNumberOfRows).flatMap { rowIndex => + (0 until table.getNumberOfColumns).map { colIndex => + val cellContent = + Option(table.getCell(rowIndex, colIndex)).map(_.getText).getOrElse("").trim + HTMLElement( + elementType = ElementType.TABLE, + content = cellContent, + metadata = + mutable.Map("tableLocation" -> s"(${rowIndex.toString}, ${colIndex.toString})")) + } + } + + cellElements + + case _ => Seq() + } + + titleElement ++ content + } + + def extractXSLFSlideContent(slide: XSLFSlide): Seq[HTMLElement] = { + val title = Option(slide.getTitle).getOrElse("") + val titleElement = if (title.nonEmpty) { + Seq(HTMLElement(elementType = ElementType.TITLE, content = title, metadata = mutable.Map())) + } else Seq() + + val content: Seq[HTMLElement] = slide.getShapes.asScala.flatMap { + case textShape: XSLFTextShape + if textShape.getText != null && + textShape.getText != title => + textShape.getTextParagraphs.asScala.map { paragraph => + val isBullet = paragraph.isBullet + val bulletSymbol = Option(paragraph.getBulletCharacter).getOrElse("") + val paragraphText = paragraph.getText + if (isBullet) { + HTMLElement( + elementType = ElementType.LIST_ITEM, + content = s"$bulletSymbol $paragraphText", + metadata = mutable.Map()) + } else { + HTMLElement( + elementType = ElementType.NARRATIVE_TEXT, + content = paragraphText, + metadata = mutable.Map()) + } + } + case table: XSLFTable => + val cellElements = table.getRows.asScala.zipWithIndex.flatMap { case (row, rowIndex) => + row.getCells.asScala.zipWithIndex.map { case (cell, colIndex) => + val cellContent = Option(cell.getText).getOrElse("").trim // Extract cell content + HTMLElement( + elementType = ElementType.TABLE, + content = cellContent, + metadata = + mutable.Map("tableLocation" -> s"(${rowIndex.toString}, ${colIndex.toString})")) + } + } + + cellElements + + case _ => Seq() + } + + titleElement ++ content + } + +} diff --git a/src/test/resources/reader/ppt/fake-power-point-table.pptx b/src/test/resources/reader/ppt/fake-power-point-table.pptx new file mode 100755 index 0000000000000000000000000000000000000000..3e49ae3df04e862e6b4b334dce04fb453e9b3d38 GIT binary patch literal 39894 zcmeFYV~}Ot*0!0pZCBd1ZQHh4Y1?+CZQGfZR;8^<+wQD-&WS$J{l$ssc>CwOf6SP3 ztQk9Et=Rj%#y!Sd@>0McC;$);5C9EIJ*oiz@j>~T+uJ+SIh&iKg1o{7T~tx8NpAd*MeJFvlEEgi2*tAqPf=GY z0qSthCDjV80)9b#SQ=n8nYWlT)$(72G)#DT--1sVORmTzfx$&8E8Vsk=}>n<*zt#; zh+Un^4(tm~2}~O19+bA9b1fF;P;23B4PbMTMhfuDs*qWCHLRMKSyBO{HgZuY3>s*N zkAqyKPmafy^a8O){=I0`1k2W!#PzL<8@3B0+MNG8Q5s3mX+Ss$jFr635VOkEqI4B6 zF{Y>qG+^SZKsD6}r1}goF}DFHc&HKGt0E(oYr1e~T2r>>hr@am)df!P9i?rYucS64 z#K=8r)?v%G+fgfT_CkomSUH5t5Z(#15rh&!PqE32J9wPhG&} z;~Q*^QXd=IQ!(fk{)D%wUqoI{Kn+_*cDG*NEA-niluyCRBc00qv;aeaSLYb6V!;~C ztkX~N+EdlmOV72c^g8Gyt3p|UX_s&DwujGYhYA6g&>R5#c-lY3WA2!Pg`ojjXedlE zJv$|8>vX9>ElnsAgOmv@%GajEC_)%D_;q<-uMZl?jt&||M&NVs_?`})b_Vvq`LrNv zjPTNf=(<05FYWk5e0iQPh6-xV+)Z|UKaU%>eBS5aevw{1FU5YkzdSv&<$gX75JP)u z?dC!9{eVPP|TQR$41h6ueD+ht&1CoT9j? zrO`^Xgvg{07~u%wj&Q+QItH>KVTYK-Pz@JzoM3po=p*@wzNO13Ag(3XtV;&Jb}=g` z;U?^6Xagco!}Vx~1WlJ>Zq_>da)v^3(GEea zWP}B%(ccK*6H6hlwBKH=o4i9s>v#m}$&f9mAc@NF*-eP0-B)(0COPxoybpOz4q4Y7 z4107#wJzp#2!&oA*pBgXu5^H0uDiv0E@v+dV|CT054Y0MCzn~^*Euqc&yPKYG1r!q zL~YAEqp9fWaC!}SN5g-IzB7jyP*ev*l1z<=L%lOBT>C^RA?zI>5vkP2phyM>K&LgN zxcNX9={tGcQqm>*4SyF48gd-yG@e@7ZxBxpz`_iihqBWC&Sanq$X&!d-6C}>m-HKJ zGpJpF-E(%^<`?vA9C=zi{jF!8K3a+|gRt&om_n40p$6;OE!JlQ+dC751Fvj#75j=# z73pvVlk+cE+E|#o9Zc9njPCH(!5Vvc{XS+xR)M9Hfahzx~!Eu##w9t2g@@VOjVJ0;t zOgf`G`F5z>5v1rt-(y9^6_1=bQ-aP;=BCu>Z%=+dup22qq_?$G(sYOJ$JGW_erIER z#YLHJmpBf?X~AJ$Qq>tSze2;;&Zud+rUqe|u775UF1E7H%wZSQeC z)Y!CCYBl+B2G49u3Ea${gBQjV#+z|@3xRYkSV7(Myo=y$i#cpk`B9-HZzarn+*y8ppFdyApieG z-WmUqceyWlNBOtBYdM~O$vc;8N0UY1;N%Q3%`B2t1d2FknVpa)4!yoQ+Zm&N3vP%F zpr(H6#k}!ptqXroe<@!xMWk#Z>jqRK2MN)==jHpRr&nyD@e39JPv30S{>3q?M$!$n65J?g3VfIAS z{05cmo1=dEX-JK%hsv)YMUF(-SCCteOdsvSA+V&fFJf2rzld>g1n253S7J@nI9+bAB`02~k?5BEd^j36_Dh^-DTYY-APPw4HF#%q>Z}KhIa2vBOv`fIo@GIIq zS!#Z#TxWcUf^J>7cIb8l*->BHf^LP4;!GSh7mF+CnXp_rLkxjRDeGBzekdhO;C8wKq?ZR6B=cSu1&#o4 zoQg$r0Nvb8I>gP`CuKw6D^rwIKdz{F+C9-++v4Usa%l^5)~OOsTljvV#Pdx_pVxU| z8dGbqUAFjKU!ka`?aImC}kCfn&Em$VM&8dxDupc(P4U0&KtC(`0)LYsMjJt=rTrJ zqJDlpe!(>3iz|)r3z^c4c!qN>W3<$G?cH!l4`{|>;+B;{^geb5$6@BVI{e6R&3uG2 zMhlX@G1P<@6+8l+%nRVC{b$QpS~%icAc-yMwlWd~s-?ZCD8hV^NII5NjCuI%*QjhQ z=#jGb@3^*2QjZw$R~=i0EjvjUL@Ma`-|s^4-gzI6T<*QDvprU#Y~Fjb%fgp(x<}ID zyg7)EQhu(s%3pv2v!uf^<_#I}h5s@#s1AbWn8*P%yHTPpc!wYzi%Qgh5Xs|2Autfs zVW@0sZ{Q+VFQ%`L!xnim$nxKQHOeaeyu6j0Rs5L6>L{K8=9Z>Dn$5iSXsGM3+ z(yvgjQmOjLex=0`*CP>{xGhwux9-H5aTnrztbbaK-09@yfn81cE*(@Se&1K@2qE4; zvupB~FSPKjts)4~Ko#jEtI85NPUiqP`ePGoP7&cFcV=z{i?mh zd&`%?Z&)I`i~}6gEwnN`j^4O)8hcW{>eo#JHzCk9Mcimf$Dkah{MIf%>EQ%zTHJpn znvT(|#kLCYZf{$HZ#D~Sa{FF-ZW+G1%0q%(_10j)+we^HN8vTFGv|JMDZJHxRd~XG zCGfU(&L&PW22RcobTvn(Ya@A56&?p_R))`u z{e)+bW(KNN{BfyQLyNSP2vrB8FeQFs7k;PL^>!e_Vxo=s3@ITJ1TLADmH%rxxx83_ zz(H1$B=|=%{W=QU+`>uef&*+lHS~zQQKYimL{cn z4JmvFUdT0lgy?XY1EVBZHu+$Igz<2b+W>nz4$Omh5J6?(3x8`KXx~_7$;bNR5BVRq z`bH@Fh9^`N=vP^KQU~Jji-=TRfxg`=0uy~Cy}Z=B8!Uw>w9gq{2c|hE`-JAPii;_Z z?V9{_1jX7&<8yGVK@ObDv1og0phf-*mj)mUjedIo0XnJx(4#4&P^rY? zerO32%SSFJeuEkI7{nl z=CR;+O0>WW*fFh&%K|aVN!!%GIp;VVa!3IlVWUg27PQnRB#pOZNAE zHCz^4F%V9bzQkswY=FWpRC@wzMg`usuT^v63THFkF;1?G>|YS_yt^0VAyA8*Qj^z! zN7o=`)SK*PDes>FH0y5PmBtjOMF3OpMgz^aJ)_b7&h33G@$UQiG4IG!{{)nmnaPjx zyX8%_;!f4Ye?_N1K{0%HQF(JhUT%~44mAmvHTlPY7d8CtK{Gcq0{f%&CbgtxukZ*U zGG{LU%pM*+9R1cb0{tu;{lK1IIF5e*4hhaO&mB3lE|2_q^qrvmRs>I zMz8BXrjssvxAiG|2_}cqgB1ps=^^_}hU1VqX65a{;F7s!Ejx(DFga$`?ezk3==F0d z*D&Y>e4MHJLazj6%yn(c6pHz`Y~QK9w^7{V9(cByq={|FE^Y;3FA;LG#l5L6-H=g7 z^{!|vwI6qWtdoA4)8Nys8XaKf8hW^P6jl8;&)rv&aBLY^u{3{>;7ulR%S@=kGyW$S zyKIU5Pf*LZN>=Zmpw^P*)cxO`*w$4(n*RjJ7A^h%2KCxM*{$820=j&ke4p|EC_kH* z4tK?u^0U+bx7?ok|D^nSjwaUsZ~dqJ6OEppgzBe<0ly-9`3X**{#y+{jtNlnuKmlDcc!uk! zPA{-@k%2u$lq#JeEnY}K>yYLMXEvOWH4`E1&9BB<5UaZXrOyFz@OkmLNhT46UoSG|Av8gK~6 zK2@LkuRA3M{BO?!|39CFlC_1g$-f@L-}zq#W~;s{?y(~H26P9Gd1 zF|Eg2908Np8+y*DeOn>)eyJv*auCcY?u}~FeRplr2yeOR?ZZSAey8Vvk(N0 zHz6)3dcQr`G0Y$xHdHVq1*MsTWjRh=+v2TAk4wn_nFvnbA*!lUVG4pNqZs+ENJ*)0 z_l;!)P1hw@GJ0t{g()dkbSZ34w6Lp-cSN^Z6*g~+#DW8rm3=nJ06H(TU4iub-ad{S z&gqxUL>NVFRcEcN!@hJrEvA}Njnuf@3t$tLB34^mnP4Yvh-rXOJ;W(vW{5%q+^5IPY6`h7qBe5ta_kv&-z`1X4NWrrcV{*wxro+`JyaH!28_@ z-F2W;PUJb%s+N4m@_wf-<{8!YiHy!18zoKUD&EMVv`-L`j6ciCFcA)z6uYg5dC@rZ zNm32bN~TwIdGlgpET{LrW8C%n(Uoi5@HF+D?rHEL8oKNy^giS24vJDzZH5o%-3xZ6 z+KgOr8wBxYPxxmn^3KuIp~D+2mG9MwFEW7W3#FSD+@y(^ zUSUfEI2$t71tWv7h#s&x$QxBHx?G+d0AwPmEIdgos@I%(kEW-afFKU6+LaJg#3brO zC}MI=|777|etacGcDV2C!cESxPkU(8N?=>ZAvUW^?{n1*B#oBB_p;*FH`=ivg&##b zAJQ#BeMr3_`UVaIAJ}g0(f7jq9dvlB-XM_ke0AS1-pn08i#i`E4|p2^+XH`m9A2FB z{gICVjn1VTU;qGm=>H}kl>Z0$_zxxhzeVI6kmlB$25YZX+n1pru zf!hG8C%nv6$U|U#=c5Ud4QWMsTXbxphm4uvCh0_;r8b};?1Q$ueFNv;*U$RGh}_u4 zNa5*_*cgLw>A{|vLoN#2jjPlAs0Ah_2*H}h>$UPA(`wpl;f zc!2%$I7+DoA6vd|mi51?>AyXN(;p#~Ht?`>adx8nkHtTpLfht#r{MYW6b8OrZ^h)_ z97z@Sq`9j1!n1(+Nl5CzAkqAcsj{c`;kpFuW*_uo6B1DXAQkcBh`BC=>yPS05VWZu zd1%W*SqjnV?3j!v2=geWUW;!hSYgvf*d=Zyw%+VrT#?ejObFxv`Z>-rp~WXtOSZbm zzoUFR)b}@=*rs({1@?9tQ=e_1#*#AtJ7UuA&BDhvN&p;o8y%E{Xx*u1GHz?e)x;Ec zYQU0n#f%-OvZsG8FsWrD6-|tMV30;k?LWT478= z%rB(=3``(J5k!f*4^aHLa|{{vg%fly z?kCf(sd3)Zlpw`~Bll&Ot~dO80#U9t#)#R$lXQT9K!U+Enp+9|wjl17FbUq``%}1+ z0Ta#^qIMiK`zQkAA;_`oK?CY}#9~^dy|hF_Nj)&N!d=t{^ZN?gqS6__*T?G4L#9n4 zm+UL8F==d)SH@=5j9f!?T%4J)2{5ZVhX}VQXcDbT>Z@o(8+Jxk6OmU`%41E@-`>?& zk=cFpHVs&Ip*pn!-B8@`&hzN@u6v?JY_8l{@E+~2-C^;1hWBu3@p}4@f4RR63?VbS z_hZS~x4QR($(0NvH?Sb;pkg2;>0dHu2#usw>syLo;Ij|3Jvz_EF!CfBn&aL+S7W&Ix_CxMWm;`I>5v&nDwzX0EXs^q#pbf^_7R zeP{l+M6T>9meO>h+AT_(!d;`s+~It?>%#3mRgN|r)OpFoe9x!3g=XsD_l$sy;+RX^ z10^pV%To_1RJnjcvJZ@<+eX@*du|uYocuG8rc3l9N(*yE~)K!-zQhxp^A(SQFpWqLIw6(L954-*D24s?fD*y6g0W}}jF^(d6QSP3LO~yA`WOK6psU)o( zTrYtsQIoffJ(f}!VVFcpq`_(!vmXo8YE8%gv02Ml!;gSR*M|YiNPi)W8->d&`POYcHwM$#gcBI9it0TDJ{# zt0gkgMpDr><5{d|>`i43)@5}zCQHK^)vPQrG%U?DrfcfMi^aorMIqf_lzQFr0K_mh z`=fh$Zzm_}jCivwqT!Y8AEs)2WHG1DR&*zqkHz^JjwB7zq7uUASYdVxe$S(!1`?QD z?{CL7mwdi2T3L~!OgBoS&~aq>bLwg_&^rFhmb{I2$+;(~!Fs|zU_%{kQQW{u0*rAw z!A~E4bBzpjDcz%ccYnz&6&E1S4F&)JFZ|!g{BM)UkKDt7nBAe3Y)cGeVtoC=waxprZQY&YMFhC5Fq~pMiRN!%_M_|nB^A< z4%~d}l*f?(%WZz`Zae`0A+xajDR63?iT zhR}qZka&ZW#RWEjJ{%!E`i|~Vjm*EA0ZtsmUT|AnPoTJm>+`^d)0n*-u)?^HWhbDP z5aZZsYCg}P)*jBNS2hs^$-yRL2E*P#(o9siPi=>DIK(ES8xdIe&Wn4)*o!ecAK@b< zC=vrHha(RGn6}@{DXG3+3&(bs7VOR-J|{>b3kn@M2vLusq$u-1Tu+{H!aHk_ybw~V zSUU!Dm;i^?NE@RhQ#xJor|=QwE-MymM2CRU7Xi@W@-^Oo&=G-{pmzHu=0Lf$g5Kn# zj!4R76U^&Y*OEj3Kpe2B9T2H zlj(PV&ta+3b<~~{(GOFIGZe<_2O(2l0(r5gt=SReJ(^n5$_$@EnR}Rgl(ODnq$r z($};N1*N?t!I)Sn;ci#CEpw6raX=r<*DW__ouUhrJmQ>#jN@2tz6AYr)2@)4)Woz6 zuB&f4V?EBGv*sx|tWRI7!vt>820TAO9W|yTiP;k(Q8_v+UP8a$eg-~{JFP81UW#^3KDNXMZvXl4f0RV~Ad#5$*AM^G?yvOkgF z&YTjWCN69bjL`#(B96tP+43am-wMBs56y2fwa9G(IQL}Omq;loH84jyq1ENIP|1-JPI|zy+IbKb$wrvftckpby1Jx0Jllorv>W%_A+zo zzMi{RN5w50KEkWbEgL*SbIl$aIZ}1y{_QWFQSo{P|I)diiTwI0G}26d2U3zd@C(z* z?o*Q7eLK4xO72l5TZ0zG0Z{k)C`gq;VcAX4bq$lj7!k^Dv_F>(Zx{tkIe^EfQPYt@ zx^?X3$r<%(k&+^;w^w}Hs9y_yK7H!NhPy2L6BRT_l#Kx0pClQ_bUmcVUm(?;CRi<< zcbJ+g53nIRa~VzF>iP3{cKz5u^xU{{Fgt7Ov#M?R*mD;GHhIIcfQZ*=SK>0! z^7G=T8~646GUrOl-qXIc6-z~)-V3J{x_Y=EIl6x^`*+Yers~kq zTUk4y>UcZli?Kz&UPKyyjErF1IK@(ZJ0L2A!{YWb;Y7idRx-4HFD8LGSQ~th3Qy6xmY6^Sd*4YOe|pDmN-_H z%+PL=zaU=zfZDvqzq}e26#+gX(CBEYgD587=k@!gp0C%Z*woNMlp$pk07*jfaJ^$zGiq4$2?L1e zLmH6E)6kt;oR4PtPorM;!qjrAWO$*U(p1O=59$fGzJJdJ#P)tBKY!H@*Z+UO>R+1t zFIHWgnf{)c&^~v}WdFjdH+aZBf}ty!6J$X1qXidRQ!KJ+_;f*^6dJ z>^zx<1c5z$tHYSALv#rXfljqET&lgZcPKjDC2?gX!e~DUUd1qzj*9#lnE=N0$jJ!2 z8d*-cG0HIDphVR036{ypc!Qp9VF3*Il6^LHS-`ZTUrQvq18^Ka|2d|6jCe{KAFN%B8ik~QZ7@(4?ay1b*z$DOvZ2I zMoQ>Rw)r~>x7D*pbOd>G0`##t+e8pMS-s+%60Utybb@v9W@jXpi6qUR5Qi2wknzlr zdr0V~UQjHVsm)n|1~zS!X4g^Kii7E@lK~z~ci`xhEN3O`fbdzpL*Kb9vAR%NTcPEQ z%m@Ppo~GpN1(k#I9?>fw!9>sSvI%m{5IPAg;UI??Va`?^41NZB<)@Ap`56q*Zdd{3sNc>r~ZI9yEQL zmuD?~emkPaT$qvUYK17Y;a8l+PG|U9b;52a4NYUemoMmbTGig8r@}5~;P9_uBn^y{ zE%}Wnv~H4&;t-4Udcsv6mgx}fmr&e&sPv06{X!z&Dvk`W!cvS<4$pS8%p^9QywU-X z9zbcOmU5H5$3m_?1(Kpez$ylIUh?rnj!{gQWg`zq>1I-ej08z;8CY-;byBkE&)nOm z98`x1*aL^v+{_;xEhdKJPyy09nv0#&5Q?qpl$H4Fkjt93g;zy2ZUuw$vlPkA_R%i`h3qnD`L zGHTcS0VtXiDjTOEc$cOvw2I6(l7@W2U19{tiF(tOWUo&04d@ zW!RZB9%6k;h$&ulUXqxOqt?uhkv1+>x@aVB>I~+9E?xB%~%G@c}ToJ3g zcK6lKcGn+(*3 zlx%HMyEO^AWT=%+byF$p&?*48KLV-HrY+yhGuS-TJRk$pOV7tcE4!*;{qXIfe@c4m zq538C5o|DhHXir>#d^ZmEjwkTc(&Yv-b>byJ&zi;AkTyU(cbDDRa909$1Y&o@;F{?$j&F&Ydq=ldCts0`9 zQq&{OlZ5^Yd4mZQdsTB0UWe}?A1D4VmSbqAnMN(fkpfnL=NyoPw&NsnpfB=Xk(Koj zT_y^G9SSlP#z{eLVfZqGo5)VnQQYDKtyR6~ea(1$R;Qzj3QkL^IUQBh#uWs{5Ya}^ z(anY_Rg5Ww%rP{~tCaT5mUbaC?9DUgRM@MO7+8XP%Yp?}ixw4Q7XNGps;Zi+{AY9d zSo6u~zcvT2Q3hcD-OK?_+1(uvK#XEHB=Lye)5@8V25*zFs-oC*p_O1rYh;`u&2?~$6tGh$aYlf@#U7iMgQk+ z`7h1>7wcbjynnF1;kYV_;`7xP6zy`oqXU7QB4M6l-doMtmZsy1Z1l5~sHjG>o;#M_ z%(I(mG51^&8%=a}`&I>l#LC_nQkxEMkm+f5&uw6&JGUEObq1`Z_G2JRuWXmBQyR+ z5hd^dQ$(LPG7K@|HzUJ63j||ZcFH~{t>e~&5frYO70bqfA3c%>-y`%v9RfL;t&agH zI51sokdeG;=!@*e(970vHz?*HsT0*tP6BmL_7L0h@L>dCsg-1owP7c`2KaUb{ls4)<_)+dhai`edcW zz2deZEq8Pdr~?Wq8*c=HDr&@L?py&nL9x@_3mOG*62i})#J?JA611E-UNm@$ZWr-I z{S)>>_;f#l@J6&Z7f1X z_Un#M{|2@hnl8i%*xbMvIm-@Ex_F`VKA~Q&7=GyE%71#6?O8pQqtmi}Kh8w6s_OJp z?H~_K1z#F8IsR7N0wF_GYVSTx^X_p16?DcNyxt{xXBk52SO4g&d4rPxQU1-#4k5dv2x&-88FslvRo3!57m%rHLpb)-2n#+srvP1W-ZPa zOX-U`s+btlz%a3KGLAy*FRUA(H1``aWBZ`wz{t@ldNX(Y$nEvQ=)HdR3V|F1byxCZ zlf0Up7pmE-!yh(Mb*J_p;nwC>4IV*ruN)XMQgg@g|Cg^ubWIJDs|hBkA(<*;b}n)l-XSsO+2k0yrk2ZrsxTTRtMw<4qCAQZ&$Ad#AxVqs zY!z9ZCe$;MDzI9z89Iz7m{&sk)h3qteEIm7%foERCJ=*MtgRlW-+72CZ4_ylnknbk zFrB1^F?2e7ukcZfDuVgBjBxwXPMuvpxB1A2Ri&a`hq(!dVFo_N@ZaDWnkx*4t?5iB zXd*Fh$0IP89*kLtD=;-Ce_D#FGU;80W>lK~iKGJ?Nvkr|XK*B04qMikR@(Gr%#J&f z60D&!EwC|FAI$VnHAPvdr<9r;Su>PAZM7)==DSSraE_hW@jfSC(|y2>wf0=GAC*doccc8KxJdnVnyYtW%KPtcUaljnyQ+5pz%^oO?N5&tB}qWSJa*GVi|ia?%lNI zUf8{A$E~Q>bV^mCqsQ@O@vl|9&_K}}+^xb7IAgw{*SeC|jU8+aw_H7Md&~Hq`@c}C z`CJ>5EPCI+G%ZoTXDmo_2tn?A)zNt;2h`X>W-~Mjqu5{KdTv|{a=tfoXUGd>7@pDY z4@7NWfz?+L;yaWxq67m!d zPT8tDGO$8lb?@Tg_Kyfrvta2zRdSbTl4vxf^glKAQ%+N?E=I;bMe@aaFqZZJ<3ARDh*O(3mI~v_8kG-NFwV{VJo&VsdHZWJAX$6kXTTqG~4@GOaS zcA1eTo_NM%Juo7Qa27pK!WGF9rWGN#8J))WQ^ooOplu~23n@5cB{c5v!I6V+Q#+t8 zw6x?ro<7J={rw{0=)u5<-0>oPu;)Lo`;Ym(*f@FWg$euUoqym5U+`uBfS72N@lU*X|pEG8hW9mTUXAoaV^ zz5yf53FUzaU(DaEfIOTu4R0R)$A%t=k1dX!(-=k@qy?vy11_vqlV`N3Cm7-sF8lm8 z9xV>MEFz2}*{n_Nd}LhLf!MmFaxzJMXYeh1yQ>0@hk<|OStmTpf|OPxpgDGRgeo^- zsIdcSauva*U#DZ9;g7A6@cLblsgz6wrPFT%B0B0w9AhHg=(;QM_=E}sN9UL;o^Zf( zVshlB``-{bU`bPe5%t6hGG|?!89r3FNxhfwUSbd!_hw%s;nC@Qzq>GE`M7_Y-tC3X zR_KXNkcW%?bU9XRb&uXP@Csk9JB*Yfai8K>mSB^}hB2de1+AwwK}+vS!ka`ZWZ|3l zLVU0zCI4{=Qf|+LFv?>tq4%wQva2s3+qQAM>ftPZcrmA|ye{PiSv)yW(cNOt(mtJ| zdP#x#UdO-Bvphr2sDWP`9{Fl=JS+<~3{?IFZ`yH{=d8mpCi^0(+)Vmp$EsaIFm*>! zdQ!ehg@{_@7}F690YE1CKqajfzW}7iwSfQYy#a1~)E-6c9(#s$qjMWB+Z2duTxh1gtXbI(*tsuxiA+6JfWV{)aY_I&m zhcYoudoW}#NW?-Es8UOZXo6#mQ9GxwpEO`<+(od$eUTh7q`F{aHZVeQ+l0C=v|74P ze!8SLIn7hKAr@^Jg(1k3itY~O)>VO)w&y|{;&SY&gvWjc#)+U#ckljPHYg{?G zPnAuT;~mD|nw+coO|{gc4hLd`$z&26&Wg#dT_H{R;G#3E1LB=Z@EI@NldLJrCac=~QZSQe!M2gkjo_#=%)!k)) z;(qzqWsu}H^&eT*<2DT+p}b0ajU1u8zJuak{MbEa;GR1=V?my+WVr`-%uMGjZpn=hi!x-0 z#D;E5BF9H{Y|HY%a=JFoUes0qVKnS;Q`cCIZhNoopGD>9(fH7ofbU1;K~=p^sdk!Q zn?v>O5UP}vUXx6UrJF~icQvGPyq_PLQt5?Cr_OTkWaq6Fp&bL(S+mAt& z_bB-})D`j?0Op~dfp%1U#|wvKUAVW%MOAOYfW@ZiFEr2LtMh=*=9k(50Uc>SO3~ur zNd!331xRL~_V~Y=`*d}y;jTXwm38=%s7-=v`Pd-WXjK$girmP{#&8LnO}g{T>mA9T z$8Z^%P3G$Rmx<*xDWl#WFL8rdNNK5pcy0y{s%6`B9{yaK)^#ys!_<5U_jbTNRKD)naESl&hZTgE&$`nQ>wJg7;5CmDqNe5E z6s)H85JIhMv$KGvYvW;GzbYxu?;QZ-DlOl*h?G?9D3$)>$`P8b$KGpS81v zerl?gds}d-R{OBJ)=HFO?j(5!?;6qJaa5q2oatDZQAl~B?~NsG0e}ikjv#3lDEaq8 z#q^pFgqtpWfz6)7<|n6tjp2b@>#M_%j;~V$Hbsk8R4!^ZD%|YcghWPa z4n;sB9XdK1JlPc$T^|AH)itf%C-ivLHSN)%rxmt)J1^zBdKULDl4~k@sPMd`-A=sl zt|{sv)fHX<6(Zru@SnEIJM+OUsP7Aa_sOwu`NypokI5B>wiqueV>$H?@89X-z~4i3 zg%4X0?(jTK_WG&segiX*gpdp#sfeQ^M|D!AR>Q6!6;OtIP3QI1&zeTQ9{^OC-f`It z6kmQi8O-BxS*#{f2fpYi!Zm!^)KIYPJcrV}S>_%?BU4cLXd&q6;&2jdC2@Ci98GiP z$+VR;ssW+B8NmVz+nM1?0z9lxG%T6KAFRcE*E8&mGv*oRO$^hKp~HE>qVtW-(U3OW z%y2Z!CnNkTxw zrsLww`Zv?j+W2!~z~|420UffJ7Lyj)cvBe#0;vGbuH^-OsYaS0B8~*tJviVO&9%Kd z2~ov-5-Q0IV^GrpD5P*Ac=&0zr_m2DoGu@yRAMVw%xQ?6<;^-PEd(->$ty!c-*yhZ z2%AV~KP5ki5M)o_w)fxrB}>i|3f}=)jlWSJ1Kl#DElUv|Ejzf^2XSnV1PM}){0=(c z|0&gMuL#B~wZ!4*wS>#g@v~NE%1ka@?)NRVe@~oovJf>C84MAD@sn4&f^Fx)sM7j@ z-$wP3f}kU%&IDspc(ki#n}*yO>cHOIh(gY{D0@b9%PDs7QTJ?OBkA0F5|g;`8J9sF z3wOo!DrQot-XMoK+8gv%ai&^*0yf#7MzxVLu&Y63w~WSqal|3JE(-f~grG6gKDD-BQsS@O5ix9=h|CTu9#>4E~U zG1n$ko~L$rO!>LM-2qfS<-9V{Xj5AuP|`+p8G#MHYF$9YR>86v$bYtu#*v$yMfh2v#8wRE@FqHb2Fz9r>1wRkcWbLaf_~nmd1qYJY6yw zovT#&v})=NjXIkW`=|Q40qQPNr=I^KjQicEklik1PwYs|H3%x6ukSSoOHOS+@)=!D z?J%;6d;j|o@`5`UYL2<@HH;Bizw(|Z=xb4b#yzA=-k6hHy>20R6qtb{2+fX2;H(BjZXFTSqJ>v84xPN&0M zF;w*)I$~Y&+13*6=~kM8M9TOwIqO9&4OTr$>qzfLBW!{6_6gH^`wRst{r+SnV7-FkC$!@-R`$tX}()A@8B&n2FQ z?_cbDdsk6YW(qe>J|#~t*8#py!X6aMEfyIY9Brxb{d_w=<+G&8Q%jOMhV3mYXI4!% zT2&!%Js~sC+GW7jc@jBO-vZ)A1Al3UDvgZ{&=+^bb8#kdjv*sEP!(4LqjHx+;~M#gk%$0LlMa9j zgy?DM%~|6>mQECA1c_G_4m$D(kKcji`TP6Jd%Prla}Ggv^w>2~(vIiC2T?4MyEBmU zI$MBvhI9ZA&ING@bRqT(CCO*04jWw?a6+L+MU!I#0|-V@r<=~1C<(A=piiQQltNID zsF2$u-LKX}w!P(q8PhL~oA4LU`-qQj9CUXaMHLy+0^Hq0hf=}>7;4I!lI zs3{Cu!jxzSdVh#H&3}i5ZV;U&H*PWb5k1SE)gFo5pg$XA3P?p$EnDO3mMrO&g0Y6A zr7OvR$Tqj4OJ`@yU+<8Xp%+v?Zwv>TD6>Wh zqIaEvsc|67?!NsN(TCoXH!Kv=f=^YGOfB9VRAurJAa^m6Z&m;t!`e1NC646M(kLk7 z$Q0%e`K_H}b*!kT8!c1_3s};_aIn3sU)I4M^Fq4H59ur*4WcH0j7^>=V@$9_(=BPf z==b6TSoF*?Yn3wwAcvlDSO}yPvb(GHJzou_<0e7WwS1y2Dtc;SBP!(ER5*XLK2e(h zr@Ek}*`$wPY)2jYKwHcI)!ti2#no)lgSdNe3mV)dXb2jd0Kwhe-6cqZyIXK~2?UqM zLU4C?C%8Mn7lW7yDH{{HJH(6_ihzuk3KC6YOow&SN+8~05NS9chK7D7@wO*`*ed^Tw!$` zpYCaas$lv3}_5w{U%mWXp>LZ0siSASXcprUJboGK`u8}*JX5V?lv z``chujM-%Gz^e#F3whEV;CA3K~(;DyBl&Zkh zd1|Ti5@#Lmij+K%GC8*EknRr;4d29KlLfa6Q|&LXRhPZfb38JT%au&Oy}TbBmIQbVu?uxiJMLZxNa(&&k{im^e1Py*NKMD&?5L&_w1PBNM{r`Jm`7xgUA7OE@=lIW}aMaA>4H0m&Xw$;f$@2ie0dr$^ zDWfl6p7m5a;ppI5PkO~de#st9Y!qC~0B&z84UeKIAe&`|O7=&DkZ+9krhQJZedDg* zObV~r=zhejvym}{_`Vb-tv7QCLecn3+h>~Q=jv&RnJ9YLQ$z6Jc{JZrmEB$L2ZHmT zxt(LA1j^H*VDZoSXxRALj74ToC2_n8N%xO@7Gal#a)lBt$Qox3arn_}F&bTko$Pso zPeSdR^7T%_Af)f6%U1f2>!HN@BA&r z_GMKmhcG|Icbknbm~gAsekRFb$R*#tUNEm3N7Iw8b?R@KcN(yovmTJ+mcR;e*lCB~ zQG71f;1C%aO!3>*robZh=vOT!H;p8d11<}rFK|=B&3mtsP^1eCtmLfgfy@Nayb3bP z7Q;1?dj+?G*y9h60eVLz9pn3qBe6_GsvsU6%RxsxJN7-#%g~p!1 zB#dlwzSUwFZe-H6Q72zz=dGYMMb^ABv=TeQa&7WhMNKm)OL6m(-d!{^s@>PBTQTht zP)fcMgVs&39Tl4}2n%)^UWA~Dg4z2Jw@1`u^PrKWIfuh!^c#6c&X?dapG4e z&oE(ipd?KSDw{j~sQYu_#vq760-{t!f$_zz%g|@nV08AvYs$*m->n@nRy$c)K8^D` zZ@UuicEZD9$jOl!=!J5jSMcVE2%bn&di$`!$|92jTy4iN0y7!i@j4L@x>hRQvk##Q zSmLY1({De(0WL8~L}Af~GP`(v^<6t#q=`vh;x;Z(;SioMwR=pykvonNrUAY-Pl`*i}8m47q|UP^-5(1Jk&LEFI62+;yum z_d^y?!2&{-F7*xsSN5`ns8T53l4fEKfz!Qd@e5y-wdJAi&cbIPWb4kd;n_J}8Mfu& z>dqoyAmr`Vv*x)zUSYE3p$qHg(Bvt>5AQv$4#NshTs5)d**IP?Vd0TGmgg2(a}s(e#zfpsZ^;7P?6* zPMyk6HtnxPuY{L`Ihr3!t_h_Ly}NPvQZgE(g6GzGitpn>;SB%AR#7r zfy#!e^!#=J`}w7if)plsrh$2u7tg(!Tu!lR$Q1fX>Qb2K^dw~^1@HM-&JX#*QEH1b z`(Z~pb?Bt#?>t&W{9>j1!1VgLno^iZ^+HB!ZtxytnQBvDV^}6uLU3HokYC>G>6&v+ zQW||-(?tN;_wroz3aQXAU@18<&#kdw&vKV#uPTQ@WL1F1p~@*5N*ac$9M)&SGhpeZ zxL4{mi~$!NVEf3I%$kwGKqH?3CUGOB69c~H{@TSaM<7!vu?}5%#%>`Kf5{MG?Ts<@ zoDWTOESQv=v|=mhhPoTC#D3^WG?WdE~Zn+EbSjBk+Rby)8Ls0-%L`r94s$ zJ=q{a;gfV<${A;@JZ-+Zsn@!rPnioByi7Bs5pOzXRg>Kb&6^IAv*P)JrnGNsGrpjz ze%wS!n#o+NphW$)`ujVYhhz1DR(W16@()2B(S0dXIvizE1uBS!k1|Tu(X2o z7|H>SHLWY*-Fkz~Y1e9Iw1Si4hOlOVxHM{K8ZNA9=nHf(<+wd%=1o|kf$lX*ntM0L zo6QnhD-Eyd&k7aa2O>1}YhIn|=*(b%>xv45X!>Vk>#Z$tp5+gUhZJ(0sV77A_a#W3 z$AhY~W@(io6*lW{v=*``syn<$yIIf6Mlg(3)<8lW@&gnEh>R^Oz6xnu6CwzS7p` z&FOCg27#;8yR(Do_qiTA6QtWg8s4qBG@9Mo#WaVJ_7(NWJ?_263wuTe!_vas7e z2TbwvDq;*T%1B7sJu_L3M;Ltgk;HaCk(DF|4B#UFf3Q^jL#F@Fe8s_@^XGhJ-*)C_ zZhH8I(~gy%u^7bw`}|j6Zu+OHn=(>!(4?N{H1J^a(}~R!2fzrfViX--2r;pN8cTv0 ze_Uy)eOTFkR&Za@WH1Viov0l88;^14PepJcw;ZpBv&%g3HB(&*qvTK^LAp`*iX;*W z%Lct%W6$L9=e!Mgvf%7ca~{*zVqWk{N!(E(@L!WiYj@57m|#H-0`)T?A$S-ki6Zm0_!eSQ>=r}}Ei1pZ3pZVGV5fj?LwgFiLaoxM zt#QIJ+NZ*370~RMiPqr~_RMYoHi7iL^{*WaNz-ewked|y9W4XjJ|Y$3eA-ac8e zbiTVkA3fTCE~u06LJ*9YLHN{(42R4~=&U7m5fu{BW1MYlZde-*Ex%lk4!A3sBE7k!Vqy(srY{3ulf52S zJ-72aeHaBcHQ0j0q>F~o zUBD9eD~u;-)H!N5XI#UEZRW$)OF$u;{IbsoLInM>uBm-qDY)<^EM~K^#J*_c3MxYZ z=px{kC>X1RzMx1x<5<(~4HemowgDmW!i|;?huEK%)gmb9+u{<(0n6*i3+m#ZESC!8 zRY7}zt2nlLEq(|B3QScmR^>ryrH?op#39;K6jb6d7$sfq3IXWFyzk8=*+?G&)zGEv zvok-c>U>pIzYOa(rNh?2DeA7NZC8Id1*%LoGfxVEOqSxn5V=_R}6-Z2BB5JM#!A>oKnpFyyF;1(eg(b|PdqgqNR z>sudGMl^`LbcyGxZPL;)Dkui5AM!EI0yHwqmS~O=$O(&D^#~aVi#qkNoQqex^x~aU zz`{Kmom0@l)s}34(-O6nZFJ|k)vi&hV`s>;Z^tXg;ym+U;ZBXt4PfE5_hZt`O=J!M zFLGv(+6>3x+06vY6%26UFjqijKdf1ynU(4|a(zQ8F~ozyvR{-a=Jn_LlE&qRTtXwt zO7>Ab$m3aXSQmsvu5sPa*7jbHdCFQ2VpJm~oOY)%hhBAqE^|E;5@q|sc&d8G%G=$_+^iqlLOie5w zE=W-nG_V&?^v06e1R!~xT5^evL(1@qrTUUhZsXri%-cs57UjD}H44zIXWmtda*d6A zEzr{9a4Tc5uWyCfTIh$Ta=OADu$|H(|73|9x864n`Gpc;)MRd_J#XznLkr(dz+Mg`z^TG+JK3?RUU0lF}h~ ztJj--2vAy8S?&k|G4sBSy3%CE!I;+caM!8Dc6?XPyjz6I zqE8GM6i}3;;=hP^jwmGdHWq*l$)n7^vG*|^;f#FTH#Ty!kS=C#6HHij<-DFl+ zyJY2(fPmoupymT=OHlBViDrg4YKEq2@AULm-{9fqVdBoRH<_C|nFy|q3Fyr#riV(s zvUr>pn9nX%SJV_BhRVZ-TFf@Ev)k8vvN8QsVZkyBV6SCol?JZbMgv_IBO$OqIg?5w zd7YeAWqrIlDr7y?U=YoN5S*3!}+Lai%m_$h&Wx!0`Ks}n-R=s7fw!P)I@ zyqD+w^}5z8ObpV>Po1zIz|u@;<+{Z*0+eaI;Q9MK`S{5^nXVAY4mWi-h()Q$;xnn2 z`6eS0<6g}4A?Kw${0LE;>NA?f0<+URh5z4&D1Ip2{~siuI_@5ZX zuSMHDwC_gKCGy!S^5{Jy8IHm8h}C4X0{Q8()z6%;hn%MH_ULNo_hqlixU+?NE4Iga zna9E~$S}zX2vZM7KiwYtod?IigdN^MrCXuh-`z?qFq6gA*GZASz7AZTjw~~S>@fKt z7ANB2fr@e6zj~;-*5vEA=^Qzb07XBN8F+IYQ_*OIEzB5-rq0uULg{a}8uB8cjXo8d zxNCBWi#Wg_DJ^n|W4!3zKKXvy86SEWS%py|GbUErScvicQLsr0)`%LK`6_+enQ7c} zNR_Df!LpLNwJ6eNK<$5(Wm0xx6VnMg5M=(!ev)vy~pFjv)#C7JQ~GDp(7IhKv~&P2?@BZ}g$YevdSMYKB8!|rJF_EV+i@^HWNygWZ! zyWL#gO%GVnk3=>>KUU=O5;tnLHVk=?ZqE!p0`NRiMLluf9mE|*!kgJDsz zh__3?MO-=~DmHk-IMl7PP|bb7QlLgd(wM2087{6}TZ!o2Sd9v_E)Szsj0%UXx2uU0 zrgWjSanetb=O)PK4;uuV1?lLcw|r&vH$qT5c?TsL9gWiuuz}1&5Fw(R@}?6do3Zn~ zwD7gSAxO+NFejhjCp>}@vcqAt2y25&H0;={>?$&*wNR0r5wB7+k^$@?ec9PFb`%$? z*8TdXTwXOmDgTEpOIzN|vy%bp$v0K-&VrZ&jJPcffy$wu!K}uYA|Yg&dCv@ZzD*u0 zBNBcA=T#5$})##D4Nt|0yCgeX*p7dHw~U zSxc&PQIh;Xw8rV0TJuA-i`?dDVVuJ0nrIqr|HW6JSqq;RjfrY(k7?2z(5yx8`I@}D zf@_h>37JgFqgjj6X*Kos_@h}1Pn}&v`0;pwZxTfdrVI?H%q>&t#&$(6kti=D|3#WF z(DVR7L`uqGmV06OC z2~S3Cao&O_oV=8_=5yr%UFij-DzG?bN~CpjGE6lTrmM)6s{O} z-p>cxn%2s3u>R({*2?TwYEfqawzu7(Xgcp(KvnLM%j4BL#| zF^-#jjA09dLOO|M;%X8+j!8l3laG;!vL%jFb|ao_r&3{lypAE(u|z8> zD)0e%wTz8TRdeWCr~tiM0^@L#C*BvAKmfg3hUD}{(GqI99=%#rP0dx$t7?~kUM+$W zp;9!d6BCbKEye{ECN`5Zl|Zi+Bswd!NV5! z`(Ul$k2LK$ort1>nkK~(-Sve6=B&@xUiKm12DdYO;#;|$R~JfK@)bUNhu?SQgb|>PfXhF0=K0&{+g*X=% z&tkXW`YP-y&`V7&n@(3UiU;2Q=<8Sw2mC2zZ?4b1r@iOeF77ABV4}V%j-hy_VJ$+h zi%e`3bd6SnS9!R0k&&PXXE-@st&E}^!SbSM>%u7&X3_Y05tV{GY$OPL%#&2iw4-}C zRFce14bQFYyj~@e>D&quejDY;%6W!QDynj+ABQgFD$!vvmxH1;7@Wl@1HYl0lHo`1 z4|G!_8QSE)d-p1AgshXZmm+47MO24FIa?GLYt~+6DWQBwxRV;UHptB%U=76-iLmWQ1?BIb#;XNyvZ?9!boR3oOlPnUvq)s2ni5;gvLsw?pADB%nj)KYPMVU{z% zvf$*aai;cyw~JW@+|sMgj}HB?DRh%nBHKCbT^g5AUuXc7y`>%a>&lbZ*Vcei&Gwjq z;dEWeXcQ8Qn78O=<|n3EmjS3^(-uD847o9sq$`c>vz}Mg<0Hvt8>|l)9g0{<%h10H zSqP#3_9{jF{^%2@!}+P`RJ5+&YC>IQmzg?3Fb(aTI(=z*oRH4}r|Y^E$9v)83cak# z{Jqc<%V9>vTSDO>a$CCuE*j+U-8qjtC9;;>;l>57!EU zD@>5O!8;gUKmxe`T*m0i#W!LL0+<1ay^bRY&_v(>ACAHcou#?!p56zloYv?|l*GjY12 zoz`QDP?eTSaCst@hU)?s0ITi<;p0mcW{(Nu6?i<-q1_8wJksyGOO82!spJ+%XQn&{ zz*KTJ=SEx^E3f~!vjOJ=Ep4M^$Co$3c??I`bzW##Ih0X5Z4s_}ND343MU@M@?^Mt} zj>dAW;x6+Au}p%>CHpjQi!;ZJ6dUP393 zntRnF-*(`kHt*Pnd5)_!4}Bd!=(RN~%E2b}dZbeX;4;H{SDJ02fKzBvNhZ~L{`%15 zG+R1Pp{rn{x76i?ZqaLor{L5?p|{Y*mP{sY{gJ=Ncz?| zj|-a&?<99m_vMZN|3I`>Fa=1;54}HAhDVWU0y#ct0%2N*Bg>pE;ZvnSqVl=&ZFih* zi6sy0!X(pWKO%=t#`QKi(@?XyJ?vV7j`zK=a+p_mRCJ|zprUIy0A=2O94Par$}4?J z!88vGD~0rj(g}T^%NiKpozZ~ZMP-XCzA3$HXf$s$gDoeB(5hSu&RcC*GON8=czartRA8HC z^lohlB7hcI#u{umz$7~q@>-}dS|;|n1>qJBi#`?L%_noTpy=I%=ZL}*Z-s4Rgm_dY zbkDmaz|516Ti*4k9;XV#gxJWDbI#e5lO>l%7yv``q6x{JJEo@I^lV6MZ0r_c%Cnfm zIY3ng6-{g8a=JfOdp#t+G#nX9QSDU$dh z`8U%Oj+G9NlK+X`H>j=*y_Hee%;n_ry;f)#@yc@E3raMDNv(k$2MF3(6_@A-FEi>w zSV3kP;g|%hGUA7zFb-~4K3b}*Zmm}jS9ib*=eRk4E7E}+iYVAQr-GgUs}QbY_*SJw z)|1qm@|NV5ly-q$XpM@D6_B^`uB{~`gVjPZWMa}6|KUe=TEN7Vv(rBFf(Xf>QQT6)a5{3O z_7b?hU&!2VAta}u0zo4aj*i3UF4ZljK>f6C3#+m;HIe-iMO+0ww$i7 zjD6Y!IiKBQuw((}baqj?eG5^ssA#uo{?gAnn570qZBn+sAM2;Do~|<^WgebtM~mQ; z_(o8+#VE9>cMDTx<+Qy5?qgc82+AUofydNErC@48=TBzbwDS$iOAPiz+xlZ3254x-vwxg!p4HOD_r9cw_}&VNE<}lHW(_2<-a`n zV8&fID^}e2=7_}Wc_c}ARj!8l=BB;W%Ni+y2s`q3^1*NRB9d6LN+5#+zLp4K^xUNo zO(>!?HaCh7XvZ{Y@c3DkXOTp8PJbbivCVv5)z8EsA<(Q*TsSFjdkZnx$MdlR%O>i> zOCHZN9^x5JLrzi3k+t99h1GV`nQr8ft*)w+RQPQvmaAH2 zvzT?`($Rz$k-!aEPUddSwx5r~xOL8g6jh#eSKaMf9?_ZYBs!d;*qV>dS(2q;WM?yE z@r*m`ltxo#aMi7zMn@@@j`$~6rQ~U)koDFgh!`;GYXsw1**N#6Vw(3ifEcxLQ4CU{ zYG^hk?}Cyy)H=t;W^y*xespFL4(p1b3SJWaYHHnnK%AGLz2J|#?b=9@pn9k9{=*-}~oXV^C?ZMdHdZT%GDn6;uLp8)T8F$nBaEGR`$aiRJ6Wfcj^ zq&R8{RCUAK2$NcZx$n-HbU6%kG+NuWH5c1anyX5O8s<~6oYsi}hie8i&a~`C&yz&M z^d{CA*cnbjfDYsw+DE+IVvR%b)o-;A)^@#=*k&@bF6 zRAiHRVjVjfWo94Zs?CU7xg6s;uq(E~gzv-lRq(DGa5eTvloUrP%Dx5HVSAk+OBB`7 z=2MI*)si%ChkWV{aB&vbP1%zGKe6KPFitJ&`Xt{%hs||Pht73wfs#a^#8|G1um=SW z&-fMr{7YA>31c}Zco(C!D#9ETIP_yJMkfGItHbf`0FW-ObyWc=JG^#jLT5zQsd#e?NGR-~WJMeg7E~f0w;3RS-IlL5Y27 z>Xl5*!Z-sCM^75jVSn`Mt}7y^qhOfT)je;7-aYbK zf)n3H0&+r1GbNW~7-??>?IMd$N>Yl~gDh4`a|jng*sM2;&7Rw(`)#au)v4k4h~90Q zvW_+9DEzPiUia5fwn*tI&C1-8-?E9j?Ij zyxyXnTT#O%%RNKdwm{bz1kOUzNX&_6Ezxz@9ZC9mIruMw2U(iFsN^0Ipf-f|&6ZR# z+#{K5dvoSqT*WSbT702hNHqk)BJC64!K#?xT*!>b_<(NgZIb`G;}saiR}qCosDv0e z*138VP6Bk2yq6dx$-M;<;+DliGd;GY@g#Qz@N#P%>PJYrWFI1-y*mV(6}+)wS^w=n zehhZ+CJR!2WKUxOzKIxTrDXfdzgmvPixsCeaH{$DUYMpbc8VxOqugI+1}6h^N-^%#OXTSW&P-utuTGAllh z+vN1%AQt*7yHaJa$_Sb`tD^JY{xr*eQG%M|o zDlMUTKlM)4DbbJOl6i4RX<1Cn4KorNp7U9E9j{SeM1l>x3q~f*8>_1Z!|utMz)!n_L8t&!;B(1#F-Tfm6?&fHMX3g%&#bxg8OKGRe>v{DA?vy&Tz#S`w(ifgh zW9_Dt$`d0GT*bBHlZ4;D_m?3sMZ{!&eJ(x`LSw$g$$Pm-G4%e@JHTkn57yFA5v z#NMLiQqK4Ju6&bDFbX3f3_(m$JC2D=#izF;LLYJi;<0fD9@q%Q0BrnNAF);{(pa*A zu8Z_Sl-qlcm;`<#3``8l1&lo9$7j_9JyqTFy~IN1b{QFEf) z1Wb7e>-Gew5so0!fqNjm?yECC)1c8p_;DodD9LebfB^B6n9U(R)|S*Zcgq?gSS)TTZFH_ijhdL z=F|1h%CJKrqEC0V;-EPj_9xKE#6QN^9co3_nbz} z+#uv*C9cg`mv07u)_;9=Pd-~+66MT%!j*ZB9F}4E7O^>KTHWIVMy=o=asNR#RICIYxAeeBT5Uj)a==u(+O!oEl-x-7d?K4g>wWMN&SC>J2BVGGnZ0 z3%NdaCY3}vMS%WnOr?dSVh(T;#N2S{x%3h4*;gK{CoLDjHdyy|19DB zoEfSN*sgG(cmllyV-JODnS2-pbG;?yML;SF$FWu-=+vFhEAg=>_=!iN zC-Nd-Nto8Wa`4)*L|s3P&r9(kmxaV|M-ej80fFfz$#G|b@#Imja|NYjU7^zTjpi?Z zA$+uc5EL(d%R)60-Nq%-SUdC7^t+M#!b`5m+AdFDG$l@ElhMIUry_;lO+;OM1DR;VN_)Ow(WKkOf`hepYB z%35CE*k*!ALGzapfP~pbca0*5fqlhR#EpG*R-^&_szYT=W6Ue5Xs#CU`iK=qazJ^O zp_=(58}wYDF?MCoGMxTf^TBYycc}7;0*67oy6&><^Iee-kfg2k7X|U|QNsYiF9=(C zk&Isy+y zTW^kUez<1qq8C~G-ZgNodw2{f+!LxBP*}2PPG1p;j@j5S=Q0IkA_U?DGLmp<{3SWC z_$0C=b%qehtTOrnb1>~x>sLc<2+>^&Cqp=#vTvsJ)(fa&zX{dqm$G5pOgO}xgLPug zsU0LHh?{c*CSWJDKZYox2dLF1-i}3FB;l@G9N^8{yUjSvmp0JejZuc0? zZa;AXa}rv4CKosiPX-RDV}T=xCHZmzFTgE*E6nM0D26gcay^#5$Z<_QA-jVCjZ0)e z>Yd4yN$xUzYntmR{)0+o*F48ZE`{3Exj%Z@++h^MTW<|ydJI4);4J86>DhX%3XL7$ep0-^R;}k>T1=)&YYLw#Su2_ zSKsj7-k0kk#MxgtKOpr)zd*#r<5->S$VEzO%jf)Tg=D$QFU)Ztnjb1N=80ajuO(e# zF=nMgc_8Krx0Y4qs&{X_OraV6ZTCZL;O!YVM&RlYFI6nhR^2&O*rLc@Ds$ztif)Q? zhV}&IRPjy*(%Uwu%FJ8zug~9lQhD(VyV|P;@=2U|DZnW67PY44C2f3mePQ|PUE97I z$K0!Mz1(}D6U%M<9@$SHak(R_!*?|uiooXiKGXk9&MOrb^9cuTCLsEsnO}KVh!7Tb6Q#! zAEGV-2VLZpXz`z^7(0N&_2!|NlplF|)_Xy8tx$fZLRD3cSRR_+*RrvdvvLkeRPaYp z!xlb?X4-p`La-YXxsxkn;3(J4oUnHrh&QyCw9yvB2Ht=uPw?K~7J)ARH0qdPZ|q`t z)$Sx7N6bZzQV-cV~2u%q_$Bt1X@!MdwwFjoU$7N6Peh<`=`sbsXyGnc14LvZ zaXM2p#@9!hvw6g4{TZL}##}^g_&^V=%X&W5>S2o4JrF+XN&>r8^Q(b^Z{TEpMTfc z|8~5iiGkCvj*scLoCS7#OU46{wUs>cNGl4h(J;xpy)2Q*1+B1zkSG#Uwn35BV&wPo zRBWH6P|8;a%c;bH$$439Vp)vF){y;iR1z`KZ%vgj_AYi$bqAtQ{Av6_^WY{5ThnT_ z*k4&ybKSQ4ozbslYCD9y+T`g`RY8qgip{Dg$iRqfP(DWM^7%Y*q>%K=mZnlk- zPTi=S2XuX1Ul*Zo-v`kJ!4eN_6UA?BDn3?jEfLzh#P_CIdTzeb3Ax&C_E)XS%5$x(JmW=F;6q3=iuVXU$OXgs6A&M%Ox+P94CG(3 z%EpUjKHyj*L;Dj}C$D7oy2;tOR7VHyErXldR*>~t4_;0~4p*M-WVoMyC+yjQBiT7$ zEvXm7S=m7`lcx3;`SKDLDx$9{pyOq^hLoRgj=4k^P6+*Yy1c)m#+7bgb36paEzRv~ zjWDxy#!(pe$o999M!Z?O<}XQ*yR+T!>Axig47`s-x(R;KP}g zzJndnNgRQ!vCK#IGjX+F@h8$jaVLjdM|*jE&2miL?+k|*+l)zML6^z@b{8QbB_4_t zO2y9$S_j{+jW(@w`PS8z1%9=6-W)@E<@=7|mn6XQcl%Rl25#vT*^4{CD*|=s+YJBY z1Oh3DMhMNL1RBsULe9tb7YE}-{JGgBGll9O_X_+%a_KoHQs5-1VT>Oh#?J02N^*w- z_32n$vq;)~yl?@+u5j+o#M|pbqs$BGx1cm)SQ(|S1u|hezf}f~k$Cp7 zXWzNa4MDwYz@KL^A{?+S3fzPDcso(;he_T$1Va{Gp1Z%qK&MoZMD0pK$=pPxW-0L(?6T}zaudckmfKgDPf33F` z^#;jJ=fIGZlQJhE_Fxn_`ZZiDC|eS?HzvC0^astrDn$8{z%l9r{50Y4Wl5{|No_Xx z!4+vk)qMVwfj4>EW9o@_MG#S?oV4wJZwO@##acZ91kv%==ax+*8VVy^3?`ZyCH;6J z3^*E%Y}O--Y)6Ny^DiH}4?y`|5Cr&TZ2F%%i2g(e4J_?->`kmJe`KsB&Rgzqpme8e zFB13hN(sCYmF+;ah&Qdtqp{y2+*Zf!hF}Owts#KpopD;U>4C0>`E)PGd+%oG zDV>Srq{YrzH6cVz?X2teveW`KOjz6{Zpy1!(zU{ZD%3eBVTr7wPH0+7q-74HLjXWBcFDd(#<^lxkxFnMWDz{oaYWSdHa)Dj(Z7+z73$ptZgJP{E}5qE?BbU(Vs zp^|?`;^PtEOsx5l7Gm8(v-~n~WG~A?@5mQf&I0{qc}}yDG!|sO%K>P9J3Jrh2p3d@senvzOw{mn&@6MX`^&{qPOnyBMl%F`_-~E z*o9ea3OsW#Eq(UUG*VCP_2tk)7n+D0KSBf1xrdPz=E4g{^uUT;feq*AsX9%R1fS6W zy+-yM%~>Z+DwT|=ZLG>+mL>yz`@pYr_SAH>h6+;4{wF2O*$YxcacxDh(}4J66`#;g za6DZzs-#E9>UFPLInO>*1Ot?cQms}U%egeSo_UTJj&`aDR~Tv@w;Ss~Huz5S+wrM=VXBGJcIM*3&T9eIe_S(!`& zEL9|itiZ4|ia2AEG^Ji1DVH@%I=LqgrEl1vo`bul#i<^=bo2sJSC~0s{i48mX#@kD zZOi+;x`-R49xTY|Ln3H8{Rg=M zN9O7QhO4ao-DM+Ikxne&IhzHVDSPTL^f#4d^00k1F2Y(X#bLWHuXgo$>JM)`2Sabg z7`7Gzo2E{on`XN|^1(LURBVPlFx{~*uoC3uMdj*KfIUmZQP`)xR8kie|17?-1;{P=1Ht&D(v$X9Q2S>&7%5-%ZKkHx#p zp}>Y+8+-J6wrBj_MF?b2hw#j>EZM%M7WfR-k(2n&R)p11f`i6b?V-+!tb$fOaI7h?6 z`CsGef6KJ~ylr@#CO;h>e#Dtd#)hTa8PTDZfK_Va7QTQ{qjf9+60CJ}(Vs6(5dd^& ztIqJ>E-XMfn8T}lo-%l-@oI7G-{`k8ispr)?=><_347A(N#gtC8!Ni%McgU($)0yDX1p?30%_%t|I1v0xTipeGT$ypD z9E=_Fg^O5J))V21+>4{We`=R#FsY(Kd=)j#d1h+}PiI315P*Rc&kgHXw2lHW=M3V# z5X3zv_iTa)f-WU+7z_N2gC+~PhLjn0jZ*i%8S%3G@&$A-q6q2U`#D5d9kq>11X^tA z2$88IrUN~xKz$dqHTojdTEnx-1L&5LZ@hOoO&?UCbsTBe?x`SUb(q-JzPT65+DhWX ztr1zzG)b@IHhh;+z5Q`}LRHN+!UP| zc(E=R{`4ibU7jtuV3q1;7P(tnif&b*6DfVg75GdEY{? zQi{6CqOMA}C6PITVv5=I7P=;zeHZRck%HO9A@>|R0KPtbg33k^XKm-yg}0EE8cBX; zt}iPD7>E*fD;%n~BpA{A-O4DZM7SY@?;uT~Jgf6ep~kV*ds*4}kQxz(Cnn;8qb9o>tk{C-Y7V0c|}S??s;RtgyLpL!P0mgD20* zhzV6Lz!uIijq#JSiitd@7R#=)hZ9MXHqYKh4`oe_LLHf=9F-jTu?7zPz}X=N(5>fx-)pD|T3Oly2QRI6F4hKi8jrJ^f%pO7{U$

    r{h$9ft7*Nbvw!yS^AoVzK8_u`OSsJmVmdN zYHX{IM1VHQmqujW@adTo$lcmpS1^`opEW{x*?o0YIg;=FY1M8 zG-{SRs*&0ktEzjp8P9)8sxe!Ai^jxu5#dmF1 z+~4vRMS{L~s|6jazl&V~FUV5HpE#^}yNrRATja_hl-EoS8rbXQ{@hXA%VX5yGBbpU zBBZ1}{pQPU{wtK8gK(4MAL_Nsm|ao_94%}RA3t|cFmw=f5EvL3kSh5W#Uk37;Z)%D zpbU(QJx*+YKdi0o8SIS>EDU}={s@BoiJ*d)Jdy!KF#89>BCz!jgda7R{zQ0M5T5uq z0&T!S{Sm>^%HF{4zsopJe)ub)r#00d3oQORz5wrh{sH$>GyVkqp&@?~eOhk$QS5&m z-Dppc<^Nit{ZFv`C&h{%D>VN)ps@Z4_JrjR_+RnJKXIQ{p8UNxF|dE){_Dn@$M5-{ za8D~Y{@$Azgnxni$@9r8{5#R7wY+{OS_bUg$Is~r%x^n{{(2StJJAdBzrg*2=&y2M ze|Evs>LkAtg`)Ti@)OoSYLWa2_wPi%0aMKX@xK2-^jCqCKaroBrT+B>#f^gy;`Xgg@c_ooMvCzrg*2=r5y%KarpA?fji+ zrovy4pAh}Alk-oweJZs+8t`!TTrGe^;lcc_zPq{2Uff zp#M85`9bwbR>_~8`ZS~BcNx8M_zT<tnwEOWgd=)1QWlexFb)`2BkKPXj}LB0LRu{65RH{O}Xu z=g08Z0MnmfPoo8o5t3iW-pAkKC4Zjy^kxy^*IB@4*Fis7{5Q1l=b2A$H-7&FSVMtJ c6aT!md@BJ7%+Ln`p#uJk0lpS(_~TFi7c}=W)Bpeg literal 0 HcmV?d00001 diff --git a/src/test/resources/reader/ppt/fake-power-point.pptx b/src/test/resources/reader/ppt/fake-power-point.pptx new file mode 100755 index 0000000000000000000000000000000000000000..01d84494896d575c52aacc04646eae6b2b724ad5 GIT binary patch literal 38412 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 aANPT>5@3L`cpxBhz?TBxy>dgIp8bEqD_2zj literal 0 HcmV?d00001 diff --git a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala index b1c72ce97de59d..df4530a3b91d35 100644 --- a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.johnsnowlabs.reader import com.johnsnowlabs.tags.FastTest @@ -20,7 +36,7 @@ class ExcelReaderTest extends AnyFlatSpec { "ExcelReader" should "read a directory of excel files" taggedAs FastTest in { val excelReader = new ExcelReader() val excelDf = excelReader.xls(docDirectory) - excelDf.select("xls") show (false) + excelDf.select("xls").show(false) assert(!excelDf.select(col("xls").getItem(0)).isEmpty) assert(!excelDf.columns.contains("content")) diff --git a/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala new file mode 100644 index 00000000000000..191e341c5a8a75 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala @@ -0,0 +1,51 @@ +/* + * Copyright 2017-2024 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.johnsnowlabs.reader + +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.sql.functions.col +import org.scalatest.flatspec.AnyFlatSpec + +class PowerPointTest extends AnyFlatSpec { + + val docDirectory = "src/test/resources/reader/ppt" + + "PowerPointReader" should "read a power point file" taggedAs FastTest in { + val powerPointReader = new PowerPointReader() + val pptDf = powerPointReader.ppt(s"$docDirectory/fake-power-point.pptx") + pptDf.select("ppt").show(false) + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + } + + "PowerPointReader" should "read a power point directory" taggedAs FastTest in { + val powerPointReader = new PowerPointReader() + val pptDf = powerPointReader.ppt(s"$docDirectory") + pptDf.select("ppt").show(false) + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + } + + "PowerPointReader" should "read a power point file with table" taggedAs FastTest in { + val powerPointReader = new PowerPointReader() + val pptDf = powerPointReader.ppt(s"$docDirectory/fake-power-point-table.pptx") + pptDf.select("ppt").show(false) + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + } + +} From 6dc756e082eb1d62e5517bc9036fb62b6def4391 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Tue, 24 Dec 2024 16:52:23 -0500 Subject: [PATCH 081/108] [SPARKNLP-1103] Adding documentation and notebook example for PowerPoint reader --- .../SparkNLP_PowerPoint_Reader_Demo.ipynb | 206 ++++++++++++++++++ .../johnsnowlabs/reader/SparkNLPReader.scala | 43 ++++ 2 files changed, 249 insertions(+) create mode 100644 examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb diff --git a/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb new file mode 100644 index 00000000000000..2f5d9938929ad5 --- /dev/null +++ b/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb @@ -0,0 +1,206 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Introducing PowerPoint reader in SparkNLP\n", + "This notebook showcases the newly added `sparknlp.read().ppt()` method in Spark NLP that parses Excel content from both local files and both local and distributed file systems into a Spark DataFrame." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading html files was introduced in Spark NLP 5.5.2. Please make sure you have upgraded to the latest Spark NLP release." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For local files example we will download a couple of HTML files from Spark NLP Github repo:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ya8qZe00dalC", + "outputId": "9992a6c7-fe25-47be-b49b-03ed1ebf865a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2024-12-24 15:23:15-- https://raw.githubusercontent.com/Unstructured-IO/unstructured/main/example-docs/fake-power-point.pptx\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 38412 (38K) [application/octet-stream]\n", + "Saving to: โ€˜power-point-files/fake-power-point.pptxโ€™\n", + "\n", + "\r", + "fake-power-point.pp 0%[ ] 0 --.-KB/s \r", + "fake-power-point.pp 100%[===================>] 37.51K --.-KB/s in 0.01s \n", + "\n", + "2024-12-24 15:23:15 (3.31 MB/s) - โ€˜power-point-files/fake-power-point.pptxโ€™ saved [38412/38412]\n", + "\n" + ] + } + ], + "source": [ + "!mkdir power-point-files\n", + "!!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/ppt/fake-power-point.pptx -P power-point-files" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EoFI66NAdalE" + }, + "source": [ + "## Parsing PowerPoint slides from Local Files\n", + "Use the `ppt()` method to parse Excel content from local directories." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bAkMjJ1vdalE", + "outputId": "6991a101-6177-4b4c-aeb6-cb2550b48dd0" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| content| ppt|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/pow...|[50 4B 03 04 14 0...|[{Title, Adding a...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "ppt_df = sparknlp.read().ppt(\"./power-point-files\")\n", + "\n", + "ppt_df.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "VWbUgoVQrO8m", + "outputId": "d88323f8-d174-48a3-bb1d-cf795bbf64a6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- path: string (nullable = true)\n", + " |-- content: binary (nullable = true)\n", + " |-- ppt: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- elementType: string (nullable = true)\n", + " | | |-- content: string (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + "\n" + ] + } + ], + "source": [ + "ppt_df.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BB2FEfegGuxl" + }, + "source": [ + "You can also use DFS file systems like:\n", + "- Databricks: `dbfs://`\n", + "- HDFS: `hdfs://`\n", + "- Microsoft Fabric OneLake: `abfss://`" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index daa9ceaa8036f6..58d61d5fbabe66 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -261,6 +261,49 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM params.asScala.getOrElse("cellSeparator", "\t") } + /** Instantiates class to read PowerPoint files. + * + * docPath: this is a path to a directory of Excel files or a path to an HTML file E.g. + * "path/power-point/files" + * + * ==Example== + * {{{ + * val docsPath = "home/user/power-point-directory" + * val sparkNLPReader = new SparkNLPReader() + * val pptDf = sparkNLPReader.ppt(docsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val pptDf = SparkNLP.read.ppt(docsPath) + * }}} + * + * {{{ + * xlsDf.select("ppt").show(false) + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |ppt | + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[{Title, Adding a Bullet Slide, {}}, {ListItem, โ€ข Find the bullet slide layout, {}}, {ListItem, โ€“ Use _TextFrame.text for first bullet, {}}, {ListItem, โ€ข Use _TextFrame.add_paragraph() for subsequent bullets, {}}, {NarrativeText, Here is a lot of text!, {}}, {NarrativeText, Here is some text in a text box!, {}}]| + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * + * pptDf.printSchema() + * root + * |-- path: string (nullable = true) + * |-- content: binary (nullable = true) + * |-- ppt: array (nullable = true) + * | |-- element: struct (containsNull = true) + * | | |-- elementType: string (nullable = true) + * | | |-- content: string (nullable = true) + * | | |-- metadata: map (nullable = true) + * | | | |-- key: string + * | | | |-- value: string (valueContainsNull = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ + def ppt(docPath: String): DataFrame = { val powerPointReader = new PowerPointReader() powerPointReader.ppt(docPath) From 26e023e01f822973f3393f3a81ab6119b6ae400a Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Thu, 6 Mar 2025 12:05:18 -0500 Subject: [PATCH 082/108] [SPARKNLP-1117] Adding storeContent param --- .../SparkNLP_PowerPoint_Reader_Demo.ipynb | 131 +++++++++++++----- python/sparknlp/reader/sparknlp_reader.py | 50 +++++-- .../reader/PowerPointReader.scala | 6 +- .../johnsnowlabs/reader/SparkNLPReader.scala | 84 +++++------ .../johnsnowlabs/reader/PowerPointTest.scala | 12 ++ 5 files changed, 192 insertions(+), 91 deletions(-) diff --git a/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb index 2f5d9938929ad5..eee30d5d46fc7a 100644 --- a/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb @@ -2,7 +2,9 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "H_ssGnSHQytt" + }, "source": [ "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", "\n", @@ -33,7 +35,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "UYkjwyv7Qyt2" + }, "source": [ "- Let's install and setup Spark NLP in Google Colab\n", "- This part is pretty easy via our simple script" @@ -41,8 +45,10 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 1, + "metadata": { + "id": "oRvzXqEFQyt3" + }, "outputs": [], "source": [ "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" @@ -50,45 +56,50 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "3YoyZLVYQyt4" + }, "source": [ "For local files example we will download a couple of HTML files from Spark NLP Github repo:" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ya8qZe00dalC", - "outputId": "9992a6c7-fe25-47be-b49b-03ed1ebf865a" + "outputId": "8c76ad45-1102-4f7e-d18e-35df54b51265" }, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "--2024-12-24 15:23:15-- https://raw.githubusercontent.com/Unstructured-IO/unstructured/main/example-docs/fake-power-point.pptx\n", - "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", - "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", - "HTTP request sent, awaiting response... 200 OK\n", - "Length: 38412 (38K) [application/octet-stream]\n", - "Saving to: โ€˜power-point-files/fake-power-point.pptxโ€™\n", - "\n", - "\r", - "fake-power-point.pp 0%[ ] 0 --.-KB/s \r", - "fake-power-point.pp 100%[===================>] 37.51K --.-KB/s in 0.01s \n", - "\n", - "2024-12-24 15:23:15 (3.31 MB/s) - โ€˜power-point-files/fake-power-point.pptxโ€™ saved [38412/38412]\n", - "\n" - ] + "data": { + "text/plain": [ + "['--2025-03-06 17:00:19-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1103-Adding-support-to-read-PowerPoint-files/src/test/resources/reader/ppt/fake-power-point.pptx',\n", + " 'Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.111.133, ...',\n", + " 'Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.',\n", + " 'HTTP request sent, awaiting response... 200 OK',\n", + " 'Length: 38412 (38K) [application/octet-stream]',\n", + " 'Saving to: โ€˜power-point-files/fake-power-point.pptxโ€™',\n", + " '',\n", + " '',\n", + " 'fake-power-point.pp 0%[ ] 0 --.-KB/s ',\n", + " 'fake-power-point.pp 100%[===================>] 37.51K --.-KB/s in 0.004s ',\n", + " '',\n", + " '2025-03-06 17:00:19 (9.90 MB/s) - โ€˜power-point-files/fake-power-point.pptxโ€™ saved [38412/38412]',\n", + " '']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "!mkdir power-point-files\n", - "!!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/ppt/fake-power-point.pptx -P power-point-files" + "!!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1103-Adding-support-to-read-PowerPoint-files/src/test/resources/reader/ppt/fake-power-point.pptx -P power-point-files" ] }, { @@ -103,13 +114,13 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "bAkMjJ1vdalE", - "outputId": "6991a101-6177-4b4c-aeb6-cb2550b48dd0" + "outputId": "d8391d2f-17b8-495d-bbba-03ef73db3bd2" }, "outputs": [ { @@ -117,31 +128,31 @@ "output_type": "stream", "text": [ "Warning::Spark Session already created, some configs may not take.\n", - "+--------------------+--------------------+--------------------+\n", - "| path| content| ppt|\n", - "+--------------------+--------------------+--------------------+\n", - "|file:/content/pow...|[50 4B 03 04 14 0...|[{Title, Adding a...|\n", - "+--------------------+--------------------+--------------------+\n", + "+--------------------+--------------------+\n", + "| path| ppt|\n", + "+--------------------+--------------------+\n", + "|file:/content/pow...|[{Title, Adding a...|\n", + "+--------------------+--------------------+\n", "\n" ] } ], "source": [ "import sparknlp\n", - "ppt_df = sparknlp.read().ppt(\"./power-point-files\")\n", "\n", + "ppt_df = sparknlp.read().ppt(\"./power-point-files\")\n", "ppt_df.show()" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VWbUgoVQrO8m", - "outputId": "d88323f8-d174-48a3-bb1d-cf795bbf64a6" + "outputId": "faf985ce-92a3-4c4f-9827-70ce51081082" }, "outputs": [ { @@ -150,7 +161,6 @@ "text": [ "root\n", " |-- path: string (nullable = true)\n", - " |-- content: binary (nullable = true)\n", " |-- ppt: array (nullable = true)\n", " | |-- element: struct (containsNull = true)\n", " | | |-- elementType: string (nullable = true)\n", @@ -177,6 +187,55 @@ "- HDFS: `hdfs://`\n", "- Microsoft Fabric OneLake: `abfss://`" ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e9KEkKxERI_U" + }, + "source": [ + "### Configuration Parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VLbJsw20ROAO" + }, + "source": [ + "- `storeContent`: By default, this is set to `false`. When enabled, the output will include the byte content of the file." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "5ARg336ZROUc", + "outputId": "c26761ad-c3f2-41dd-d334-25c7f73a0726" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| ppt| content|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/pow...|[{Title, Adding a...|[50 4B 03 04 14 0...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"storeContent\": \"true\"}\n", + "ppt_df = sparknlp.read(params).ppt(\"./power-point-files\")\n", + "ppt_df.show()" + ] } ], "metadata": { diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 18aa19e87dfb86..2a3d6a902902fe 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -202,17 +202,17 @@ def xls(self, docPath): |[{Title, Financial performance, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Quarterly revenue\tNine quarters to 30 June 2023\t\t\t1.0, {SheetName -> Index}}, {NarrativeText, Group financial performance\tFY 22\tFY 23\t\t2.0, {SheetName -> Index}}, {NarrativeText, Segmental results\tFY 22\tFY 23\t\t3.0, {SheetName -> Index}}, {NarrativeText, Segmental analysis\tFY 22\tFY 23\t\t4.0, {SheetName -> Index}}, {NarrativeText, Cash flow\tFY 22\tFY 23\t\t5.0, {SheetName -> Index}}, {Title, Operational metrics, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Mobile customers\tNine quarters to 30 June 2023\t\t\t6.0, {SheetName -> Index}}, {NarrativeText, Fixed broadband customers\tNine quarters to 30 June 2023\t\t\t7.0, {SheetName -> Index}}, {NarrativeText, Marketable homes passed\tNine quarters to 30 June 2023\t\t\t8.0, {SheetName -> Index}}, {NarrativeText, TV customers\tNine quarters to 30 June 2023\t\t\t9.0, {SheetName -> Index}}, {NarrativeText, Converged customers\tNine quarters to 30 June 2023\t\t\t10.0, {SheetName -> Index}}, {NarrativeText, Mobile churn\tNine quarters to 30 June 2023\t\t\t11.0, {SheetName -> Index}}, {NarrativeText, Mobile data usage\tNine quarters to 30 June 2023\t\t\t12.0, {SheetName -> Index}}, {NarrativeText, Mobile ARPU\tNine quarters to 30 June 2023\t\t\t13.0, {SheetName -> Index}}, {Title, Other, {SheetName -> Index}}, {Title, Topic\tPeriod\t\t\tPage, {SheetName -> Index}}, {NarrativeText, Average foreign exchange rates\tNine quarters to 30 June 2023\t\t\t14.0, {SheetName -> Index}}, {NarrativeText, Guidance rates\tFY 23/24\t\t\t14.0, {SheetName -> Index}}]| +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - xlsDf.printSchema() - root - |-- path: string (nullable = true) - |-- content: binary (nullable = true) - |-- xls: array (nullable = true) - | |-- element: struct (containsNull = true) - | | |-- elementType: string (nullable = true) - | | |-- content: string (nullable = true) - | | |-- metadata: map (nullable = true) - | | | |-- key: string - | | | |-- value: string (valueContainsNull = true) + >>> xlsDf.printSchema() + root + |-- path: string (nullable = true) + |-- content: binary (nullable = true) + |-- xls: array (nullable = true) + | |-- element: struct (containsNull = true) + | | |-- elementType: string (nullable = true) + | | |-- content: string (nullable = true) + | | |-- metadata: map (nullable = true) + | | | |-- key: string + | | | |-- value: string (valueContainsNull = true) """ if not isinstance(docPath, str): raise TypeError("docPath must be a string") @@ -221,6 +221,34 @@ def xls(self, docPath): return dataframe def ppt(self, docPath): + """ + Reads power point document files and returns a Spark DataFrame. + + Parameters + ---------- + docPath : str + Path to an excel document file. + + Returns + ------- + pyspark.sql.DataFrame + A DataFrame containing parsed document content. + + Examples + -------- + >>> from sparknlp.reader import SparkNLPReader + >>> pptDf = SparkNLPReader().ppt(spark, "home/user/powerpoint-directory") + + You can use SparkNLP for one line of code + >>> import sparknlp + >>> pptDf = sparknlp.read().ppt("home/user/powerpoint-directory") + >>> pptDf.show(truncate=False) + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |ppt | + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |[{Title, Adding a Bullet Slide, {}}, {ListItem, โ€ข Find the bullet slide layout, {}}, {ListItem, โ€“ Use _TextFrame.text for first bullet, {}}, {ListItem, โ€ข Use _TextFrame.add_paragraph() for subsequent bullets, {}}, {NarrativeText, Here is a lot of text!, {}}, {NarrativeText, Here is some text in a text box!, {}}]| + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + """ if not isinstance(docPath, str): raise TypeError("docPath must be a string") jdf = self._java_obj.ppt(docPath) diff --git a/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala index 2e3818bd8fb144..e25d2666c0ae00 100644 --- a/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala @@ -26,7 +26,7 @@ import org.apache.spark.sql.functions.{col, udf} import java.io.ByteArrayInputStream import scala.collection.JavaConverters._ -class PowerPointReader extends Serializable { +class PowerPointReader(storeContent: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -38,9 +38,11 @@ class PowerPointReader extends Serializable { val byteArray = portableDataStream.toArray() (path, byteArray) } - byteArrayRDD + val powerPointDf = byteArrayRDD .toDF("path", "content") .withColumn("ppt", parsePowerPointUDF(col("content"))) + if (storeContent) powerPointDf.select("path", "ppt", "content") + else powerPointDf.select("path", "ppt") } else throw new IllegalArgumentException(s"Invalid filePath: $filePath") } diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 58d61d5fbabe66..a723e24713ac78 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -262,50 +262,50 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM } /** Instantiates class to read PowerPoint files. - * - * docPath: this is a path to a directory of Excel files or a path to an HTML file E.g. - * "path/power-point/files" - * - * ==Example== - * {{{ - * val docsPath = "home/user/power-point-directory" - * val sparkNLPReader = new SparkNLPReader() - * val pptDf = sparkNLPReader.ppt(docsPath) - * }}} - * - * ==Example 2== - * You can use SparkNLP for one line of code - * {{{ - * val pptDf = SparkNLP.read.ppt(docsPath) - * }}} - * - * {{{ - * xlsDf.select("ppt").show(false) - * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - * |ppt | - * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - * |[{Title, Adding a Bullet Slide, {}}, {ListItem, โ€ข Find the bullet slide layout, {}}, {ListItem, โ€“ Use _TextFrame.text for first bullet, {}}, {ListItem, โ€ข Use _TextFrame.add_paragraph() for subsequent bullets, {}}, {NarrativeText, Here is a lot of text!, {}}, {NarrativeText, Here is some text in a text box!, {}}]| - * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - * - * pptDf.printSchema() - * root - * |-- path: string (nullable = true) - * |-- content: binary (nullable = true) - * |-- ppt: array (nullable = true) - * | |-- element: struct (containsNull = true) - * | | |-- elementType: string (nullable = true) - * | | |-- content: string (nullable = true) - * | | |-- metadata: map (nullable = true) - * | | | |-- key: string - * | | | |-- value: string (valueContainsNull = true) - * }}} - * - * @param params - * Parameter with custom configuration - */ + * + * docPath: this is a path to a directory of Excel files or a path to an HTML file E.g. + * "path/power-point/files" + * + * ==Example== + * {{{ + * val docsPath = "home/user/power-point-directory" + * val sparkNLPReader = new SparkNLPReader() + * val pptDf = sparkNLPReader.ppt(docsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val pptDf = SparkNLP.read.ppt(docsPath) + * }}} + * + * {{{ + * xlsDf.select("ppt").show(false) + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |ppt | + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[{Title, Adding a Bullet Slide, {}}, {ListItem, โ€ข Find the bullet slide layout, {}}, {ListItem, โ€“ Use _TextFrame.text for first bullet, {}}, {ListItem, โ€ข Use _TextFrame.add_paragraph() for subsequent bullets, {}}, {NarrativeText, Here is a lot of text!, {}}, {NarrativeText, Here is some text in a text box!, {}}]| + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * + * pptDf.printSchema() + * root + * |-- path: string (nullable = true) + * |-- content: binary (nullable = true) + * |-- ppt: array (nullable = true) + * | |-- element: struct (containsNull = true) + * | | |-- elementType: string (nullable = true) + * | | |-- content: string (nullable = true) + * | | |-- metadata: map (nullable = true) + * | | | |-- key: string + * | | | |-- value: string (valueContainsNull = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ def ppt(docPath: String): DataFrame = { - val powerPointReader = new PowerPointReader() + val powerPointReader = new PowerPointReader(getStoreContent) powerPointReader.ppt(docPath) } diff --git a/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala index 191e341c5a8a75..2b3c0e13abb597 100644 --- a/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala @@ -30,6 +30,7 @@ class PowerPointTest extends AnyFlatSpec { pptDf.select("ppt").show(false) assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + assert(!pptDf.columns.contains("content")) } "PowerPointReader" should "read a power point directory" taggedAs FastTest in { @@ -38,6 +39,7 @@ class PowerPointTest extends AnyFlatSpec { pptDf.select("ppt").show(false) assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + assert(!pptDf.columns.contains("content")) } "PowerPointReader" should "read a power point file with table" taggedAs FastTest in { @@ -46,6 +48,16 @@ class PowerPointTest extends AnyFlatSpec { pptDf.select("ppt").show(false) assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + assert(!pptDf.columns.contains("content")) + } + + "PowerPointReader" should "store content" taggedAs FastTest in { + val powerPointReader = new PowerPointReader(storeContent = true) + val pptDf = powerPointReader.ppt(docDirectory) + pptDf.show() + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + assert(pptDf.columns.contains("content")) } } From 87100117dbfd5f4533b22cc7a61f91bed0719e10 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 17 Feb 2025 14:14:11 -0500 Subject: [PATCH 083/108] [SPARKNLP-1113] Adding Text Reader --- python/sparknlp/reader/sparknlp_reader.py | 21 +++- python/test/sparknlp_test.py | 15 ++- .../johnsnowlabs/reader/SparkNLPReader.scala | 58 +++++++++ .../com/johnsnowlabs/reader/TextReader.scala | 110 ++++++++++++++++++ src/test/resources/reader/txt/simple-text.txt | 9 ++ .../johnsnowlabs/reader/TextReaderTest.scala | 34 ++++++ 6 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/reader/TextReader.scala create mode 100644 src/test/resources/reader/txt/simple-text.txt create mode 100644 src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 2a3d6a902902fe..755a410dd60ffc 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -253,4 +253,23 @@ def ppt(self, docPath): raise TypeError("docPath must be a string") jdf = self._java_obj.ppt(docPath) dataframe = self.getDataFrame(self.spark, jdf) - return dataframe \ No newline at end of file + return dataframe + return self.getDataFrame(self.spark, jdf) + + def txt(self, docPath): + """Reads TXT files and returns a Spark DataFrame. + + Parameters + ---------- + docPath : str + Path to a TXT file. + + Returns + ------- + pyspark.sql.DataFrame + A DataFrame containing parsed document content. + """ + if not isinstance(docPath, str): + raise TypeError("docPath must be a string") + jdf = self._java_obj.txt(docPath) + return self.getDataFrame(self.spark, jdf) \ No newline at end of file diff --git a/python/test/sparknlp_test.py b/python/test/sparknlp_test.py index 8db39446d6d98f..bc543d5a687c6a 100644 --- a/python/test/sparknlp_test.py +++ b/python/test/sparknlp_test.py @@ -112,4 +112,17 @@ def runTest(self): excel_df = sparknlp.read().ppt(self.excel_file) excel_df.show() - self.assertTrue(excel_df.select("ppt").count() > 0) \ No newline at end of file + self.assertTrue(excel_df.select("ppt").count() > 0) + +@pytest.mark.fast +class SparkNLPTestTXTFilesSpec(unittest.TestCase): + + def setUp(self): + self.data = SparkContextForTest.data + self.txt_file = f"file:///{os.getcwd()}/../src/test/resources/reader/txt/simple-text.txt" + + def runTest(self): + txt_df = sparknlp.read().txt(self.txt_file) + txt_df.show() + + self.assertTrue(txt_df.select("txt").count() > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index a723e24713ac78..978f8918262ef9 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -309,4 +309,62 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM powerPointReader.ppt(docPath) } + /** Instantiates class to read txt files. + * + * filePath: this is a path to a directory of TXT files or a path to an TXT file E.g. + * "path/txt/files" + * + * ==Example== + * {{{ + * val filePath = "home/user/txt/files" + * val sparkNLPReader = new SparkNLPReader() + * val txtDf = sparkNLPReader.txt(filePath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val txtDf = SparkNLP.read.txt(filePath) + * }}} + * + * {{{ + * txtDf.select("txt").show(false) + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |txt | + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * |[{Title, BIG DATA ANALYTICS, {paragraph -> 0}}, {NarrativeText, Apache Spark is a fast and general-purpose cluster computing system.\nIt provides high-level APIs in Java, Scala, Python, and R., {paragraph -> 0}}, {Title, MACHINE LEARNING, {paragraph -> 1}}, {NarrativeText, Spark's MLlib provides scalable machine learning algorithms.\nIt includes tools for classification, regression, clustering, and more., {paragraph -> 1}}]| + * +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + * + * emailDf.printSchema() + * root + * |-- path: string (nullable = true) + * |-- content: binary (nullable = true) + * |-- txt: array (nullable = true) + * | |-- element: struct (containsNull = true) + * | | |-- elementType: string (nullable = true) + * | | |-- content: string (nullable = true) + * | | |-- metadata: map (nullable = true) + * | | | |-- key: string + * | | | |-- value: string (valueContainsNull = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ + def txt(filePath: String): DataFrame = { + val textReader = new TextReader(getTitleLengthSize) + textReader.txt(filePath) + } + + private def getTitleLengthSize: Int = { + val titleLengthSize = + try { + params.asScala.getOrElse("titleLengthSize", "50").toInt + } catch { + case _: IllegalArgumentException => 50 + } + + titleLengthSize + } + } diff --git a/src/main/scala/com/johnsnowlabs/reader/TextReader.scala b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala new file mode 100644 index 00000000000000..6b17d774f12864 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala @@ -0,0 +1,110 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.udf + +import scala.collection.mutable + +class TextReader(titleLengthSize: Int = 50) extends Serializable { + + private val spark = ResourceHelper.spark + import spark.implicits._ + + /** Parses TXT files and returns a DataFrame. + * + * The DataFrame will contain: + * - "path": the file path, + * - "content": the raw text content, + * - "txt": a Seq[HTMLElement] containing the parsed elements. + */ + def txt(filePath: String): DataFrame = { + if (ResourceHelper.validFile(filePath)) { + val textFilesRDD = spark.sparkContext.wholeTextFiles(filePath) + textFilesRDD + .toDF("path", "content") + .withColumn("txt", parseTxtUDF($"content")) + } else { + throw new IllegalArgumentException(s"Invalid filePath: $filePath") + } + } + + private val parseTxtUDF = udf((text: String) => parseTxt(text)) + + /** Parses the given text into a sequence of HTMLElements. + * + * Parsing logic: + * - Split the text into blocks using a delimiter of two or more consecutive newlines. + * - Using heuristics, consider a block a title if it is all uppercase and short. + * - If a block is a title candidate and the following block exists and is not a title + * candidate, treat the first as the Title and the second as its NarrativeText. + * - Otherwise, treat blocks as narrative text. + * - Omit any element with empty content. + */ + private def parseTxt(text: String): Seq[HTMLElement] = { + val blocks = text.split("\\n\\n+").map(_.trim).filter(_.nonEmpty) + val elements = mutable.ArrayBuffer[HTMLElement]() + var i = 0 + while (i < blocks.length) { + val currentBlock = blocks(i) + if (isTitleCandidate(currentBlock)) { + elements += HTMLElement( + "Title", + currentBlock, + mutable.Map("paragraph" -> (i / 2).toString)) + if (i + 1 < blocks.length && !isTitleCandidate(blocks(i + 1))) { + val narrative = blocks(i + 1) + if (narrative.nonEmpty) { + elements += HTMLElement( + "NarrativeText", + narrative, + mutable.Map("paragraph" -> (i / 2).toString)) + } + i += 2 + } else { + i += 1 + } + } else { + elements += HTMLElement( + "NarrativeText", + currentBlock, + mutable.Map("paragraph" -> (i / 2).toString)) + i += 1 + } + } + elements + } + + /** Heuristic function to determine if a given line/block is a title candidate. + * + * Currently, we consider a block a title candidate if: + * - It is non-empty. + * - It consists mostly of uppercase letters (ignoring non-letter characters). + * - It is relatively short (e.g., 50 characters or fewer). + */ + private def isTitleCandidate(text: String): Boolean = { + val trimmed = text.trim + if (trimmed.isEmpty) return false + val isAllUpper = trimmed.forall(c => !c.isLetter || c.isUpper) + val isTitleCase = trimmed.split("\\s+").forall(word => word.headOption.exists(_.isUpper)) + val isShort = trimmed.length <= 50 + val hasLetters = trimmed.exists(_.isLetter) + (isAllUpper || isTitleCase) && isShort && hasLetters + } + +} diff --git a/src/test/resources/reader/txt/simple-text.txt b/src/test/resources/reader/txt/simple-text.txt new file mode 100644 index 00000000000000..dfc415d8d6701e --- /dev/null +++ b/src/test/resources/reader/txt/simple-text.txt @@ -0,0 +1,9 @@ +BIG DATA ANALYTICS + +Apache Spark is a fast and general-purpose cluster computing system. +It provides high-level APIs in Java, Scala, Python, and R. + +MACHINE LEARNING + +Spark's MLlib provides scalable machine learning algorithms. +It includes tools for classification, regression, clustering, and more. diff --git a/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala new file mode 100644 index 00000000000000..86a800b8e5c871 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.sql.functions.col +import org.scalatest.flatspec.AnyFlatSpec + +class TextReaderTest extends AnyFlatSpec { + + val txtDirectory = "src/test/resources/reader/txt/" + + "Text Reader" should "read a directory of text files" taggedAs FastTest in { + val textReader = new TextReader() + val textDf = textReader.txt(s"$txtDirectory/simple-text.txt") + textDf.select("txt").show(false) + + assert(!textDf.select(col("txt").getItem(0)).isEmpty) + } + +} From 30502cc3676a6359f96f659edd7dc8b2b6ab2bd3 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 17 Feb 2025 17:00:23 -0500 Subject: [PATCH 084/108] [SPARKNLP-1113] Adding txt reader notebook example --- .../reader/SparkNLP_TXT_Reader_Demo.ipynb | 241 ++++++++++++++++++ python/sparknlp/reader/sparknlp_reader.py | 1 - 2 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb diff --git a/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb new file mode 100644 index 00000000000000..36d8d2da5bf9e9 --- /dev/null +++ b/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb @@ -0,0 +1,241 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "c0efed73-75e9-41f1-9a2e-a2d0953b3a76", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Introducing TXT reader in SparkNLP\n", + "This notebook showcases the newly added `sparknlp.read().txt()` method in Spark NLP that parses txt file content from both local files and real-time URLs into a Spark DataFrame." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "356de93e-af38-4156-823b-6371d7fd825c", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading html files was introduced in Spark NLP 5.6.0. Please make sure you have upgraded to the latest Spark NLP release.\n", + "\n", + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For local files example we will download a TXT file from Spark NLP Github repo:" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "bb622e88-2ef9-49c4-8cfb-e49209ad206a", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ya8qZe00dalC", + "outputId": "268ccacb-ba1c-4753-f251-014fb0003f38" + }, + "outputs": [], + "source": [ + "!mkdir txt-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/txt/simple-text.txt -P txt-files" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "13d72e9f-04b4-4547-bc4e-35b3878a93c2", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "EoFI66NAdalE" + }, + "source": [ + "## Parsing text from Local Files\n", + "Use the `txt()` method to parse text file content from local directories." + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "df54ed9b-682b-4b99-891a-84c23bc5cbd0", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bAkMjJ1vdalE", + "outputId": "a0a2e727-fcc3-474b-eaaa-20bf15f19773" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| content| txt|\n", + "+--------------------+--------------------+--------------------+\n", + "|dbfs:/danilo/data...|BIG DATA ANALYTIC...|[{Title, BIG DATA...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "txt_df = sparknlp.read().txt(\"dbfs:/danilo/datasets/txt\")\n", + "\n", + "txt_df.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "9f5c787d-2eab-4546-8001-e34f00124670", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "4iky1gvEz7Pt", + "outputId": "a986947b-f874-46bc-88c8-093dc42c83cb" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|txt |\n", + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{Title, BIG DATA ANALYTICS, {paragraph -> 0}}, {NarrativeText, Apache Spark is a fast and general-purpose cluster computing system.\\nIt provides high-level APIs in Java, Scala, Python, and R., {paragraph -> 0}}, {Title, MACHINE LEARNING, {paragraph -> 1}}, {NarrativeText, Spark's MLlib provides scalable machine learning algorithms.\\nIt includes tools for classification, regression, clustering, and more., {paragraph -> 1}}]|\n", + "+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "txt_df.select(\"txt\").show(truncate=False)" + ] + } + ], + "metadata": { + "application/vnd.databricks.v1+notebook": { + "computePreferences": null, + "dashboards": [], + "environmentMetadata": null, + "language": "python", + "notebookMetadata": { + "pythonIndentUnit": 4 + }, + "notebookName": "SparkNLP_TXT_Reader_Demo", + "widgets": {} + }, + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 755a410dd60ffc..4b9cea8d7aa763 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -254,7 +254,6 @@ def ppt(self, docPath): jdf = self._java_obj.ppt(docPath) dataframe = self.getDataFrame(self.spark, jdf) return dataframe - return self.getDataFrame(self.spark, jdf) def txt(self, docPath): """Reads TXT files and returns a Spark DataFrame. From cdb8f360b702ccf5615d54144a9d607a53a9be09 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Thu, 6 Mar 2025 19:39:14 -0500 Subject: [PATCH 085/108] [SPARKNLP-1117] Adding storeContent param --- .../reader/SparkNLP_TXT_Reader_Demo.ipynb | 162 ++++++++++++++++-- python/sparknlp/reader/sparknlp_reader.py | 15 ++ .../johnsnowlabs/reader/SparkNLPReader.scala | 2 +- .../com/johnsnowlabs/reader/TextReader.scala | 7 +- .../johnsnowlabs/reader/TextReaderTest.scala | 10 ++ 5 files changed, 174 insertions(+), 22 deletions(-) diff --git a/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb index 36d8d2da5bf9e9..9dbfe24aaa9a4f 100644 --- a/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb @@ -2,7 +2,9 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "0o5UQ-Gy2Xvr" + }, "source": [ "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", "\n", @@ -58,8 +60,10 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 1, + "metadata": { + "id": "xrWTskQJ2Xv5" + }, "outputs": [], "source": [ "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" @@ -67,14 +71,16 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "9B98jlOn2Xv8" + }, "source": [ "For local files example we will download a TXT file from Spark NLP Github repo:" ] }, { "cell_type": "code", - "execution_count": 0, + "execution_count": 11, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { @@ -91,12 +97,31 @@ "base_uri": "https://localhost:8080/" }, "id": "ya8qZe00dalC", - "outputId": "268ccacb-ba1c-4753-f251-014fb0003f38" + "outputId": "144186be-781d-451b-894e-d9c590a93c6a" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mkdir: cannot create directory โ€˜txt-filesโ€™: File exists\n", + "--2025-03-07 00:33:21-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1113-Adding-support-to-enhance-read-TXT-files/src/test/resources/reader/txt/simple-text.txt\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.109.133, 185.199.111.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 300 [text/plain]\n", + "Saving to: โ€˜txt-files/simple-text.txtโ€™\n", + "\n", + "simple-text.txt 100%[===================>] 300 --.-KB/s in 0s \n", + "\n", + "2025-03-07 00:33:21 (4.67 MB/s) - โ€˜txt-files/simple-text.txtโ€™ saved [300/300]\n", + "\n" + ] + } + ], "source": [ "!mkdir txt-files\n", - "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/txt/simple-text.txt -P txt-files" + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1113-Adding-support-to-enhance-read-TXT-files/src/test/resources/reader/txt/simple-text.txt -P txt-files" ] }, { @@ -122,7 +147,7 @@ }, { "cell_type": "code", - "execution_count": 0, + "execution_count": 12, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { @@ -139,7 +164,7 @@ "base_uri": "https://localhost:8080/" }, "id": "bAkMjJ1vdalE", - "outputId": "a0a2e727-fcc3-474b-eaaa-20bf15f19773" + "outputId": "74f0e218-6378-4df4-9b12-3ee6e33020e6" }, "outputs": [ { @@ -147,25 +172,25 @@ "output_type": "stream", "text": [ "Warning::Spark Session already created, some configs may not take.\n", - "+--------------------+--------------------+--------------------+\n", - "| path| content| txt|\n", - "+--------------------+--------------------+--------------------+\n", - "|dbfs:/danilo/data...|BIG DATA ANALYTIC...|[{Title, BIG DATA...|\n", - "+--------------------+--------------------+--------------------+\n", + "+--------------------+--------------------+\n", + "| path| txt|\n", + "+--------------------+--------------------+\n", + "|file:/content/txt...|[{Title, BIG DATA...|\n", + "+--------------------+--------------------+\n", "\n" ] } ], "source": [ "import sparknlp\n", - "txt_df = sparknlp.read().txt(\"dbfs:/danilo/datasets/txt\")\n", "\n", + "txt_df = sparknlp.read().txt(\"./txt-files\")\n", "txt_df.show()" ] }, { "cell_type": "code", - "execution_count": 0, + "execution_count": 13, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { @@ -182,7 +207,7 @@ "base_uri": "https://localhost:8080/" }, "id": "4iky1gvEz7Pt", - "outputId": "a986947b-f874-46bc-88c8-093dc42c83cb" + "outputId": "ead23526-18be-4bb9-e952-38ef3d483cb0" }, "outputs": [ { @@ -201,6 +226,107 @@ "source": [ "txt_df.select(\"txt\").show(truncate=False)" ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "brto-6NX2wLT" + }, + "source": [ + "You can also use DFS file systems like:\n", + "- Databricks: `dbfs://`\n", + "- HDFS: `hdfs://`\n", + "- Microsoft Fabric OneLake: `abfss://`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "CYnoVMVD211Z" + }, + "source": [ + "### Configuration Parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rJhyeem_3Gqh" + }, + "source": [ + "- `titleLengthSize`: You can customize the font size used to identify titles that should be treated as titles. By default, the font size is set to 50. However, if your text files require a different configuration, you can adjust this parameter accordingly. The example below demonstrates how to modify and work with this setting:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "nLUtWTk-3jcT", + "outputId": "60d10ba0-cf91-4706-efb4-4e640d7e6bb0" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|path |txt |\n", + "+---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|file:/content/txt-files/simple-text.txt|[{NarrativeText, BIG DATA ANALYTICS, {paragraph -> 0}}, {NarrativeText, Apache Spark is a fast and general-purpose cluster computing system.\\nIt provides high-level APIs in Java, Scala, Python, and R., {paragraph -> 0}}, {NarrativeText, MACHINE LEARNING, {paragraph -> 1}}, {NarrativeText, Spark's MLlib provides scalable machine learning algorithms.\\nIt includes tools for classification, regression, clustering, and more., {paragraph -> 1}}]|\n", + "+---------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"titleLengthSize\": \"5\"}\n", + "txt_df = sparknlp.read(params).txt(\"./txt-files\")\n", + "txt_df.show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d444S-MK239M" + }, + "source": [ + "- `storeContent`: By default, this is set to `false`. When enabled, the output will include the raw content of the file." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "optYF_SS22TW", + "outputId": "e21f8dab-ef69-432b-aa3e-fb0afc075bbb" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+--------------------+--------------------+--------------------+\n", + "| path| txt| content|\n", + "+--------------------+--------------------+--------------------+\n", + "|file:/content/txt...|[{Title, BIG DATA...|BIG DATA ANALYTIC...|\n", + "+--------------------+--------------------+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "params = {\"storeContent\": \"true\"}\n", + "txt_df = sparknlp.read(params).txt(\"./txt-files\")\n", + "txt_df.show()" + ] } ], "metadata": { diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 4b9cea8d7aa763..36917d4573093a 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -267,6 +267,21 @@ def txt(self, docPath): ------- pyspark.sql.DataFrame A DataFrame containing parsed document content. + + Examples + -------- + >>> from sparknlp.reader import SparkNLPReader + >>> txtDf = SparkNLPReader().txt(spark, "home/user/txt/files") + + You can use SparkNLP for one line of code + >>> import sparknlp + >>> txtDf = sparknlp.read().txt("home/user/txt/files") + >>> txtDf.show(truncate=False) + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |txt | + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + |[{Title, BIG DATA ANALYTICS, {paragraph -> 0}}, {NarrativeText, Apache Spark is a fast and general-purpose cluster computing system.\nIt provides high-level APIs in Java, Scala, Python, and R., {paragraph -> 0}}, {Title, MACHINE LEARNING, {paragraph -> 1}}, {NarrativeText, Spark's MLlib provides scalable machine learning algorithms.\nIt includes tools for classification, regression, clustering, and more., {paragraph -> 1}}]| + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ """ if not isinstance(docPath, str): raise TypeError("docPath must be a string") diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 978f8918262ef9..285f0584adbfee 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -352,7 +352,7 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM * Parameter with custom configuration */ def txt(filePath: String): DataFrame = { - val textReader = new TextReader(getTitleLengthSize) + val textReader = new TextReader(getTitleLengthSize, getStoreContent) textReader.txt(filePath) } diff --git a/src/main/scala/com/johnsnowlabs/reader/TextReader.scala b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala index 6b17d774f12864..d4950ebc9e6f45 100644 --- a/src/main/scala/com/johnsnowlabs/reader/TextReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala @@ -21,7 +21,7 @@ import org.apache.spark.sql.functions.udf import scala.collection.mutable -class TextReader(titleLengthSize: Int = 50) extends Serializable { +class TextReader(titleLengthSize: Int = 50, storeContent: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -36,9 +36,10 @@ class TextReader(titleLengthSize: Int = 50) extends Serializable { def txt(filePath: String): DataFrame = { if (ResourceHelper.validFile(filePath)) { val textFilesRDD = spark.sparkContext.wholeTextFiles(filePath) - textFilesRDD + val textDf = textFilesRDD .toDF("path", "content") .withColumn("txt", parseTxtUDF($"content")) + if (storeContent) textDf.select("path", "txt", "content") else textDf.select("path", "txt") } else { throw new IllegalArgumentException(s"Invalid filePath: $filePath") } @@ -102,7 +103,7 @@ class TextReader(titleLengthSize: Int = 50) extends Serializable { if (trimmed.isEmpty) return false val isAllUpper = trimmed.forall(c => !c.isLetter || c.isUpper) val isTitleCase = trimmed.split("\\s+").forall(word => word.headOption.exists(_.isUpper)) - val isShort = trimmed.length <= 50 + val isShort = trimmed.length <= titleLengthSize val hasLetters = trimmed.exists(_.isLetter) (isAllUpper || isTitleCase) && isShort && hasLetters } diff --git a/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala index 86a800b8e5c871..1e2e0c0c90cbec 100644 --- a/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala @@ -29,6 +29,16 @@ class TextReaderTest extends AnyFlatSpec { textDf.select("txt").show(false) assert(!textDf.select(col("txt").getItem(0)).isEmpty) + assert(!textDf.columns.contains("content")) + } + + "Text Reader" should "store content" taggedAs FastTest in { + val textReader = new TextReader(storeContent = true) + val textDf = textReader.txt(txtDirectory) + textDf.show() + + assert(!textDf.select(col("txt").getItem(0)).isEmpty) + assert(textDf.columns.contains("content")) } } From 19d2dd41fe4b03e1b2952858b9b1d2ef54704551 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Tue, 11 Mar 2025 04:35:08 +0000 Subject: [PATCH 086/108] added notebook --- ...gingFace_OpenVINO_in_Spark_NLP_Janus.ipynb | 1048 +++++++++++++++++ 1 file changed, 1048 insertions(+) create mode 100644 examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Janus.ipynb diff --git a/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Janus.ipynb b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Janus.ipynb new file mode 100644 index 00000000000000..50d0c7ceef1284 --- /dev/null +++ b/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Janus.ipynb @@ -0,0 +1,1048 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/openvino/HuggingFace_OpenVINO_in_Spark_NLP_Janus.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import OpenVINO Janus models from HuggingFace ๐Ÿค— into Spark NLP ๐Ÿš€\n", + "\n", + "This notebook provides a detailed walkthrough on optimizing and importing Janus models from HuggingFace for use in Spark NLP, with [Intel OpenVINO toolkit](https://www.intel.com/content/www/us/en/developer/tools/openvino-toolkit/overview.html). The focus is on converting the model to the OpenVINO format and applying precision optimizations (INT8 and INT4), to enhance the performance and efficiency on CPU platforms using [Optimum Intel](https://huggingface.co/docs/optimum/main/en/intel/inference).\n", + "\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "- OpenVINO support was introduced in `Spark NLP 5.4.0`, enabling high performance CPU inference for models. So please make sure you have upgraded to the latest Spark NLP release.\n", + "- Model quantization is a computationally expensive process, so it is recommended to use a runtime with more than 32GB memory for exporting the quantized model from HuggingFace.\n", + "- You can import Janus models via `Janus`. These models are usually under `Text Generation` category and have `Janus` in their labels.\n", + "- Reference: [Janus](https://huggingface.co/docs/transformers/model_doc/llama#transformers.Janus)\n", + "- Some [example models](https://huggingface.co/models?search=Janus)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Export and Save the HuggingFace model\n", + "\n", + "- Let's install `transformers` and `openvino` packages with other dependencies. You don't need `openvino` to be installed for Spark NLP, however, we need it to load and save models from HuggingFace.\n", + "- We lock `transformers` on version `4.41.2`. This doesn't mean it won't work with the future release, but we wanted you to know which versions have been tested successfully." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "import requests" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "%pip install -q --upgrade transformers==4.41.2\n", + "%pip install -U --pre \"openvino>2024.5\" --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly\n", + "%pip install -q \"git+https://github.com/eaidova/optimum-intel.git@ea/minicpmv\"\n", + "%pip install -q \"nncf>=2.14.0\" \"sentencepiece\" \"tokenizers>=0.12.1\" \"transformers>=4.45.0\" \"gradio>=4.36\"\n", + "%pip install -q -U --pre --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly openvino-tokenizers openvino openvino-genai\n", + "%pip install -q --upgrade huggingface_hub\n", + "%pip install -q --upgrade onnx==1.15.0\n", + "%pip install -q --upgrade torch==2.3.0\n", + "%pip install -q \"git+https://github.com/deepseek-ai/Janus\" --extra-index-url https://download.pytorch.org/whl/cpu\n", + "\n", + "\n", + "import platform\n", + "\n", + "if platform.system() == \"Darwin\":\n", + " %pip install -q \"numpy<2.0.0\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "from pathlib import Path\n", + "import requests\n", + "\n", + "utility_files = [\"notebook_utils.py\"]\n", + "local_helpers = [\"ov_janus_helper.py\", \"gradio_helper.py\"]\n", + "\n", + "base_utils_url = \"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/\"\n", + "base_local_files_url = \"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/notebooks/janus-multimodal-generation/\"\n", + "\n", + "\n", + "for util_path in utility_files:\n", + " if not Path(util_path).exists():\n", + " r = requests.get(base_utils_url + util_path)\n", + " with open(util_path, \"w\") as f:\n", + " f.write(r.text)\n", + "\n", + "for util_path in local_helpers:\n", + " if not Path(util_path).exists():\n", + " r = requests.get(base_local_files_url + util_path)\n", + " with open(util_path, \"w\") as f:\n", + " f.write(r.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Convert the model to OpenVino" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:nncf:NNCF initialized successfully. Supported frameworks detected: torch, onnx, openvino\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/importlib/util.py:245: DeprecationWarning: The `openvino.runtime` module is deprecated and will be removed in the 2026.0 release. Please replace `openvino.runtime` with `openvino`.\n", + " self.__spec__.loader.exec_module(self)\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/transformers/models/auto/image_processing_auto.py:524: FutureWarning: The image_processor_class argument is deprecated and will be removed in v4.42. Please use `slow_image_processor_class`, or `fast_image_processor_class` instead\n", + " warnings.warn(\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/attrdict/mapping.py:4: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working\n", + " from collections import Mapping\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/attrdict/mixins.py:5: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working\n", + " from collections import Mapping, MutableMapping, Sequence\n", + "/home/prabod/anaconda3/envs/pth23/lib/python3.9/site-packages/attrdict/mixins.py:5: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working\n", + " from collections import Mapping, MutableMapping, Sequence\n" + ] + } + ], + "source": [ + "import nncf\n", + "from ov_janus_helper import convert_janus_model\n", + "\n", + "model_id = \"deepseek-ai/Janus-1.3B\"\n", + "model_path = Path(model_id.split(\"/\")[-1] + \"-ov\")\n", + "\n", + "compression_configuration = {\n", + " \"mode\": nncf.CompressWeightsMode.INT4_ASYM,\n", + " \"group_size\": 64,\n", + " \"ratio\": 1.0,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โœ… Janus-1.3B model already converted. You can find results in Janus-1.3B-ov\n" + ] + } + ], + "source": [ + "convert_janus_model(model_id, model_path, compression_configuration)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Load openvino models" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "import openvino as ov\n", + "core = ov.Core()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Some kwargs in processor config are unused and will not have any effect: ignore_id, image_start_tag, image_end_tag, num_image_tokens, add_special_token, mask_prompt, image_tag, sft_format. \n" + ] + } + ], + "source": [ + "from pathlib import Path\n", + "from PIL import Image\n", + "from io import BytesIO\n", + "from janus.utils.io import load_pil_images\n", + "from janus.models import VLChatProcessor\n", + "import requests\n", + "\n", + "input_prompt = \"Describe image in details\"\n", + "\n", + "image_path = Path(\"cat_in_box.png\")\n", + "processor = VLChatProcessor.from_pretrained(model_path)\n", + "\n", + "if not image_path.exists():\n", + " response = requests.get(\"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\")\n", + " image = Image.open(BytesIO(response.content)).convert(\"RGB\")\n", + " image.save(image_path)\n", + "\n", + "conversation = [\n", + " {\n", + " \"role\": \"User\",\n", + " \"content\": f\"{input_prompt}\\n\",\n", + " \"images\": [str(image_path)],\n", + " },\n", + " {\"role\": \"Assistant\", \"content\": \"\"},\n", + "]\n", + "pil_images = load_pil_images(conversation)\n", + "\n", + "prepare_inputs = processor(conversations=conversation, images=pil_images, force_batchify=True)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "model_dir = model_path\n", + "VISION_EMBEDDINGS = \"openvino_vision_embeddings_model.xml\"\n", + "TEXT_EMBEDDINGS = \"openvino_text_embeddings_model.xml\"\n", + "LANGUAGE_MODEL = \"openvino_language_model.xml\"\n", + "LM_HEAD = \"openvino_lm_head_model.xml\"\n", + "MERGE_MULTIMODAL = \"openvino_multimodal_merge_model.xml\"\n", + "GEN_HEAD = \"openvino_gen_head_model.xml\"\n", + "GEN_EMBEDDINGS = \"openvino_gen_embeddings_model.xml\"\n", + "GEN_DECODER = \"openvino_gen_decoder_model.xml\"" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "text_embeddings = core.compile_model(model_dir / TEXT_EMBEDDINGS, \"CPU\")\n", + "vision_embeddings = core.compile_model(model_dir / VISION_EMBEDDINGS, \"CPU\")\n", + "language_model = core.compile_model(model_dir / LANGUAGE_MODEL, \"CPU\")\n", + "lm_head = core.compile_model(model_dir / LM_HEAD, \"CPU\")\n", + "request = language_model.create_infer_request()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "class MergeMultiModalInputs(torch.nn.Module):\n", + " def __init__(self,image_token_index=100594):\n", + " super().__init__()\n", + " self.image_token_index = image_token_index\n", + "\n", + " def forward(\n", + " self,\n", + " vision_embeds,\n", + " inputs_embeds,\n", + " input_ids,\n", + " ):\n", + " image_features = vision_embeds\n", + " inputs_embeds = inputs_embeds\n", + " special_image_mask = (input_ids == self.image_token_index).unsqueeze(-1).expand_as(inputs_embeds)\n", + " # image_features = image_features.to(inputs_embeds.dtype)\n", + " final_embedding = inputs_embeds.masked_scatter(special_image_mask, image_features)\n", + "\n", + " return {\n", + " \"final_embeddings\": final_embedding\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# sample text and image embeddings\n", + "\n", + "inputs = {}\n", + "# Set the initial input_ids\n", + "current_input_ids = prepare_inputs[\"input_ids\"]\n", + "attention_mask = prepare_inputs[\"attention_mask\"]\n", + "position_ids = attention_mask.long().cumsum(-1) - 1\n", + "position_ids.masked_fill_(attention_mask == 0, 1)\n", + "pixel_values = prepare_inputs[\"pixel_values\"]\n", + "\n", + "# Set the initial input_ids\n", + "text_out = text_embeddings(prepare_inputs[\"input_ids\"])[0]\n", + "vision_out = vision_embeddings(pixel_values)[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING:nncf:NNCF provides best results with torch==2.5.*, while current torch version is 2.3.1+cu121. If you encounter issues, consider switching to torch==2.5.*\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n" + ] + } + ], + "source": [ + "import openvino as ov\n", + "\n", + "torch_model_merge = MergeMultiModalInputs()\n", + "\n", + "# convert MergeMultiModalInputs to OpenVINO IR\n", + "ov_model_merge = ov.convert_model(\n", + " torch_model_merge,\n", + " example_input={\n", + " \"vision_embeds\": vision_out,\n", + " \"inputs_embeds\": text_out,\n", + " \"input_ids\": current_input_ids,\n", + " }\n", + ")\n", + "ov.save_model(ov_model_merge, model_path/\"openvino_multimodal_merge_model.xml\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "โŒ› Check if all models are converted\n", + "โœ… All models are converted. You can find results in Janus-1.3B-ov\n" + ] + } + ], + "source": [ + "# check if all the models are converted\n", + "\n", + "print(\"โŒ› Check if all models are converted\")\n", + "lang_model_path = model_dir / \"openvino_language_model.xml\"\n", + "image_embed_path = model_dir / \"openvino_vision_embeddings_model.xml\"\n", + "img_projection_path = model_dir / \"openvino_text_embeddings_model.xml\"\n", + "merge_model_path = model_dir / \"openvino_multimodal_merge_model.xml\"\n", + "gen_head_path = model_dir / \"openvino_gen_head_model.xml\"\n", + "gen_embed_path = model_dir / \"openvino_gen_embeddings_model.xml\"\n", + "gen_decoder_path = model_dir / \"openvino_gen_decoder_model.xml\"\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "if all(\n", + " [\n", + " lang_model_path.exists(),\n", + " image_embed_path.exists(),\n", + " img_projection_path.exists(),\n", + " merge_model_path.exists(),\n", + " gen_head_path.exists(),\n", + " gen_embed_path.exists(),\n", + " gen_decoder_path.exists(),\n", + " ]\n", + "):\n", + " print(f\"โœ… All models are converted. You can find results in {model_dir}\")\n", + "else:\n", + " print(\"โŒ Not all models are converted. Please check the conversion process\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Copy assets to the assets folder" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# update the preprocessor_config.json with the format needed for spark-nlp\n", + "import json\n", + "\n", + "with open(model_path / \"preprocessor_config.json\") as f:\n", + " preprocessor_config = json.load(f)\n", + "\n", + "preprocessor_config[\"size\"] = {\n", + " \"width\": preprocessor_config[\"image_size\"],\n", + " \"height\": preprocessor_config[\"image_size\"],\n", + "}\n", + "\n", + "preprocessor_config[\"do_normalize\"] = True\n", + "preprocessor_config[\"do_resize\"] = True\n", + "preprocessor_config[\"do_rescale\"] = True\n", + "preprocessor_config[\"resample\"] = 2\n", + "\n", + "with open(model_path / \"preprocessor_config.json\", \"w\") as f:\n", + " json.dump(preprocessor_config, f)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "assets_dir = model_dir / \"assets\"\n", + "assets_dir.mkdir(exist_ok=True)\n", + "\n", + "# copy all the assets to the assets directory (json files, vocab files, etc.)\n", + "\n", + "import shutil\n", + "\n", + "# copy all json files\n", + "\n", + "for file in model_dir.glob(\"*.json\"):\n", + " shutil.copy(file, assets_dir)\n", + "\n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 2.2G\n", + "drwxrwxr-x 2 prabod prabod 4.0K Feb 5 07:59 assets\n", + "-rw-rw-r-- 1 prabod prabod 1.5K Jan 22 05:52 config.json\n", + "-rw-rw-r-- 1 prabod prabod 82M Jan 22 05:54 openvino_gen_decoder_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 370K Jan 22 05:54 openvino_gen_decoder_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 8.3M Jan 22 05:54 openvino_gen_embeddings_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 8.8K Jan 22 05:54 openvino_gen_embeddings_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 73M Jan 22 05:54 openvino_gen_head_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 6.7K Jan 22 05:54 openvino_gen_head_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 640M Jan 22 05:54 openvino_language_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 2.1M Jan 22 05:54 openvino_language_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 400M Jan 22 05:52 openvino_lm_head_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 2.2K Jan 22 05:52 openvino_lm_head_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 40 Feb 10 05:00 openvino_multimodal_merge_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 9.8K Feb 10 05:00 openvino_multimodal_merge_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 401M Jan 22 05:52 openvino_text_embeddings_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 2.9K Jan 22 05:52 openvino_text_embeddings_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 592M Jan 22 05:54 openvino_vision_embeddings_model.bin\n", + "-rw-rw-r-- 1 prabod prabod 738K Jan 22 05:54 openvino_vision_embeddings_model.xml\n", + "-rw-rw-r-- 1 prabod prabod 370 Feb 10 05:00 preprocessor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 288 Jan 22 05:52 processor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 663 Jan 22 05:52 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 104K Jan 22 05:52 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 7.3M Feb 5 02:02 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -lh {model_dir}" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 7.4M\n", + "-rw-rw-r-- 1 prabod prabod 1.5K Feb 10 05:00 config.json\n", + "-rw-rw-r-- 1 prabod prabod 370 Feb 10 05:00 preprocessor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 288 Feb 10 05:00 processor_config.json\n", + "-rw-rw-r-- 1 prabod prabod 663 Feb 10 05:00 special_tokens_map.json\n", + "-rw-rw-r-- 1 prabod prabod 104K Feb 10 05:00 tokenizer_config.json\n", + "-rw-rw-r-- 1 prabod prabod 7.3M Feb 10 05:00 tokenizer.json\n" + ] + } + ], + "source": [ + "!ls -lh {assets_dir}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.3 Test the openvino model" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "import openvino as ov\n", + "import torch\n", + "\n", + "core = ov.Core()\n", + "device = \"CPU\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "text_embeddings = core.compile_model(model_dir / TEXT_EMBEDDINGS, \"CPU\")\n", + "vision_embeddings = core.compile_model(model_dir / VISION_EMBEDDINGS, \"CPU\")\n", + "language_model = core.compile_model(model_dir / LANGUAGE_MODEL, \"CPU\")\n", + "lm_head = core.compile_model(model_dir / LM_HEAD, \"CPU\")\n", + "model_merge = core.compile_model(model_dir / MERGE_MULTIMODAL, \"CPU\")\n", + "request = language_model.create_infer_request()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "generated_tokens = []\n", + "\n", + "from pathlib import Path\n", + "from PIL import Image\n", + "from io import BytesIO\n", + "from janus.utils.io import load_pil_images\n", + "import requests\n", + "import numpy as np\n", + "\n", + "input_prompt = \"Describe image in details\"\n", + "\n", + "image_path = Path(\"cat_in_box.png\")\n", + "\n", + "if not image_path.exists():\n", + " response = requests.get(\"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\")\n", + " image = Image.open(BytesIO(response.content)).convert(\"RGB\")\n", + " image.save(image_path)\n", + "\n", + "conversation = [\n", + " {\n", + " \"role\": \"User\",\n", + " \"content\": f\"{input_prompt}\\n\",\n", + " \"images\": [str(image_path)],\n", + " },\n", + " {\"role\": \"Assistant\", \"content\": \"\"},\n", + "]\n", + "pil_images = load_pil_images(conversation)\n", + "\n", + "prepare_inputs = processor(conversations=conversation, images=pil_images, force_batchify=True)\n", + "request = language_model.create_infer_request()\n", + "merge_model_request = model_merge.create_infer_request()\n", + "\n", + "current_input_ids = prepare_inputs[\"input_ids\"]\n", + "attention_mask = prepare_inputs[\"attention_mask\"]\n", + "position_ids = attention_mask.long().cumsum(-1) - 1\n", + "position_ids.masked_fill_(attention_mask == 0, 1)\n", + "\n", + "pixel_values = prepare_inputs[\"pixel_values\"]\n", + "\n", + "for i in range(50):\n", + " # Generate input embeds each time\n", + " if current_input_ids.shape[-1] > 1:\n", + " vision_embeds = vision_embeddings(pixel_values)[0] \n", + " text_embeds = text_embeddings(current_input_ids)[0]\n", + "\n", + " \n", + " if i == 0:\n", + " # Merge the text and vision embeddings\n", + " text_embeds = torch.from_numpy(text_embeds)\n", + " vision_embeds = torch.from_numpy(vision_embeds)\n", + " final_embedding = model_merge({\n", + " \"vision_embeds\": vision_embeds,\n", + " \"inputs_embeds\": text_embeds,\n", + " \"input_ids\": current_input_ids,\n", + " }, share_inputs=True)[0]\n", + " input_embeds = final_embedding\n", + " else:\n", + " input_embeds = torch.from_numpy(text_embeds)\n", + " inputs = {}\n", + " # Prepare inputs for the model\n", + " inputs[\"inputs_embeds\"] = input_embeds\n", + " inputs[\"attention_mask\"] = attention_mask\n", + " inputs[\"position_ids\"] = position_ids\n", + " inputs[\"beam_idx\"] = np.arange(attention_mask.shape[0], dtype=int)\n", + "\n", + " request.start_async(inputs,share_inputs=True)\n", + " request.wait()\n", + " hidden_states = request.get_tensor(\"last_hidden_state\").data\n", + " logits = torch.from_numpy(lm_head(hidden_states,share_inputs=True,share_outputs=True)[0])\n", + " \n", + " next_token =logits.argmax(-1)[0][-1]\n", + "\n", + " # Append the generated token\n", + " generated_tokens.append(next_token)\n", + " \n", + " # Update input_ids with the new token\n", + " current_input_ids = torch.cat([next_token.unsqueeze(0).unsqueeze(0)], dim=-1)\n", + " \n", + " # update the attention mask\n", + " attention_mask = torch.cat([attention_mask, torch.ones_like(attention_mask[:, :1])], dim=-1)\n", + "\n", + " # Update inputs for the next iteration\n", + " position_ids = attention_mask.long().cumsum(-1) - 1\n", + " position_ids.masked_fill_(attention_mask == 0, 1)\n", + " position_ids = position_ids[:, -current_input_ids.shape[1] :]\n", + " inputs[\"position_ids\"] = position_ids" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Question:\n", + " Describe image in details\n", + "Answer:\n", + "The image depicts a gray and white tabby cat lying comfortably inside a cardboard box. The cat is lying on its back with its paws up in the air, and its eyes are closed, suggesting it is relaxed and possibly asleep. The box is placed\n" + ] + } + ], + "source": [ + "generated_text = processor.tokenizer.decode(generated_tokens, skip_special_tokens=True)\n", + "\n", + "print(\"Question:\\n Describe image in details\")\n", + "print(\"Answer:\")\n", + "print(generated_text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Import and Save Janus in Spark NLP\n", + "\n", + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start Spark with Spark NLP included via our simple `start()` function" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "24/11/07 09:56:55 WARN Utils: Your hostname, minotaur resolves to a loopback address: 127.0.1.1; using 192.168.1.4 instead (on interface eno1)\n", + "24/11/07 09:56:55 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address\n", + "24/11/07 09:56:55 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Setting default log level to \"WARN\".\n", + "To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "25/03/11 02:20:47 WARN NativeLibrary: Failed to load library null: java.lang.UnsatisfiedLinkError: Can't load library: /tmp/openvino-native2264403399992055719/libtbb.so.2\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING: An illegal reflective access operation has occurred\n", + "WARNING: Illegal reflective access by org.apache.spark.util.SizeEstimator$ (file:/home/prabod/spark/jars/spark-core_2.12-3.3.2.jar) to field java.util.regex.Pattern.pattern\n", + "WARNING: Please consider reporting this to the maintainers of org.apache.spark.util.SizeEstimator$\n", + "WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n", + "WARNING: All illegal access operations will be denied in a future release\n" + ] + } + ], + "source": [ + "imageClassifier = JanusForMultiModal.loadSavedModel(model_dir, spark) \\\n", + " .setInputCols(\"image_assembler\") \\\n", + " .setOutputCol(\"answer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "imageClassifier.write().overwrite().save(\"file:///tmp/Janus_spark_nlp\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import sparknlp\n", + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.sql.functions import lit\n", + "from pyspark.ml import Pipeline\n", + "from pathlib import Path\n", + "import os\n", + "\n", + "# download two images to test into ./images folder\n", + "\n", + "url1 = \"https://github.com/openvinotoolkit/openvino_notebooks/assets/29454499/d5fbbd1a-d484-415c-88cb-9986625b7b11\"\n", + "url2 = \"http://images.cocodataset.org/val2017/000000039769.jpg\"\n", + "\n", + "Path(\"images\").mkdir(exist_ok=True)\n", + "\n", + "!wget -q -O images/image1.jpg {url1}\n", + "!wget -q -O images/image2.jpg {url2}\n", + "\n", + "\n", + "\n", + "images_path = \"file://\" + os.getcwd() + \"/images/\"\n", + "image_df = spark.read.format(\"image\").load(\n", + " path=images_path\n", + ")\n", + "\n", + "test_df = image_df.withColumn(\"text\", lit(\"You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\\n\\nUser: Describe image in details\\n\\nAssistant:\"))\n", + "\n", + "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n", + "\n", + "imageClassifier = JanusForMultiModal.load(\"file:///tmp/Janus_spark_nlp\")\\\n", + " .setMaxOutputLength(50) \\\n", + " .setInputCols(\"image_assembler\") \\\n", + " .setOutputCol(\"answer\")\n", + "\n", + "pipeline = Pipeline(\n", + " stages=[\n", + " image_assembler,\n", + " imageClassifier,\n", + " ]\n", + " )\n", + "\n", + "model = pipeline.fit(test_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "image_path: file:///home/prabod/Projects/spark-nlp/examples/python/transformers/openvino/images/image1.jpg\n", + "[Annotation(document, 0, 222, The image depicts a gray tabby cat lounging in a cardboard box. The cat is lying on its back with its legs and paws spread out in a relaxed manner. Its eyes are closed, and it appears to be enjoying a moment of tranquility., Map(), [])]\n" + ] + } + ], + "source": [ + "light_pipeline = LightPipeline(model)\n", + "image_path = \"file://\" + os.getcwd() + \"/images/\" + \"image1.jpg\"\n", + "print(\"image_path: \" + image_path)\n", + "annotations_result = light_pipeline.fullAnnotateImage(\n", + " image_path,\n", + " \"You are a helpful language and vision assistant. You are able to understand the visual content that the user provides, and assist the user with a variety of tasks using natural language.\\n\\nUser: Describe image in details\\n\\nAssistant:\"\n", + ")\n", + "\n", + "for result in annotations_result:\n", + " print(result[\"answer\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Image Generation with Janus\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use Janus for image generation, an image must be provided to the pipeline as it is a required column for the ImageAssembler. This image can be an empty image or any placeholder image. The provided image will not be used to generate a new image but will serve as a necessary input for the pipeline." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "image_path: file:///tmp/empty_image.jpg\n" + ] + }, + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAGAAYADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5/ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Z", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAIAAAArpSLoAAAHoklEQVR4Ae3QgQAAAADDoPlTX+AIhVBhwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwICBPzDB2gABCQd7EQAAAABJRU5ErkJggg==", + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# create an empty image to test the model\n", + "\n", + "from PIL import Image\n", + "import numpy as np\n", + "\n", + "image = Image.new(\"RGB\", (384, 384))\n", + "image.save(\"/tmp/empty_image.jpg\")\n", + "\n", + "image_path = \"file:///tmp/empty_image.jpg\"\n", + "print(\"image_path: \" + image_path)\n", + "\n", + "image" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "\n", + "import sparknlp\n", + "from sparknlp.base import *\n", + "from sparknlp.annotator import *\n", + "from pyspark.sql.functions import lit\n", + "from pyspark.ml import Pipeline\n", + "from pathlib import Path\n", + "import os\n", + "\n", + "image_df = spark.read.format(\"image\").load(\n", + " path=image_path\n", + ")\n", + "test_df = image_df.withColumn(\"text\", lit(\"User: Create a detailed image of a whimsical forest filled with vibrant, oversized mushrooms, glowing flowers, and towering, twisted trees with bioluminescent vines. The atmosphere is magical, with soft, ethereal light filtering through a misty canopy. Small floating orbs of light hover among the branches, and tiny fairy-like creatures flit through the air. A winding, moss-covered path leads to a mysterious glowing portal hidden within the trees. The scene should feel enchanting, otherworldly, and full of wonder, like a dreamlike fantasy realm.\\n\\nAssistant:\"))\n", + "\n", + "\n", + "image_assembler = ImageAssembler().setInputCol(\"image\").setOutputCol(\"image_assembler\")\n", + "\n", + "imageClassifier = JanusForMultiModal.load(\"file:///tmp/Janus_spark_nlp\")\\\n", + " .setMaxOutputLength(50) \\\n", + " .setImageGenerateMode(True) \\\n", + " .setInputCols(\"image_assembler\") \\\n", + " .setOutputCol(\"answer\")\n", + "\n", + "generate_pipeline = Pipeline(\n", + " stages=[\n", + " image_assembler,\n", + " imageClassifier,\n", + " ]\n", + " )\n", + "\n", + "generate_model = generate_pipeline.fit(test_df)\n", + "generation_result = generate_model.transform(test_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " \r" + ] + } + ], + "source": [ + "metadata = generation_result.select(\"answer.metadata\").collect()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import io\n", + "from PIL import Image\n", + "import base64\n", + "from IPython.display import display\n", + "\n", + "for row in metadata:\n", + " result = row[\"metadata\"][0]\n", + " for key in result:\n", + " if \"generated_image\" in key:\n", + " image = result[key]\n", + " image = base64.b64decode(image)\n", + " image = Image.open(io.BytesIO(image)).resize((384, 384))\n", + " display(image)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "mllama", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.21" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From d7bbb5181416f35290bffc42fde9c8a8669ab273 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 14 Mar 2025 12:10:10 +0100 Subject: [PATCH 087/108] Improved Error Handling for AutoGGUF models --- .../embeddings/auto_gguf_embeddings.py | 6 +- .../embeddings/auto_gguf_embeddings_test.py | 75 +++++++++++++++++-- .../annotator/seq2seq/auto_gguf_model_test.py | 49 ++++++++++++ .../annotators/seq2seq/AutoGGUFModel.scala | 16 +++- .../nlp/embeddings/AutoGGUFEmbeddings.scala | 16 +++- .../seq2seq/AutoGGUFModelTest.scala | 24 ++++++ .../AutoGGUFEmbeddingsTestSpec.scala | 43 ++++++++++- 7 files changed, 215 insertions(+), 14 deletions(-) diff --git a/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py b/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py index 30cee663c16129..77ca3d759b11d6 100755 --- a/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py +++ b/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py @@ -32,7 +32,7 @@ class AutoGGUFEmbeddings(AnnotatorModel, HasBatchedAnnotate): ... .setInputCols(["document"]) \\ ... .setOutputCol("embeddings") - The default model is ``"nomic-embed-text-v1.5.Q8_0.gguf"``, if no name is provided. + The default model is ``"Nomic_Embed_Text_v1.5.Q8_0.gguf"``, if no name is provided. For extended examples of usage, see the `AutoGGUFEmbeddingsTest `__ @@ -513,13 +513,13 @@ def loadSavedModel(folder, spark_session): return AutoGGUFEmbeddings(java_model=jModel) @staticmethod - def pretrained(name="nomic-embed-text-v1.5.Q8_0.gguf", lang="en", remote_loc=None): + def pretrained(name="Nomic_Embed_Text_v1.5.Q8_0.gguf", lang="en", remote_loc=None): """Downloads and loads a pretrained model. Parameters ---------- name : str, optional - Name of the pretrained model, by default "nomic-embed-text-v1.5.Q8_0.gguf" + Name of the pretrained model, by default "Nomic_Embed_Text_v1.5.Q8_0.gguf" lang : str, optional Language of the pretrained model, by default "en" remote_loc : str, optional diff --git a/python/test/annotator/embeddings/auto_gguf_embeddings_test.py b/python/test/annotator/embeddings/auto_gguf_embeddings_test.py index 72b82c19b6e830..0f9ebe0b4ea247 100644 --- a/python/test/annotator/embeddings/auto_gguf_embeddings_test.py +++ b/python/test/annotator/embeddings/auto_gguf_embeddings_test.py @@ -47,6 +47,7 @@ def runTest(self): .setOutputCol("embeddings") .setBatchSize(4) .setNGpuLayers(99) + .setNCtx(4096) ) pipeline = Pipeline().setStages([self.document_assembler, model]) @@ -57,7 +58,7 @@ def runTest(self): embds = row["embeddings"][0] assert embds is not None assert ( - sum(embds) > 0 + sum(embds) > 0 ), "Embeddings should not be zero. Was there an error on llama.cpp side?" @@ -83,10 +84,7 @@ def setUp(self): def runTest(self): model = ( - # AutoGGUFEmbeddings.pretrained() - AutoGGUFEmbeddings.loadSavedModel( - "models/nomic-embed-text-v1.5.Q8_0.gguf", SparkContextForTest.spark - ) + AutoGGUFEmbeddings.pretrained() .setInputCols("document") .setOutputCol("embeddings") .setBatchSize(4) @@ -102,5 +100,70 @@ def runTest(self): embds = row["embeddings"][0] assert embds is not None assert ( - sum(embds) > 0 + sum(embds) > 0 + ), "Embeddings should not be zero. Was there an error on llama.cpp side?" + + +@pytest.mark.slow +class AutoGGUFEmbeddingsErrorHandlingTestSpec(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + self.document_assembler = ( + DocumentAssembler().setInputCol("text").setOutputCol("document") + ) + self.long_data_copies = 16 + self.long_text = "All work and no play makes Jack a dull boy" * 100 + self.long_data = self.spark.createDataFrame( + [self.long_text] * self.long_data_copies, schema="string" + ).toDF("text").repartition(4) + + def runTest(self): + model = ( + AutoGGUFEmbeddings.pretrained() + .setInputCols("document") + .setOutputCol("embeddings") + .setBatchSize(4) + ) + pipeline = Pipeline().setStages([self.document_assembler, model]) + results = pipeline.fit(self.long_data).transform(self.long_data) + collected = results.select("embeddings").collect() + + assert len(collected) == self.long_data_copies + for row in collected: + metadata = row[0][0]["metadata"] + assert "llamacpp_exception" in metadata, "llamacpp_exception should be present" + + +@pytest.mark.slow +class AutoGGUFEmbeddingsLongTextTestSpec(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + self.document_assembler = ( + DocumentAssembler().setInputCol("text").setOutputCol("document") + ) + self.long_data_copies = 16 + self.long_text = "All work and no play makes Jack a dull boy" * 100 + self.long_data = self.spark.createDataFrame( + [self.long_text] * self.long_data_copies, schema="string" + ).toDF("text").repartition(4) + + def runTest(self): + model = ( + AutoGGUFEmbeddings.pretrained() + .setInputCols("document") + .setOutputCol("embeddings") + .setBatchSize(4) + .setNUbatch(2048) + .setNBatch(2048) + ) + pipeline = Pipeline().setStages([self.document_assembler, model]) + results = pipeline.fit(self.long_data).transform(self.long_data) + collected = results.select("embeddings").collect() + + assert len(collected) == self.long_data_copies, "Should return the same number of rows" + for row in collected: + embds = row[0][0]["embeddings"] + assert embds is not None + assert ( + sum(embds) > 0 ), "Embeddings should not be zero. Was there an error on llama.cpp side?" diff --git a/python/test/annotator/seq2seq/auto_gguf_model_test.py b/python/test/annotator/seq2seq/auto_gguf_model_test.py index e6553bc509e5ff..5484484a7a9d3b 100644 --- a/python/test/annotator/seq2seq/auto_gguf_model_test.py +++ b/python/test/annotator/seq2seq/auto_gguf_model_test.py @@ -189,3 +189,52 @@ def runTest(self): metadata = model.getMetadata() assert len(metadata) > 0 print(eval(metadata)) + + +@pytest.mark.slow +class AutoGGUFModelErrorMessagesTestSpec(unittest.TestCase): + def setUp(self): + self.spark = SparkContextForTest.spark + self.data = ( + self.spark.createDataFrame( + [ + ["The moons of Jupiter are "], + ["Earth is "], + ["The moon is "], + ["The sun is "], + ] + ) + .toDF("text") + .repartition(1) + ) + + self.document_assembler = ( + DocumentAssembler().setInputCol("text").setOutputCol("document") + ) + + def runTest(self): + model = ( + AutoGGUFModel.pretrained() + .setInputCols("document") + .setOutputCol("completions") + .setGrammar("root ::= (") # Invalid grammar + ) + + pipeline = Pipeline().setStages([self.document_assembler, model]) + result = pipeline.fit(self.data).transform(self.data) + + collected = result.select("completions").collect() + + self.assertEqual( + len(collected), self.data.count(), "Should return the same number of rows" + ) + for row in collected: + annotation = row[0][0] + self.assertEqual( + annotation["result"], "", "Completions should be empty" + ) + self.assertIn( + "llamacpp_exception", + annotation["metadata"], + "llamacpp_exception should be present", + ) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala index 3caf4bdc0e8be2..4be4c98039058f 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala @@ -153,6 +153,16 @@ class AutoGGUFModel(override val uid: String) embedding -> false, nPredict -> 100) + /** Sets the number of parallel processes for decoding. This is an alias for `setBatchSize`. + * + * @group setParam + * @param nParallel + * The number of parallel processes for decoding + */ + def setNParallel(nParallel: Int): this.type = { + setBatchSize(nParallel) + } + override def onWrite(path: String, spark: SparkSession): Unit = { super.onWrite(path, spark) getModelIfNotSet.saveToFile(path) @@ -184,7 +194,9 @@ class AutoGGUFModel(override val uid: String) } catch { case e: Exception => logger.error("Error in llama.cpp embeddings", e) - (Array.empty[Array[Float]], Map("llamacpp_exception" -> e.getMessage)) + ( + Array.fill[Array[Float]](annotationsText.length)(Array.empty), + Map("llamacpp_exception" -> e.getMessage)) } // Choose empty text for result annotations annotations.zip(embeddings).map { case (annotation, embedding) => @@ -204,7 +216,7 @@ class AutoGGUFModel(override val uid: String) } catch { case e: Exception => logger.error("Error in llama.cpp batch completion", e) - (Array[String](), Map("llamacpp_exception" -> e.getMessage)) + (Array.fill(annotationsText.length)(""), Map("llamacpp_exception" -> e.getMessage)) } annotations.zip(completedTexts).map { case (annotation, text) => Seq( diff --git a/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala b/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala index 98aa10eb8b31ac..389166a7ad10f6 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala @@ -142,6 +142,16 @@ class AutoGGUFEmbeddings(override val uid: String) nCtx -> 4096, nBatch -> 512) + /** Sets the number of parallel processes for decoding. This is an alias for `setBatchSize`. + * + * @group setParam + * @param nParallel + * The number of parallel processes for decoding + */ + def setNParallel(nParallel: Int): this.type = { + setBatchSize(nParallel) + } + override def onWrite(path: String, spark: SparkSession): Unit = { super.onWrite(path, spark) getModelIfNotSet.saveToFile(path) @@ -175,7 +185,9 @@ class AutoGGUFEmbeddings(override val uid: String) } catch { case e: Exception => logger.error("Error in llama.cpp embeddings", e) - (Array.empty[Array[Float]], Map("llamacpp_exception" -> e.getMessage)) + ( + Array.fill[Array[Float]](annotationsText.length)(Array.empty), + Map("llamacpp_exception" -> e.getMessage)) } // Choose empty text for result annotations @@ -196,7 +208,7 @@ class AutoGGUFEmbeddings(override val uid: String) trait ReadablePretrainedAutoGGUFEmbeddings extends ParamsAndFeaturesReadable[AutoGGUFEmbeddings] with HasPretrained[AutoGGUFEmbeddings] { - override val defaultModelName: Some[String] = Some("nomic-embed-text-v1.5.Q8_0.gguf") + override val defaultModelName: Some[String] = Some("Nomic_Embed_Text_v1.5.Q8_0.gguf") override val defaultLang: String = "en" /** Java compliant-overrides */ diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModelTest.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModelTest.scala index f755b76dfa2e72..01cb289903550d 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModelTest.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModelTest.scala @@ -181,4 +181,28 @@ class AutoGGUFModelTest extends AnyFlatSpec { val metadataMap = model.getMetadataMap assert(metadataMap.nonEmpty) } + + it should "return error messages when completions can't be produced" taggedAs SlowTest in { + val model = AutoGGUFModel + .pretrained() + .setInputCols("document") + .setOutputCol("completions") + .setGrammar("root ::= (") // Invalid grammar + + val pipeline = + new Pipeline().setStages(Array(documentAssembler, model)) + val result = pipeline.fit(data).transform(data) + + val collected = Annotation + .collect(result, "completions") + + assert(collected.length == data.count().toInt, "Should return the same number of rows") + collected + .foreach(annotations => { + assert(annotations.head.result.isEmpty, "Completions should be empty") + assert( + annotations.head.metadata.contains("llamacpp_exception"), + "llamacpp_exception should be present") + }) + } } diff --git a/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala index b7c4544bdbd87f..f9a90635d6ac2d 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala @@ -7,6 +7,9 @@ import com.johnsnowlabs.tags.SlowTest import org.apache.spark.ml.Pipeline import org.scalatest.flatspec.AnyFlatSpec +import scala.io.Source +import scala.util.Using + class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { import ResourceHelper.spark.implicits._ @@ -23,6 +26,13 @@ class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { "The sun is " // ).toDF("text").repartition(1) + lazy val longDataCopies = 16 + lazy val longData = { + val text = "All work and no play makes Jack a dull boy" * 100 + Seq.fill(longDataCopies)(text).toDF("text").repartition(4) + } + + println(ResourceHelper.spark.version) // nomic-embed-text-v1.5.Q8_0.gguf def model(poolingType: String): AutoGGUFEmbeddings = AutoGGUFEmbeddings .pretrained() @@ -30,7 +40,7 @@ class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { .setOutputCol("embeddings") .setBatchSize(4) .setPoolingType(poolingType) - + .setNCtx(8192) def pipeline(embedModel: AutoGGUFEmbeddings = model("MEAN")) = new Pipeline().setStages(Array(documentAssembler, embedModel)) @@ -83,4 +93,35 @@ class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { .select("embeddings.embeddings") .show(truncate = false) } + + it should "return error messages when embeddings can't be created" taggedAs SlowTest in { + val result = pipeline().fit(longData).transform(longData) + val collected = Annotation.collect(result, "embeddings") + assert(collected.length == longDataCopies) + + collected.foreach { annotations => + assert( + annotations.head.metadata.contains("llamacpp_exception"), + "llamacpp_exception should be present") + } + + } + + it should "embed long text" taggedAs SlowTest in { + val result = pipeline( + model("MEAN") + .setNUbatch(2048) + .setNBatch(2048)).fit(longData).transform(longData) + val collected = Annotation.collect(result, "embeddings") + assert(collected.length == longDataCopies, "Should return the same number of rows") + + collected.foreach { annotations => + val embeddings = annotations.head.embeddings + assert(embeddings != null, "embeddings should not be null") + assert( + embeddings.sum > 0.0, + "embeddings should not be zero. Was there an error on llama.cpp side?") + } + } + } From 489af7e4469ba8c861bc5a315953d5f3b0a34e2d Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 14 Mar 2025 15:24:57 +0100 Subject: [PATCH 088/108] Add setNParallel for AutoGGUF models on python side --- .../annotator/embeddings/auto_gguf_embeddings.py | 10 +++++++--- python/sparknlp/annotator/seq2seq/auto_gguf_model.py | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py b/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py index 77ca3d759b11d6..20bb13906afe02 100755 --- a/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py +++ b/python/sparknlp/annotator/embeddings/auto_gguf_embeddings.py @@ -471,15 +471,19 @@ def setNoKvOffload(self, noKvOffload: bool): """Whether to disable KV offload""" return self._set(noKvOffload=noKvOffload) + def setNParallel(self, nParallel: int): + """Sets the number of parallel processes for decoding. This is an alias for `setBatchSize`.""" + return self.setBatchSize(nParallel) + def getMetadata(self): """Gets the metadata of the model""" return self._call_java("getMetadata") @keyword_only def __init__( - self, - classname="com.johnsnowlabs.nlp.embeddings.AutoGGUFEmbeddings", - java_model=None, + self, + classname="com.johnsnowlabs.nlp.embeddings.AutoGGUFEmbeddings", + java_model=None, ): super(AutoGGUFEmbeddings, self).__init__( classname=classname, java_model=java_model diff --git a/python/sparknlp/annotator/seq2seq/auto_gguf_model.py b/python/sparknlp/annotator/seq2seq/auto_gguf_model.py index d28ac006c9da22..74457dfc720256 100755 --- a/python/sparknlp/annotator/seq2seq/auto_gguf_model.py +++ b/python/sparknlp/annotator/seq2seq/auto_gguf_model.py @@ -601,6 +601,10 @@ def setChatTemplate(self, chatTemplate: str): """The chat template to use""" return self._set(chatTemplate=chatTemplate) + def setNParallel(self, nParallel: int): + """Sets the number of parallel processes for decoding. This is an alias for `setBatchSize`.""" + return self.setBatchSize(nParallel) + # -------- INFERENCE SETTERS -------- def setInputPrefix(self, inputPrefix: str): """Set the prompt to start generation with""" From 9df68a7d850e7e21ffd0131db07e7c1fa0e64189 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 14 Mar 2025 16:48:23 +0100 Subject: [PATCH 089/108] Improved Error Handling and setNParallel alias for batch size --- python/sparknlp/common/properties.py | 4 ++++ .../nlp/annotators/seq2seq/AutoGGUFVisionModel.scala | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/python/sparknlp/common/properties.py b/python/sparknlp/common/properties.py index 3e35a4eabce1b7..f6d9a1ec94d313 100644 --- a/python/sparknlp/common/properties.py +++ b/python/sparknlp/common/properties.py @@ -1246,6 +1246,10 @@ def setSamplers(self, samplers: List[str]): def setUseChatTemplate(self, useChatTemplate: bool): """Set whether generate should apply a chat template""" return self._set(useChatTemplate=useChatTemplate) + + def setNParallel(self, nParallel: int): + """Sets the number of parallel processes for decoding. This is an alias for `setBatchSize`.""" + return self.setBatchSize(nParallel) # -------- JAVA SETTERS -------- def setTokenIdBias(self, tokenIdBias: Dict[int, float]): diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala index f3de739613ece3..62b4d4903ec97b 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala @@ -185,6 +185,16 @@ class AutoGGUFVisionModel(override val uid: String) private[johnsnowlabs] def setEngine(engineName: String): this.type = set(engine, engineName) + /** Sets the number of parallel processes for decoding. This is an alias for `setBatchSize`. + * + * @group setParam + * @param nParallel + * The number of parallel processes for decoding + */ + def setNParallel(nParallel: Int): this.type = { + setBatchSize(nParallel) + } + setDefault( engine -> LlamaCPP.name, useChatTemplate -> true, @@ -252,7 +262,7 @@ class AutoGGUFVisionModel(override val uid: String) } catch { case e: LlamaException => logger.error("Error in llama.cpp image batch completion", e) - (Array[String](), Map("LlamaException" -> e.getMessage)) + (Array.fill(prompts.length)(""), Map("llamacpp_exception" -> e.getMessage)) } val result: Seq[Seq[Annotation]] = From f3d353c8b51609ff216605683e7abaf514111582 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Fri, 14 Mar 2025 17:26:58 +0100 Subject: [PATCH 090/108] Fix notebook error format --- ...gingFace_ONNX_in_Spark_NLP_MPNetForQuestionAnswering.ipynb | 4 ++-- ...gFace_ONNX_in_Spark_NLP_XlmRoBertaSentenceEmbeddings.ipynb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_MPNetForQuestionAnswering.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_MPNetForQuestionAnswering.ipynb index cd4835de6d3325..75b4a28a439e73 100644 --- a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_MPNetForQuestionAnswering.ipynb +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_MPNetForQuestionAnswering.ipynb @@ -378,10 +378,10 @@ "colab": { "provenance": [] }, - "kernelspec": ,{ + "kernelspec": { "display_name": "Python 3", "name": "python3" - } + }, "language_info": { "codemirror_mode": { "name": "ipython", diff --git a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBertaSentenceEmbeddings.ipynb b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBertaSentenceEmbeddings.ipynb index 4cff73dd823aa2..269471933def9d 100644 --- a/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBertaSentenceEmbeddings.ipynb +++ b/examples/python/transformers/onnx/HuggingFace_ONNX_in_Spark_NLP_XlmRoBertaSentenceEmbeddings.ipynb @@ -421,10 +421,10 @@ "gpuType": "T4", "provenance": [] }, - "kernelspec": ,{ + "kernelspec": { "display_name": "Python 3", "name": "python3" - } + }, "language_info": { "codemirror_mode": { "name": "ipython", From 5417d91f29492c03e8bda86d61d48792cfbdd3b3 Mon Sep 17 00:00:00 2001 From: ahmedlone127 Date: Sun, 16 Mar 2025 16:51:09 +0500 Subject: [PATCH 091/108] updating python and scala model names (#14488) --- python/sparknlp/annotator/seq2seq/llama3_transformer.py | 6 +++--- .../nlp/annotators/seq2seq/LLAMA3Transformer.scala | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/sparknlp/annotator/seq2seq/llama3_transformer.py b/python/sparknlp/annotator/seq2seq/llama3_transformer.py index 43b32f1a70454a..f242d68264355f 100644 --- a/python/sparknlp/annotator/seq2seq/llama3_transformer.py +++ b/python/sparknlp/annotator/seq2seq/llama3_transformer.py @@ -38,7 +38,7 @@ class LLAMA3Transformer(AnnotatorModel, HasBatchedAnnotate, HasEngine): ... .setOutputCol("generation") - The default model is ``"llama3-7b"``, if no name is provided. For available + The default model is ``"llama_3_7b_chat_hf_int4"``, if no name is provided. For available pretrained models please see the `Models Hub `__. @@ -108,7 +108,7 @@ class LLAMA3Transformer(AnnotatorModel, HasBatchedAnnotate, HasEngine): >>> documentAssembler = DocumentAssembler() \\ ... .setInputCol("text") \\ ... .setOutputCol("documents") - >>> llama3 = LLAMA3Transformer.pretrained("llama_3_7b_chat_hf_int8") \\ + >>> llama3 = LLAMA3Transformer.pretrained("llama_3_7b_chat_hf_int4") \\ ... .setInputCols(["documents"]) \\ ... .setMaxOutputLength(60) \\ ... .setOutputCol("generation") @@ -365,7 +365,7 @@ def pretrained(name="llama_3_7b_chat_hf_int4", lang="en", remote_loc=None): Parameters ---------- name : str, optional - Name of the pretrained model, by default "llama_2_7b_chat_hf_int4" + Name of the pretrained model, by default "llama_3_7b_chat_hf_int4" lang : str, optional Language of the pretrained model, by default "en" remote_loc : str, optional diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/LLAMA3Transformer.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/LLAMA3Transformer.scala index 1eecc75c557e26..6651fc57b41cf7 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/LLAMA3Transformer.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/LLAMA3Transformer.scala @@ -65,7 +65,7 @@ import org.json4s.jackson.JsonMethods._ * .setInputCols("document") * .setOutputCol("generation") * }}} - * The default model is `"llama_3_7b_chat_hf_int8"`, if no name is provided. For available + * The default model is `"llama_3_7b_chat_hf_int4"`, if no name is provided. For available * pretrained models please see the [[https://sparknlp.org/models?q=llama3 Models Hub]]. * * For extended examples of usage, see @@ -101,7 +101,7 @@ import org.json4s.jackson.JsonMethods._ * .setInputCol("text") * .setOutputCol("documents") * - * val llama3 = LLAMA3Transformer.pretrained("llama_3_7b_chat_hf_int8") + * val llama3 = LLAMA3Transformer.pretrained("llama_3_7b_chat_hf_int4") * .setInputCols(Array("documents")) * .setMinOutputLength(15) * .setMaxOutputLength(60) @@ -359,7 +359,7 @@ class LLAMA3Transformer(override val uid: String) trait ReadablePretrainedLLAMA3TransformerModel extends ParamsAndFeaturesReadable[LLAMA3Transformer] with HasPretrained[LLAMA3Transformer] { - override val defaultModelName: Some[String] = Some("llama3") + override val defaultModelName: Some[String] = Some("llama_3_7b_chat_hf_int4") /** Java compliant-overrides */ override def pretrained(): LLAMA3Transformer = super.pretrained() From a9d7980752a2969476172ce4002c8947a42c7390 Mon Sep 17 00:00:00 2001 From: Danilo Burbano <37355249+danilojsl@users.noreply.github.com> Date: Sun, 16 Mar 2025 07:06:37 -0500 Subject: [PATCH 092/108] SPARKNLP-1109 Adding Extractor to Sparknlp (#14519) * [SPARKNLP-1109] Adding Extractor annotator * [SPARKNLP-1109] Adding Cleaner annotator * [SPARKNLP-1109] Adding missing index parameter in python * [SPARKNLP-1109] Adding right inheritance for Cleaner in python * [SPARKNLP-1109] Adding notebooks demo for Cleaner and Extractor * [SPARKNLP-1110] Adding notebook demo for Email reader and Cleaner --- .../SparkNLP_Cleaner_Demo.ipynb | 1004 ++++++++++++ .../SparkNLP_Email_Data_Preparation.ipynb | 446 ++++++ .../SparkNLP_Extractor_Demo.ipynb | 1387 +++++++++++++++++ .../sparknlp/annotator/cleaners/__init__.py | 15 + python/sparknlp/annotator/cleaners/cleaner.py | 202 +++ .../sparknlp/annotator/cleaners/extractor.py | 191 +++ python/test/annotator/cleaners/__init__.py | 0 .../test/annotator/cleaners/cleaner_test.py | 73 + .../test/annotator/cleaners/extractor_test.py | 49 + .../nlp/annotators/cleaners/Cleaner.scala | 241 +++ .../nlp/annotators/cleaners/Extractor.scala | 365 +++++ .../cleaners/util/CleanerHelper.scala | 224 +++ .../annotators/cleaners/CleanerTestSpec.scala | 174 +++ .../cleaners/ExtractorTestSpec.scala | 350 +++++ .../cleaners/util/CleanerHelperTestSpec.scala | 350 +++++ 15 files changed, 5071 insertions(+) create mode 100644 examples/python/data-preprocessing/SparkNLP_Cleaner_Demo.ipynb create mode 100644 examples/python/data-preprocessing/SparkNLP_Email_Data_Preparation.ipynb create mode 100644 examples/python/data-preprocessing/SparkNLP_Extractor_Demo.ipynb create mode 100644 python/sparknlp/annotator/cleaners/__init__.py create mode 100644 python/sparknlp/annotator/cleaners/cleaner.py create mode 100644 python/sparknlp/annotator/cleaners/extractor.py create mode 100644 python/test/annotator/cleaners/__init__.py create mode 100644 python/test/annotator/cleaners/cleaner_test.py create mode 100644 python/test/annotator/cleaners/extractor_test.py create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala create mode 100644 src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/CleanerTestSpec.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/ExtractorTestSpec.scala create mode 100644 src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala diff --git a/examples/python/data-preprocessing/SparkNLP_Cleaner_Demo.ipynb b/examples/python/data-preprocessing/SparkNLP_Cleaner_Demo.ipynb new file mode 100644 index 00000000000000..f55ee760c3a8f5 --- /dev/null +++ b/examples/python/data-preprocessing/SparkNLP_Cleaner_Demo.ipynb @@ -0,0 +1,1004 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_Cleaner_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "1b585db2-ed1b-4417-b38a-033812c206c3", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Introducing Cleaner in SparkNLP\n", + "This notebook showcases the newly added `Cleaner()` annotator in Spark NLP to remove unnecessary or undesirable content from datasets, such as bullets, dashes, and non-ASCII characters, enhancing data consistency and readability." + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "68382b5d-51f1-44fc-a913-16b92e44d1ee", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "DczWop6QeE8F", + "outputId": "ac97c962-bad5-4d71-d823-da1c67580219" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "Apache Spark version: 3.4.1\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n", + "\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "c84cecef-45dc-4169-986c-30c9a6e42377", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading html files was introduced in Spark NLP 6.0.0. Please make sure you have upgraded to the latest Spark NLP release.\n", + "We simple need to import the cleaners components to use `Cleaner` annotator:" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "596ffcc0-90fb-4bfd-8840-88be66f7bb6a", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "stirVdLP-ASE" + }, + "outputs": [], + "source": [ + "from sparknlp.annotator.cleaners import *" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "7c528b73-797c-40fe-a0a9-5e9b1d72f4fd", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "EoFI66NAdalE" + }, + "source": [ + "## Cleaning data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "7a29210a-143a-4fcd-a62f-9b3403f8d3c0", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "BjAsd5Gs8drv" + }, + "source": [ + "Clean a string with bytes to output a string with human visible characters" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "4f27952c-611f-47c7-8d7a-6e9075270eea", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "bAkMjJ1vdalE" + }, + "outputs": [], + "source": [ + "data = \"Hello รฐ\\\\x9f\\\\x98\\\\x80\"\n", + "data_set = spark.createDataFrame([[data]]).toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "8bd0e20a-aae0-46fe-89e0-b4020b7f618d", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OnxOTj_Uf3a0", + "outputId": "cc841020-4e5e-4b64-e6fc-ed82cee5dce3" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------------------------------+\n", + "|cleaned |\n", + "+---------------------------------+\n", + "|[{chunk, 0, 8, Hello ๐Ÿ˜€, {}, []}]|\n", + "+---------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from sparknlp.annotator import *\n", + "from sparknlp.base import *\n", + "\n", + "document_assembler = DocumentAssembler().setInputCol(\"text\").setOutputCol(\"document\")\n", + "\n", + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"bytes_string_to_string\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "a729dcd7-b8bf-4356-96e2-199c0576dd5e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "dpohooB0_yOa" + }, + "source": [ + "Cleaning special characters from a screen" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "0bcd1ac3-8b9e-4b8c-84f6-031753f3e205", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "OC_PElzuAKZw" + }, + "outputs": [], + "source": [ + "data = [\n", + " \"โ— An excellent point!\",\n", + " \"ITEM 1A: RISK-FACTORS\"\n", + "]\n", + "\n", + "data_set = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "8f78cc6f-95bc-434e-af72-f315c8f531a1", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8ESl4yUL_2WR", + "outputId": "a22fa5dd-09d8-4b40-e84e-adc5cc047696" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-----------------------------------------------+\n", + "|cleaned |\n", + "+-----------------------------------------------+\n", + "|[{chunk, 0, 19, An excellent point!, {}, []}] |\n", + "|[{chunk, 0, 21, ITEM 1A: RISK FACTORS, {}, []}]|\n", + "+-----------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"clean\") \\\n", + " .setBullets(True) \\\n", + " .setExtraWhitespace(True) \\\n", + " .setDashes(True)\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "3676aa02-945e-486d-8026-0f56a2ecb0ac", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "Hqm_ttjEAUaH" + }, + "source": [ + "Clean non-ascii characters" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "d502021d-9668-4bb6-9b3a-262c9958aea7", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "WB0bI47xAlIr" + }, + "outputs": [], + "source": [ + "data = [\"\\\\x88This text contains ยฎnon-ascii characters!โ—\"]\n", + "data_set = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "9ef30555-9c58-492c-af19-94a4062514b2", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "YykeYZltAXQX", + "outputId": "edd53be2-df90-4e77-dd18-930fcadfe5d0" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------------+\n", + "|cleaned |\n", + "+------------------------------------------------------------------+\n", + "|[{chunk, 0, 40, This text contains non-ascii characters!, {}, []}]|\n", + "+------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"clean_non_ascii_chars\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "0a0c55c6-6ce7-4673-aac8-81ad8fc341ea", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "YPeqQL-UA17w" + }, + "source": [ + "Cleaning alphanumeric bullets from the beginning of a text" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "f4b6bb1a-31e7-4a54-b887-5ebb44afbee0", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "10_a1O9cA4Tk" + }, + "outputs": [], + "source": [ + "data = [(\"1.1 This is a very important point\",),\n", + " (\"a.1 This is a very important point\",),\n", + " (\"1.4.2 This is a very important point\",)]\n", + "\n", + "data_set = spark.createDataFrame(data).toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "52d71553-6f73-4eb9-9621-3e222ce10490", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "JbOmybPLA_nV", + "outputId": "e53a283d-c4c4-471d-b1ef-0df2cf9bc9d1" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+--------------------------------------------------------+\n", + "|cleaned |\n", + "+--------------------------------------------------------+\n", + "|[{chunk, 0, 30, This is a very important point, {}, []}]|\n", + "|[{chunk, 0, 30, This is a very important point, {}, []}]|\n", + "|[{chunk, 0, 30, This is a very important point, {}, []}]|\n", + "+--------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"clean_ordered_bullets\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "a297a9ad-c715-4360-b85d-2183a08b6d33", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "EV4Wpr_qBFm1" + }, + "source": [ + "Clean postfix from a text based on a pattern" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "f6647dcf-e29b-48a3-81c3-b135b4e07950", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "UQxqmsFgBTw7" + }, + "outputs": [], + "source": [ + "data = [\"The end! END\"]\n", + "\n", + "data_set = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "0359b9bd-b3d0-4eb3-8d0a-a5c2f72a04c7", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "AK_kwa4SBHZL", + "outputId": "a50cef0f-8be6-4139-8dad-52ffc4933322" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------------------------------+\n", + "|cleaned |\n", + "+---------------------------------+\n", + "|[{chunk, 0, 8, The end!, {}, []}]|\n", + "+---------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"clean_postfix\") \\\n", + " .setCleanPrefixPattern(\"(END|STOP)\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "ff4acecb-0da2-43c2-a911-def8ddade7da", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "w9bBC9ebBgvi" + }, + "source": [ + "Clean prefix from a text based on a pattern" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "100dd0cd-9430-4c27-aaf6-f0efa79b8328", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "nDfwOWkEBjv4" + }, + "outputs": [], + "source": [ + "data = [\"SUMMARY: This is the best summary of all time!\"]\n", + "\n", + "data_set = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "12358399-be76-4312-9a74-b34752b07dc5", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "qaVxWBT-C9eS", + "outputId": "73bb7cb7-36d1-4168-9f3f-adecbb61b615" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------------------------------------------------------------+\n", + "|cleaned |\n", + "+---------------------------------------------------------------+\n", + "|[{chunk, 0, 37, This is the best summary of all time!, {}, []}]|\n", + "+---------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"clean_prefix\") \\\n", + " .setCleanPrefixPattern(\"(SUMMARY|DESCRIPTION):\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "a3c0b2fa-e0c1-4b99-ba32-3b736de782a9", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "ZJBz2_ZTGL82" + }, + "source": [ + "Cleaning unicode characters from a text" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "4652c85c-56de-4f2c-8586-905bd792d20c", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "iGZEspw1GR6Q" + }, + "outputs": [], + "source": [ + "data = [\n", + " \"\\x93A lovely quote!\\x94\",\n", + " \"\\x91A lovely quote!\\x92\",\n", + " \"\"\"\\u201CA lovely quote!\\u201D โ€” with a dash\"\"\"\n", + "]\n", + "\n", + "data_set = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "5fa14989-2657-4b52-9b29-e6f6aa340a02", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "mm0FrFtBGqBQ", + "outputId": "49697b57-2fa7-4407-93d3-6bb1e7aa2941" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------------------------------------------------------+\n", + "|cleaned |\n", + "+---------------------------------------------------------+\n", + "|[{chunk, 0, 17, โ€œA lovely quote!โ€, {}, []}] |\n", + "|[{chunk, 0, 17, โ€˜A lovely quote!โ€™, {}, []}] |\n", + "|[{chunk, 0, 31, ?A lovely quote!? ? with a dash, {}, []}]|\n", + "+---------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"replace_unicode_characters\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "0d945d2d-c426-49ce-b755-12f0c497c38e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "NdV4paKp6fwM" + }, + "source": [ + "### Translator" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "b882f749-fc63-498a-a111-efae9455b12f", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "UGMZ5puuKzcP" + }, + "source": [ + "You can use `Cleaner` annotator to even translate a text " + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "5119cc76-cc42-475c-b8f5-26b5811e0596", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "7GuykSrsK04V" + }, + "outputs": [], + "source": [ + "data = [\"This should go to French\"]\n", + "data_set = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "a1342379-a8a3-4ffc-bc9c-64a3a36d6504", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "yX1no37ALAPO", + "outputId": "9b1cf6c0-2640-474a-a933-1428c1ae40c1" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "opus_mt_en_fr download started this may take some time.\n", + "Approximate size to download 378.7 MB\n", + "\r", + "[ | ]\r", + "[ / ]\r", + "[ โ€” ]\r", + "[ \\ ]\r", + "[ | ]\r", + "[ / ]\r", + "[ โ€” ]\r", + "[ \\ ]\r", + "[ | ]\r", + "[ / ]\r", + "[ โ€” ]\r", + "[ \\ ]\r", + "[ | ]\r", + "[ / ]\r", + "[ โ€” ]\r", + "[ \\ ]\r", + "[ | ]\r", + "[ / ]\r", + "[ โ€” ]\r", + "[ \\ ]\r", + "[ | ]\r", + "[ / ]\r", + "[ โ€” ]\r", + "[ \\ ]\r", + "[ | ]\r", + "[OK!]\n", + "+-----------------------------------------------------------------------+\n", + "|cleaned |\n", + "+-----------------------------------------------------------------------+\n", + "|[{document, 0, 28, ร‡a devrait aller en franรงais., {sentence -> 0}, []}]|\n", + "+-----------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "cleaner = Cleaner() \\\n", + " .pretrained() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"cleaned\").show(truncate=False)" + ] + } + ], + "metadata": { + "application/vnd.databricks.v1+notebook": { + "computePreferences": null, + "dashboards": [], + "environmentMetadata": null, + "language": "python", + "notebookMetadata": { + "pythonIndentUnit": 4 + }, + "notebookName": "SparkNLP_Cleaner_Demo", + "widgets": {} + }, + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/python/data-preprocessing/SparkNLP_Email_Data_Preparation.ipynb b/examples/python/data-preprocessing/SparkNLP_Email_Data_Preparation.ipynb new file mode 100644 index 00000000000000..5f256c363b4bbe --- /dev/null +++ b/examples/python/data-preprocessing/SparkNLP_Email_Data_Preparation.ipynb @@ -0,0 +1,446 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_Email_Data_Preparation.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Data Preparation with SparkNLP\n", + "This notebook demonstrates how to leverage the new `read()` component in Spark NLP alongside the `Cleaner` or `Extractor` annotators to efficiently preprocess your data before feeding it into an NLP model.\n", + "\n", + "Incorporating this preprocessing step into your pipeline is highly recommended, as it can significantly enhance the quality and performance of your NLP model." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading email files was introduced in Spark NLP 5.5.2, while `Cleaner` and `Extractor` annotators was introduced in Spark NLP 6.0.0.\n", + "Please make sure you have upgraded to the latest Spark NLP release." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "tc9FU1dr7RYd" + }, + "source": [ + "- Let's install and setup Spark NLP in Google Colab\n", + "- This part is pretty easy via our simple script" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "iR1g7FYu7cjv" + }, + "outputs": [], + "source": [ + "! wget -q http://setup.johnsnowlabs.com/colab.sh -O - | bash" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TDGhekmq7dtF" + }, + "source": [ + "### Additional Configuration for Databricks" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dtVukFk48DAd" + }, + "source": [ + "When running on Databricks, it is necessary to include the following Spark configurations to avoid dependency conflicts:\n", + "\n", + "- `spark.driver.userClassPathFirst true`\n", + "- `spark.executor.userClassPathFirst true`\n", + "\n", + "These configurations are required because the Databricks runtime environment includes a bundled version of the `com.sun.mail:jakarta.mail` library, which conflicts with `jakarta.activation`. By setting these properties, the application ensures that the user-provided libraries take precedence over those bundled in the Databricks environment, resolving the dependency conflict." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BZS99lKh7T3l" + }, + "source": [ + "For local files example we will download a couple of email files from Spark NLP Github repo:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "ya8qZe00dalC", + "outputId": "3d525daf-047e-4fbf-cf9a-cb7f3f4683f1" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2025-02-12 20:07:48-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1093-Adding-support-to-read-Email-files/src/test/resources/reader/email/email-text-attachments.eml\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.110.133, 185.199.108.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 3175 (3.1K) [text/plain]\n", + "Saving to: โ€˜email-files/email-text-attachments.emlโ€™\n", + "\n", + "\r", + " email-tex 0%[ ] 0 --.-KB/s \r", + "email-text-attachme 100%[===================>] 3.10K --.-KB/s in 0s \n", + "\n", + "2025-02-12 20:07:48 (43.7 MB/s) - โ€˜email-files/email-text-attachments.emlโ€™ saved [3175/3175]\n", + "\n", + "--2025-02-12 20:07:48-- https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1093-Adding-support-to-read-Email-files/src/test/resources/reader/email/test-several-attachments.eml\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 1324361 (1.3M) [text/plain]\n", + "Saving to: โ€˜email-files/test-several-attachments.emlโ€™\n", + "\n", + "test-several-attach 100%[===================>] 1.26M --.-KB/s in 0.06s \n", + "\n", + "2025-02-12 20:07:49 (19.6 MB/s) - โ€˜email-files/test-several-attachments.emlโ€™ saved [1324361/1324361]\n", + "\n" + ] + } + ], + "source": [ + "!mkdir email-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1093-Adding-support-to-read-Email-files/src/test/resources/reader/email/email-text-attachments.eml -P email-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1093-Adding-support-to-read-Email-files/src/test/resources/reader/email/test-several-attachments.eml -P email-files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "3xgGItNbU2DZ", + "outputId": "b65902f6-345f-477b-d59f-5853ef61a177" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 1.3M\n", + "-rw-r--r-- 1 root root 3.2K Feb 12 20:07 email-text-attachments.eml\n", + "-rw-r--r-- 1 root root 1.3M Feb 12 20:07 test-several-attachments.eml\n" + ] + } + ], + "source": [ + "!ls -lh ./email-files" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EoFI66NAdalE" + }, + "source": [ + "## Parsing Email from Local Files\n", + "Use the `email()` method to parse email content from local directories." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bAkMjJ1vdalE", + "outputId": "f6eefd3e-da98-4636-d93b-052f0dcfe219" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|email |\n", + "+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{Title, Email Text Attachments, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano }}, {NarrativeText, Email test with two text attachments\\r\\n\\r\\nCheers,\\r\\n\\r\\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain}}, {NarrativeText, \\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\nEmail  test with two text attachments\\r\\n

    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\nCheers,
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n\\r\\n\\r\\n, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/html}}, {Attachment, filename.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name=\"filename.txt\"}}, {Attachment, filename2.txt, {sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name=\"filename2.txt\"}}] |\n", + "|[{Title, Test Several Attachments, {sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , cc_to -> Danilo Burbano }}, {NarrativeText, This is only a test email with attachments to verify EmailReader feature in Spark NLP.\\r\\n\\r\\nYou don't need to reply to this message ๐Ÿ™‚\\r\\n\\r\\n\\r\\n, {sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , mimeType -> text/plain, cc_to -> Danilo Burbano }}, {NarrativeText, \\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n
    \\r\\nThis is only a test email with attachments to verify EmailReader feature in Spark NLP.
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\nYou don't need to reply to this message ๐Ÿ™‚ 
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n
    \\r\\n\\r\\n\\r\\n, {sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , mimeType -> text/html, cc_to -> Danilo Burbano }}, {Attachment, filename.txt, {sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , contentType -> text/plain; name=\"filename.txt\", cc_to -> Danilo Burbano }}, {Attachment, SparkNLP Email Reader.pdf, {sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , contentType -> application/pdf; name=\"SparkNLP Email Reader.pdf\", cc_to -> Danilo Burbano }}, {Attachment, SparkNLP 3D Logo v2.png, {sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , contentType -> image/png; name=\"SparkNLP 3D Logo v2.png\", cc_to -> Danilo Burbano }}]|\n", + "+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "email_df = sparknlp.read().email(\"./email-files\")\n", + "\n", + "email_df.select(\"email\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_5smLr4XmcsY" + }, + "source": [ + "Let's check the schema for this Dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "fht7jtiG0A3W", + "outputId": "f4a63156-ddd0-466f-ed0f-6d98627ff925" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- path: string (nullable = true)\n", + " |-- content: binary (nullable = true)\n", + " |-- email: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- elementType: string (nullable = true)\n", + " | | |-- content: string (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + "\n" + ] + } + ], + "source": [ + "email_df.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "06SvFW1Rl285" + }, + "source": [ + "As seen in the schema and output, we have the email information along with metadata that can be used to filter and sanitize the data. Let's take a closer look at the metadata for this email data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "xH9UqFE00pDe", + "outputId": "7b6dfe5f-6e4a-4a25-ad6d-69b58e716c2b" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|email_exploded |\n", + "+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|{sent_to -> Danilo Burbano , sent_from -> Danilo Burbano } |\n", + "|{sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/plain} |\n", + "|{sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , mimeType -> text/html} |\n", + "|{sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name=\"filename.txt\"} |\n", + "|{sent_to -> Danilo Burbano , sent_from -> Danilo Burbano , contentType -> text/plain; name=\"filename2.txt\"} |\n", + "|{sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , cc_to -> Danilo Burbano } |\n", + "|{sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , mimeType -> text/plain, cc_to -> Danilo Burbano } |\n", + "|{sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , mimeType -> text/html, cc_to -> Danilo Burbano } |\n", + "|{sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , contentType -> text/plain; name=\"filename.txt\", cc_to -> Danilo Burbano } |\n", + "|{sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , contentType -> application/pdf; name=\"SparkNLP Email Reader.pdf\", cc_to -> Danilo Burbano }|\n", + "|{sent_to -> Maziyar Panahi , sent_from -> Danilo Burbano , contentType -> image/png; name=\"SparkNLP 3D Logo v2.png\", cc_to -> Danilo Burbano } |\n", + "+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from pyspark.sql.functions import col, explode\n", + "\n", + "email_matadata_df = email_df.withColumn(\"email_metadata\", explode(col(\"email.metadata\")))\n", + "email_matadata_df.select(\"email_metadata\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6YeiszZSnMZU" + }, + "source": [ + "In this example, we are not interested in results containing HTML data, so we will focus only on plain text." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "aQqqlUIEXMhF", + "outputId": "ab47ae69-1c00-4fe4-d5cc-abd762c65d1e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|path |narrative_text |\n", + "+------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|file:/content/email-files/email-text-attachments.eml |Email test with two text attachments\\r\\n\\r\\nCheers,\\r\\n\\r\\n |\n", + "|file:/content/email-files/test-several-attachments.eml|This is only a test email with attachments to verify EmailReader feature in Spark NLP.\\r\\n\\r\\nYou don't need to reply to this message ๐Ÿ™‚\\r\\n\\r\\n\\r\\n|\n", + "+------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from pyspark.sql.functions import col, explode\n", + "\n", + "#Filter out only NarrativeText elements and text/plain content from the email array\n", + "narrative_email_df = email_df.selectExpr(\n", + " \"path\",\n", + " \"FILTER(email, x -> x.elementType = 'NarrativeText' AND x.metadata['mimeType'] = 'text/plain') AS narrative_email\"\n", + ")\n", + "\n", + "exploded_df = narrative_email_df.withColumn(\"email_exploded\", explode(col(\"narrative_email\")))\n", + "\n", + "#Select only the content field from the exploded struct\n", + "email_content_df = exploded_df.select(\n", + " \"path\",\n", + " col(\"email_exploded.content\").alias(\"narrative_text\")\n", + ")\n", + "\n", + "email_content_df.show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Fno3A-itndVO" + }, + "source": [ + "Now, we can use `Cleaner` annotator to remove any remaining undesired characters from the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "yzLMr8jvT4w4", + "outputId": "9774f95b-b2e3-48db-947c-30318f3e78bf" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|cleaned |\n", + "+------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{chunk, 0, 44, Email test with two text attachments Cheers,, {}, []}] |\n", + "|[{chunk, 0, 129, This is only a test email with attachments to verify EmailReader feature in Spark NLP. You don't need to reply to this message ๐Ÿ™‚, {}, []}]|\n", + "+------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from sparknlp.base import *\n", + "from sparknlp.annotator.cleaners import *\n", + "\n", + "document_assembler = DocumentAssembler() \\\n", + " .setInputCol(\"narrative_text\") \\\n", + " .setOutputCol(\"document\")\n", + "\n", + "cleaner = Cleaner() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"cleaned\") \\\n", + " .setCleanerMode(\"clean\") \\\n", + " .setBullets(True) \\\n", + " .setExtraWhitespace(True) \\\n", + " .setDashes(True)\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " cleaner\n", + "])\n", + "\n", + "model = pipeline.fit(email_content_df)\n", + "clean_email_content_df = model.transform(email_content_df)\n", + "clean_email_content_df.select(\"cleaned\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Qtttw-LbC9I5" + }, + "source": [ + "Now, you have your enhanced text ready to feed into an NLP model for improved performance." + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/python/data-preprocessing/SparkNLP_Extractor_Demo.ipynb b/examples/python/data-preprocessing/SparkNLP_Extractor_Demo.ipynb new file mode 100644 index 00000000000000..58fd23df94a0ff --- /dev/null +++ b/examples/python/data-preprocessing/SparkNLP_Extractor_Demo.ipynb @@ -0,0 +1,1387 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/reader/SparkNLP_Extractor_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "0d4a5cfc-53fe-4996-a290-4dedb2ffdbf8", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "tzcU5p2gdak9" + }, + "source": [ + "# Introducing Extractor in SparkNLP\n", + "This notebook showcases the newly added `Extractor()` annotator in Spark NLP enabling seamless extraction of key information (e.g., dates, emails, IP addresses) from various data sources such as `.eml` files. This simplifies data parsing workflows by isolating relevant details automatically." + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "53dbab4c-5f20-4dc0-aaeb-5a6f7289768e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "DczWop6QeE8F", + "outputId": "3634f091-1da2-4013-bbe8-4abdcef6d0c5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning::Spark Session already created, some configs may not take.\n", + "Apache Spark version: 3.4.1\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "# let's start Spark with Spark NLP\n", + "spark = sparknlp.start()\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "593ff948-8109-4ea8-a21a-d1ee153150bf", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "RFOFhaEedalB" + }, + "source": [ + "## Setup and Initialization\n", + "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", + "\n", + "Support for reading html files was introduced in Spark NLP 6.0.0. Please make sure you have upgraded to the latest Spark NLP release.\n", + "We simple need to import the cleaners components to use `Extractor` annotator:" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "4d27fe1e-e91e-4388-be7d-c6fc7229e8c5", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "stirVdLP-ASE" + }, + "outputs": [], + "source": [ + "from sparknlp.annotator.cleaners import *" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "71ca7bde-fb68-4ea0-855f-6931400b096f", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "EoFI66NAdalE" + }, + "source": [ + "## Extracting data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "49b2c5d2-991b-4e01-a639-7de15f5f1148", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "BjAsd5Gs8drv" + }, + "source": [ + "Extracting information from eml data" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "911c7225-8760-4e98-9878-7782ecf9d972", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "bAkMjJ1vdalE" + }, + "outputs": [], + "source": [ + "eml_data = \"\"\"from ABC.DEF.local ([ba23::58b5:2236:45g2:88h2]) by\n", + " \\n ABC.DEF.local2 ([ba23::58b5:2236:45g2:88h2%25]) with mapi id\\\n", + " n 32.88.5467.123; Fri, 26 Mar 2021 11:04:09 +1200\"\"\"\n", + "\n", + "data_set = spark.createDataFrame([[eml_data]]).toDF(\"text\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "eef12f80-6a1d-46aa-80d4-1e7c83308af6", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "DZ3tHeJM_wnD" + }, + "source": [ + "Extracting date" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "a56f1f7b-1aa4-431a-924b-fc2c94a0066c", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "OnxOTj_Uf3a0", + "outputId": "bfb8bcaa-b9ca-43c7-d8bf-ca1a80808b4e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------+\n", + "|date |\n", + "+------------------------------------------------------------+\n", + "|[{chunk, 136, 166, Fri, 26 Mar 2021 11:04:09 +1200, {}, []}]|\n", + "+------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "from sparknlp.annotator import *\n", + "from sparknlp.base import *\n", + "\n", + "document_assembler = DocumentAssembler().setInputCol(\"text\").setOutputCol(\"document\")\n", + "\n", + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"date\") \\\n", + " .setExtractorMode(\"email_date\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"date\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "59beb7a2-243f-4888-a610-c785358ab739", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "dpohooB0_yOa" + }, + "source": [ + "Extracting email addresses" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "8c1ac427-2fe9-417b-a811-ee939c6f1c9b", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "OC_PElzuAKZw" + }, + "outputs": [], + "source": [ + "eml_data = [\n", + " \"Me me@email.com and You \\n ([ba23::58b5:2236:45g2:88h2]) (10.0.2.01)\",\n", + " \"Im Rabn \"\n", + "]\n", + "\n", + "data_set = spark.createDataFrame(eml_data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "9a96a56b-3b96-41a9-a090-278ab22fb2ac", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8ESl4yUL_2WR", + "outputId": "e40cf1f5-df1b-45b3-c663-ce5fc5d789a7" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------------------------+\n", + "|email |\n", + "+------------------------------------------------------------------------------+\n", + "|[{chunk, 3, 14, me@email.com, {}, []}, {chunk, 25, 37, You@email.com, {}, []}]|\n", + "|[{chunk, 9, 26, Im.Rabn@npf.gov.nr, {}, []}] |\n", + "+------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"email\") \\\n", + " .setExtractorMode(\"email_address\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"email\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "74edbb08-3fe5-47d8-9884-c22b1bd1dec3", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "Hqm_ttjEAUaH" + }, + "source": [ + "Extracting IPv4 and IPv6 addresses" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "a33d7eaf-d87d-47f9-832d-4ff6aa967210", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "WB0bI47xAlIr" + }, + "outputs": [], + "source": [ + "eml_data = [\n", + " \"\"\"from ABC.DEF.local ([ba23::58b5:2236:45g2:88h2]) by\n", + " ABC.DEF.local ([68.183.71.12]) with mapi id\n", + " 32.88.5467.123; Fri, 26 Mar 2021 11:04:09 +1200\"\"\"\n", + "]\n", + "\n", + "data_set = spark.createDataFrame(eml_data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "36cf18e8-7395-41be-a08e-d37495842685", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "YykeYZltAXQX", + "outputId": "f250f242-098c-4766-e2d7-dfb6bfff08de" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------------------------------------------------------------------------------------+\n", + "|ip_address |\n", + "+-------------------------------------------------------------------------------------------+\n", + "|[{chunk, 21, 45, ba23::58b5:2236:45g2:88h2, {}, []}, {chunk, 72, 83, 68.183.71.12, {}, []}]|\n", + "+-------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"ip_address\") \\\n", + " .setExtractorMode(\"ip_address\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"ip_address\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "dae9271c-5d0f-45da-a2b9-35c7b60db5e0", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "YPeqQL-UA17w" + }, + "source": [ + "Extracting MAPI IDs" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "e8463ec7-46d4-4e2d-8cbd-7ff9ba3bb207", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "10_a1O9cA4Tk" + }, + "outputs": [], + "source": [ + "eml_data = \"\"\"from ABC.DEF.local ([ba23::58b5:2236:45g2:88h2]) by\n", + " \\n ABC.DEF.local2 ([ba23::58b5:2236:45g2:88h2%25]) with mapi id\\\n", + " n 32.88.5467.123; Fri, 26 Mar 2021 11:04:09 +1200\"\"\"\n", + "\n", + "data_set = spark.createDataFrame([[eml_data]]).toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "f34e1e39-b8ac-45fe-931a-71de97e6c178", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "JbOmybPLA_nV", + "outputId": "bf150f95-88d5-42db-8038-b4a44920cfd5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------------------------------------+\n", + "|mapi_id |\n", + "+-------------------------------------------+\n", + "|[{chunk, 120, 133, 32.88.5467.123, {}, []}]|\n", + "+-------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"mapi_id\") \\\n", + " .setExtractorMode(\"mapi_id\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"mapi_id\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "2c492045-9883-48a9-a452-cf646b55d4bd", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "EV4Wpr_qBFm1" + }, + "source": [ + "Extracting US phone number" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "42356372-2e35-455b-8f88-a7c6f9320584", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "UQxqmsFgBTw7" + }, + "outputs": [], + "source": [ + "data = [\n", + " \"215-867-5309\",\n", + " \"Phone Number: +1 215.867.5309\",\n", + " \"Phone Number: Just Kidding\"\n", + "]\n", + "\n", + "test_df = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "4570b13f-7195-4720-8f22-e1da4de3e140", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "AK_kwa4SBHZL", + "outputId": "ae506a88-6010-40d0-b580-2d73d171e498" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------+\n", + "|us_phones |\n", + "+------------------------------------------+\n", + "|[{chunk, 0, 11, 215-867-5309, {}, []}] |\n", + "|[{chunk, 14, 28, +1 215.867.5309, {}, []}]|\n", + "|[] |\n", + "+------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"us_phones\") \\\n", + " .setExtractorMode(\"us_phone_numbers\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(test_df)\n", + "result = model.transform(test_df)\n", + "result.select(\"us_phones\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "6bc9dcf3-70b3-4054-999b-37aea18af833", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "w9bBC9ebBgvi" + }, + "source": [ + "Extracting bullets from text" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "7fcdcbe1-bec5-49db-b94a-168ef0f9107b", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "nDfwOWkEBjv4" + }, + "outputs": [], + "source": [ + "data = [\n", + " \"1. Introduction:\",\n", + " \"a. Introduction:\",\n", + " \"5.3.1 Convolutional Networks\",\n", + " \"D.b.C Recurrent Neural Networks\",\n", + " \"2.b.1 Recurrent Neural Networks\",\n", + " \"bb.c Feed Forward Neural Networks\",\n", + " \"Fig. 2: The relationship\"\n", + "]\n", + "\n", + "test_df = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "ebf27f07-e422-4f1d-8c3f-579271096a9e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "qaVxWBT-C9eS", + "outputId": "86c71467-9b54-4acd-985b-af90e6cc075d" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------------------------------+\n", + "|bullets |\n", + "+------------------------------------------------------------------------------------+\n", + "|[{chunk, 0, 2, (1,None,None), {section -> 1}, []}] |\n", + "|[{chunk, 0, 2, (a,None,None), {section -> a}, []}] |\n", + "|[{chunk, 0, 5, (5,3,1), {section -> 5, sub_section -> 3, sub_sub_section -> 1}, []}]|\n", + "|[{chunk, 0, 5, (D,b,C), {section -> D, sub_section -> b, sub_sub_section -> C}, []}]|\n", + "|[{chunk, 0, 5, (2,b,1), {section -> 2, sub_section -> b, sub_sub_section -> 1}, []}]|\n", + "|[{chunk, 0, 4, (bb,c,None), {section -> bb, sub_section -> c}, []}] |\n", + "|[{chunk, 0, 0, (None,None,None), {}, []}] |\n", + "+------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"bullets\") \\\n", + " .setExtractorMode(\"bullets\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(test_df)\n", + "result = model.transform(test_df)\n", + "result.select(\"bullets\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "db36280f-40f6-4d4b-808d-545d2d88f6a6", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "ZJBz2_ZTGL82" + }, + "source": [ + "Extract image from URLS" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "76860b07-7b87-4396-b886-12e0ce4d646e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "iGZEspw1GR6Q" + }, + "outputs": [], + "source": [ + "data = [\n", + " \"https://my-image.png with some text\",\n", + " \"some text https://my-image.jpg with another http://my-image.bmp\",\n", + " \"http://my-path/my%20image.JPG\",\n", + " \"\"\"\n", + " \n", + " \"\"\"\n", + "]\n", + "\n", + "test_df = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "48bc7615-869b-4e65-81a3-e5d7efd58371", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "mm0FrFtBGqBQ", + "outputId": "4e206112-afe6-4bc1-fd4f-538b3373fb90" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------------------------------------------------------------------------------------------------------------------------+\n", + "|image_urls |\n", + "+-------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{chunk, 0, 19, https://my-image.png, {}, []}] |\n", + "|[{chunk, 10, 29, https://my-image.jpg, {}, []}, {chunk, 44, 62, http://my-image.bmp, {}, []}] |\n", + "|[{chunk, 0, 28, http://my-path/my%20image.JPG, {}, []}] |\n", + "|[{chunk, 10, 46, https://example.com/images/photo1.jpg, {}, []}, {chunk, 66, 100, https://example.org/assets/icon.png, {}, []}]|\n", + "+-------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"image_urls\") \\\n", + " .setExtractorMode(\"image_urls\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(test_df)\n", + "result = model.transform(test_df)\n", + "result.select(\"image_urls\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "57717be0-06be-451c-bdde-ca67cad1fab5", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "UGMZ5puuKzcP" + }, + "source": [ + "Extract text after" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "3de7cf0f-0897-4aae-812f-3808a585b2c4", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "7GuykSrsK04V" + }, + "outputs": [], + "source": [ + "data = [\"SPEAKER 1: Look at me, I'm flying!\"]\n", + "\n", + "test_df = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "b3fcf79f-c9e1-429c-8f63-60b2d0553dcd", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "yX1no37ALAPO", + "outputId": "4b6e9f5f-1b0d-4eac-ac07-b794db799565" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------+\n", + "|text_after |\n", + "+------------------------------------------------------------+\n", + "|[{chunk, 10, 34, Look at me, I'm flying!, {index -> 0}, []}]|\n", + "+------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"text_after\") \\\n", + " .setExtractorMode(\"text_after\") \\\n", + " .setTextPattern(\"SPEAKER \\\\d{1}:\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(test_df)\n", + "result = model.transform(test_df)\n", + "result.select(\"text_after\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "b0045ce9-8a27-4f50-95eb-054f5579ed91", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "ogDxF5DlLJvT" + }, + "source": [ + "Extract text before" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "1cc76379-f6a2-4c07-b2ec-86a551395d0d", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "zPiLiuh1LLC8" + }, + "outputs": [], + "source": [ + "data = [\"Here I am! STOP Look at me! STOP I'm flying! STOP\"]\n", + "\n", + "test_df = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "d989d7c7-1891-420d-9b96-f34541b5f50e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "jBBRy0hZLPz2", + "outputId": "cef60d80-3985-427a-c4b3-ebb7e54eb9bd" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+----------------------------------------------+\n", + "|text_before |\n", + "+----------------------------------------------+\n", + "|[{chunk, 0, 11, Here I am!, {index -> 0}, []}]|\n", + "+----------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"text_before\") \\\n", + " .setExtractorMode(\"text_before\") \\\n", + " .setTextPattern(\"STOP\")\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(test_df)\n", + "result = model.transform(test_df)\n", + "result.select(\"text_before\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "764fa826-06c1-4309-a5b1-e279d6a5e0a2", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "SNzyE7rmLgL4" + }, + "source": [ + "## Custom Patterns" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "1d15170a-3cba-48c2-9728-f03ea388ec60", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "OxSYMMORLrsz" + }, + "source": [ + "As you can see in the output of the example above. We have by default patterns to extract most common data. However, you can also set custom regex patterns to address your specific extraction needs." + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "faecf90c-552f-42c8-b1aa-3e50da7e7b6c", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "Be0VrtdjLmAa" + }, + "outputs": [], + "source": [ + "eml_data = [\n", + " \"\"\"from ABC.DEF.local ([ba23::58b5:2236:45g2:88h2]) by\n", + " ABC.DEF.local ([68.183.71.12]) with mapi id\n", + " 32.88.5467.123; Fri, 26 Mar 2021 11:04:09 +1200\"\"\"\n", + "]\n", + "\n", + "data_set = spark.createDataFrame(eml_data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "0aa79ac9-c404-4901-a792-b5cb5ff33659", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_6Gi_PuvMU5x", + "outputId": "0318a824-cde1-4404-d2bf-d9859a6a4eb6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------------------------------------+\n", + "|ipv4_address |\n", + "+---------------------------------------+\n", + "|[{chunk, 72, 83, 68.183.71.12, {}, []}]|\n", + "+---------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "my_ipv4_regex = \"(?:25[0-5]|2[0-4]\\\\d|1\\\\d{2}|[1-9]?\\\\d)(?:\\\\.(?:25[0-5]|2[0-4]\\\\d|1\\\\d{2}|[1-9]?\\\\d)){3}\"\n", + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"ipv4_address\") \\\n", + " .setExtractorMode(\"ip_address\") \\\n", + " .setIpAddressPattern(my_ipv4_regex)\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(data_set)\n", + "result = model.transform(data_set)\n", + "result.select(\"ipv4_address\").show(truncate=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "ada7ab5b-5773-49cf-bac4-c86668e62343", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "H05hbWuQOuTA" + }, + "source": [ + "Index in After and Before text" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "f83245f5-4070-4a67-8aaf-11571c1e7ead", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "ihzYu3qhfrQ9" + }, + "source": [ + "The `index` parameter tells the `Extractor` which occurrence of the specified `text pattern` should be used as the reference point for extracting text. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "bacb0658-0658-49b6-9811-96d593af27e1", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "id": "815xRlXsOwfP" + }, + "outputs": [], + "source": [ + "data = [\"Teacher: BLAH BLAH BLAH; Student: BLAH BLAH BLAH!\"]\n", + "\n", + "test_df = spark.createDataFrame(data, \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "a8fb680b-5ff6-47cb-a0fa-dd0a473e10b4", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Rd0_m1D8O_BY", + "outputId": "d6295c30-6e96-4c8c-cd5e-1df5d53238a5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------------------------------------------+\n", + "|text_before |\n", + "+-------------------------------------------------+\n", + "|[{chunk, 0, 14, Teacher: BLAH, {index -> 1}, []}]|\n", + "+-------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"text_before\") \\\n", + " .setExtractorMode(\"text_before\") \\\n", + " .setTextPattern(\"BLAH\") \\\n", + " .setIndex(1)\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(test_df)\n", + "result = model.transform(test_df)\n", + "result.select(\"text_before\").show(truncate=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 0, + "metadata": { + "application/vnd.databricks.v1+cell": { + "cellMetadata": { + "byteLimit": 2048000, + "rowLimit": 10000 + }, + "inputWidgets": {}, + "nuid": "c489beb3-caaf-44f4-8f66-736ae50fb95e", + "showTitle": false, + "tableResultSettingsMap": {}, + "title": "" + }, + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "IIwNAetLYUYN", + "outputId": "6eb8da1e-c0b8-4966-d621-34570d7949b3" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-------------------------------------------+\n", + "|text_before |\n", + "+-------------------------------------------+\n", + "|[{chunk, 0, 9, Teacher:, {index -> 0}, []}]|\n", + "+-------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "extractor = Extractor() \\\n", + " .setInputCols([\"document\"]) \\\n", + " .setOutputCol(\"text_before\") \\\n", + " .setExtractorMode(\"text_before\") \\\n", + " .setTextPattern(\"BLAH\") \\\n", + "\n", + "pipeline = Pipeline().setStages([\n", + " document_assembler,\n", + " extractor\n", + "])\n", + "\n", + "model = pipeline.fit(test_df)\n", + "result = model.transform(test_df)\n", + "result.select(\"text_before\").show(truncate=False)" + ] + } + ], + "metadata": { + "application/vnd.databricks.v1+notebook": { + "computePreferences": null, + "dashboards": [], + "environmentMetadata": null, + "language": "python", + "notebookMetadata": { + "pythonIndentUnit": 4 + }, + "notebookName": "SparkNLP_Extractor_Demo", + "widgets": {} + }, + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/python/sparknlp/annotator/cleaners/__init__.py b/python/sparknlp/annotator/cleaners/__init__.py new file mode 100644 index 00000000000000..38ba4d88294012 --- /dev/null +++ b/python/sparknlp/annotator/cleaners/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from sparknlp.annotator.cleaners.extractor import * +from sparknlp.annotator.cleaners.cleaner import * \ No newline at end of file diff --git a/python/sparknlp/annotator/cleaners/cleaner.py b/python/sparknlp/annotator/cleaners/cleaner.py new file mode 100644 index 00000000000000..d52affd4bb7d54 --- /dev/null +++ b/python/sparknlp/annotator/cleaners/cleaner.py @@ -0,0 +1,202 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains classes for Cleaner.""" +from sparknlp.annotator import MarianTransformer +from sparknlp.common import * + +class Cleaner(MarianTransformer): + name = "Cleaner" + + inputAnnotatorTypes = [AnnotatorType.TOKEN] + + outputAnnotatorType = AnnotatorType.CHUNK + + encoding = Param(Params._dummy(), + "encoding", + "The encoding to be used for decoding the byte string (default is utf-8)", + typeConverter=TypeConverters.toString) + + cleanPrefixPattern = Param(Params._dummy(), + "cleanPrefixPattern", + "The pattern for the prefix. Can be a simple string or a regex pattern.", + typeConverter=TypeConverters.toString) + + cleanPostfixPattern = Param(Params._dummy(), + "cleanPostfixPattern", + "The pattern for the postfix. Can be a simple string or a regex pattern.", + typeConverter=TypeConverters.toString) + + cleanerMode = Param( + Params._dummy(), + "cleanerMode", + "possible values: " + + "clean, bytes_string_to_string, clean_non_ascii_chars, clean_ordered_bullets, clean_postfix, clean_prefix, remove_punctuation, replace_unicode_quotes", + typeConverter=TypeConverters.toString + ) + + extraWhitespace = Param(Params._dummy(), + "extraWhitespace", + "Whether to remove extra whitespace.", + typeConverter=TypeConverters.toBoolean) + + dashes = Param(Params._dummy(), + "dashes", + "Whether to handle dashes in text.", + typeConverter=TypeConverters.toBoolean) + + bullets = Param(Params._dummy(), + "bullets", + "Whether to handle bullets in text.", + typeConverter=TypeConverters.toBoolean) + + trailingPunctuation = Param(Params._dummy(), + "trailingPunctuation", + "Whether to remove trailing punctuation from text.", + typeConverter=TypeConverters.toBoolean) + + lowercase = Param(Params._dummy(), + "lowercase", + "Whether to convert text to lowercase.", + typeConverter=TypeConverters.toBoolean) + + ignoreCase = Param(Params._dummy(), + "ignoreCase", + "If true, ignores case in the pattern.", + typeConverter=TypeConverters.toBoolean) + + strip = Param(Params._dummy(), + "strip", + "If true, removes leading or trailing whitespace from the cleaned string.", + typeConverter=TypeConverters.toBoolean) + + def setEncoding(self, value): + """Sets the encoding to be used for decoding the byte string (default is utf-8). + + Parameters + ---------- + value : str + The encoding to be used for decoding the byte string (default is utf-8) + """ + return self._set(encoding=value) + + def setCleanPrefixPattern(self, value): + """Sets the pattern for the prefix. Can be a simple string or a regex pattern. + + Parameters + ---------- + value : str + The pattern for the prefix. Can be a simple string or a regex pattern. + """ + return self._set(cleanPrefixPattern=value) + + def setCleanPostfixPattern(self, value): + """Sets the pattern for the postfix. Can be a simple string or a regex pattern. + + Parameters + ---------- + value : str + The pattern for the postfix. Can be a simple string or a regex pattern. + """ + return self._set(cleanPostfixPattern=value) + + def setCleanerMode(self, value): + """Sets the cleaner mode. + + Possible values: + clean, bytes_string_to_string, clean_non_ascii_chars, clean_ordered_bullets, + clean_postfix, clean_prefix, remove_punctuation, replace_unicode_quotes + + Parameters + ---------- + value : str + The mode for cleaning operations. + """ + return self._set(cleanerMode=value) + + def setExtraWhitespace(self, value): + """Sets whether to remove extra whitespace. + + Parameters + ---------- + value : bool + Whether to remove extra whitespace. + """ + return self._set(extraWhitespace=value) + + def setDashes(self, value): + """Sets whether to handle dashes in text. + + Parameters + ---------- + value : bool + Whether to handle dashes in text. + """ + return self._set(dashes=value) + + def setBullets(self, value): + """Sets whether to handle bullets in text. + + Parameters + ---------- + value : bool + Whether to handle bullets in text. + """ + return self._set(bullets=value) + + def setTrailingPunctuation(self, value): + """Sets whether to remove trailing punctuation from text. + + Parameters + ---------- + value : bool + Whether to remove trailing punctuation from text. + """ + return self._set(trailingPunctuation=value) + + def setLowercase(self, value): + """Sets whether to convert text to lowercase. + + Parameters + ---------- + value : bool + Whether to convert text to lowercase. + """ + return self._set(lowercase=value) + + def setIgnoreCase(self, value): + """Sets whether to ignore case in the pattern. + + Parameters + ---------- + value : bool + If true, ignores case in the pattern. + """ + return self._set(ignoreCase=value) + + def setStrip(self, value): + """Sets whether to remove leading or trailing whitespace from the cleaned string. + + Parameters + ---------- + value : bool + If true, removes leading or trailing whitespace from the cleaned string. + """ + return self._set(strip=value) + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cleaners.Cleaner", java_model=None): + super(Cleaner, self).__init__( + classname=classname, + java_model=java_model + ) \ No newline at end of file diff --git a/python/sparknlp/annotator/cleaners/extractor.py b/python/sparknlp/annotator/cleaners/extractor.py new file mode 100644 index 00000000000000..d1a2a1bbb1326e --- /dev/null +++ b/python/sparknlp/annotator/cleaners/extractor.py @@ -0,0 +1,191 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains classes for Extractor.""" +from sparknlp.common import * + +class Extractor(AnnotatorModel): + name = "Extractor" + + inputAnnotatorTypes = [AnnotatorType.DOCUMENT] + + outputAnnotatorType = AnnotatorType.CHUNK + + emailDateTimeTzPattern = Param(Params._dummy(), + "emailDateTimeTzPattern", + "Specifies the date-time pattern for email timestamps, including time zone formatting.", + typeConverter=TypeConverters.toString) + + emailAddress = Param( + Params._dummy(), + "emailAddress", + "Specifies the pattern for email addresses.", + typeConverter=TypeConverters.toString + ) + + ipAddressPattern = Param( + Params._dummy(), + "ipAddressPattern", + "Specifies the pattern for IP addresses.", + typeConverter=TypeConverters.toString + ) + + ipAddressNamePattern = Param( + Params._dummy(), + "ipAddressNamePattern", + "Specifies the pattern for IP addresses with names.", + typeConverter=TypeConverters.toString + ) + + mapiIdPattern = Param( + Params._dummy(), + "mapiIdPattern", + "Specifies the pattern for MAPI IDs.", + typeConverter=TypeConverters.toString + ) + + usPhoneNumbersPattern = Param( + Params._dummy(), + "usPhoneNumbersPattern", + "Specifies the pattern for US phone numbers.", + typeConverter=TypeConverters.toString + ) + + imageUrlPattern = Param( + Params._dummy(), + "imageUrlPattern", + "Specifies the pattern for image URLs.", + typeConverter=TypeConverters.toString + ) + + textPattern = Param( + Params._dummy(), + "textPattern", + "Specifies the pattern for text after and before.", + typeConverter=TypeConverters.toString + ) + + extractorMode = Param( + Params._dummy(), + "extractorMode", + "possible values: " + + "email_date, email_address, ip_address, ip_address_name, mapi_id, us_phone_numbers, image_urls, bullets, text_after, text_before", + typeConverter=TypeConverters.toString + ) + + index = Param( + Params._dummy(), + "index", + "Specifies the index of the pattern to extract in text after or before", + typeConverter=TypeConverters.toInt + ) + + def setEmailDateTimeTzPattern(self, value): + """Sets specifies the date-time pattern for email timestamps, including time zone formatting. + + Parameters + ---------- + value : str + Specifies the date-time pattern for email timestamps, including time zone formatting. + """ + return self._set(emailDateTimeTzPattern=value) + + def setEmailAddress(self, value): + """Sets the pattern for email addresses. + + Parameters + ---------- + value : str + Specifies the pattern for email addresses. + """ + return self._set(emailAddress=value) + + def setIpAddressPattern(self, value): + """Sets the pattern for IP addresses. + + Parameters + ---------- + value : str + Specifies the pattern for IP addresses. + """ + return self._set(ipAddressPattern=value) + + def setIpAddressNamePattern(self, value): + """Sets the pattern for IP addresses with names. + + Parameters + ---------- + value : str + Specifies the pattern for IP addresses with names. + """ + return self._set(ipAddressNamePattern=value) + + def setMapiIdPattern(self, value): + """Sets the pattern for MAPI IDs. + + Parameters + ---------- + value : str + Specifies the pattern for MAPI IDs. + """ + return self._set(mapiIdPattern=value) + + def setUsPhoneNumbersPattern(self, value): + """Sets the pattern for US phone numbers. + + Parameters + ---------- + value : str + Specifies the pattern for US phone numbers. + """ + return self._set(usPhoneNumbersPattern=value) + + def setImageUrlPattern(self, value): + """Sets the pattern for image URLs. + + Parameters + ---------- + value : str + Specifies the pattern for image URLs. + """ + return self._set(imageUrlPattern=value) + + def setTextPattern(self, value): + """Sets the pattern for text after and before. + + Parameters + ---------- + value : str + Specifies the pattern for text after and before. + """ + return self._set(textPattern=value) + + def setExtractorMode(self, value): + return self._set(extractorMode=value) + + def setIndex(self, value): + """Sets the index of the pattern to extract in text after or before. + + Parameters + ---------- + value : int + Specifies the index of the pattern to extract in text after or before. + """ + return self._set(index=value) + + @keyword_only + def __init__(self, classname="com.johnsnowlabs.nlp.annotators.cleaners.Extractor", java_model=None): + super(Extractor, self).__init__( + classname=classname, + java_model=java_model + ) \ No newline at end of file diff --git a/python/test/annotator/cleaners/__init__.py b/python/test/annotator/cleaners/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/python/test/annotator/cleaners/cleaner_test.py b/python/test/annotator/cleaners/cleaner_test.py new file mode 100644 index 00000000000000..1868dbae935737 --- /dev/null +++ b/python/test/annotator/cleaners/cleaner_test.py @@ -0,0 +1,73 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +import pytest + +from sparknlp.annotator.cleaners import * +from sparknlp.base import * +from test.util import SparkContextForTest + + +@pytest.mark.fast +class CleanerBytesTestSpec(unittest.TestCase): + + def setUp(self): + self.spark = SparkContextForTest.spark + eml_data = """Hello รฐ\x9f\x98\x80""" + self.data_set = self.spark.createDataFrame([[eml_data]]).toDF("text") + + def runTest(self): + document_assembler = DocumentAssembler().setInputCol("text").setOutputCol("document") + + cleaner = Cleaner() \ + .setInputCols(["document"]) \ + .setOutputCol("cleaned") \ + .setCleanerMode("bytes_string_to_string") + + pipeline = Pipeline().setStages([ + document_assembler, + cleaner + ]) + + model = pipeline.fit(self.data_set) + result = model.transform(self.data_set) + result.show(truncate=False) + +@pytest.mark.fast +class CleanerBulletsTestSpec(unittest.TestCase): + + def setUp(self): + self.spark = SparkContextForTest.spark + data = [("1.1 This is a very important point",), + ("a.1 This is a very important point",), + ("1.4.2 This is a very important point",)] + self.data_set = self.spark.createDataFrame(data).toDF("text") + + def runTest(self): + document_assembler = DocumentAssembler().setInputCol("text").setOutputCol("document") + + cleaner = Cleaner() \ + .setInputCols(["document"]) \ + .setOutputCol("cleaned") \ + .setCleanerMode("clean_ordered_bullets") + + pipeline = Pipeline().setStages([ + document_assembler, + cleaner + ]) + + model = pipeline.fit(self.data_set) + result = model.transform(self.data_set) + result.show(truncate=False) \ No newline at end of file diff --git a/python/test/annotator/cleaners/extractor_test.py b/python/test/annotator/cleaners/extractor_test.py new file mode 100644 index 00000000000000..b1243152f2e69d --- /dev/null +++ b/python/test/annotator/cleaners/extractor_test.py @@ -0,0 +1,49 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest + +import pytest + +from sparknlp.annotator.cleaners import * +from sparknlp.base import * +from test.util import SparkContextForTest + + +@pytest.mark.fast +class ExtractorTestSpec(unittest.TestCase): + + def setUp(self): + self.spark = SparkContextForTest.spark + eml_data = """from ABC.DEF.local ([ba23::58b5:2236:45g2:88h2]) by + \n ABC.DEF.local2 ([ba23::58b5:2236:45g2:88h2%25]) with mapi id\ + n 32.88.5467.123; Fri, 26 Mar 2021 11:04:09 +1200""" + self.data_set = self.spark.createDataFrame([[eml_data]]).toDF("text") + + def runTest(self): + document_assembler = DocumentAssembler().setInputCol("text").setOutputCol("document") + + extractor = Extractor() \ + .setInputCols(["document"]) \ + .setOutputCol("date") \ + .setExtractorMode("email_date") + + pipeline = Pipeline().setStages([ + document_assembler, + extractor + ]) + + model = pipeline.fit(self.data_set) + result = model.transform(self.data_set) + result.show(truncate=False) + diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala new file mode 100644 index 00000000000000..71f6f692c12c1b --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala @@ -0,0 +1,241 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.cleaners + +import com.johnsnowlabs.ml.tensorflow.sentencepiece.ReadSentencePieceModel +import com.johnsnowlabs.nlp.Annotation +import com.johnsnowlabs.nlp.AnnotatorType.CHUNK +import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper +import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper._ +import com.johnsnowlabs.nlp.annotators.seq2seq.{ + MarianTransformer, + ReadMarianMTDLModel, + ReadablePretrainedMarianMTModel +} +import org.apache.spark.ml.param.Param +import org.apache.spark.ml.util.Identifiable + +class Cleaner(override val uid: String) extends MarianTransformer { + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + def this() = this(Identifiable.randomUID("CLEANER")) + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + override val outputAnnotatorType: AnnotatorType = CHUNK + + val encoding = new Param[String]( + this, + "encoding", + "The encoding to be used for decoding the byte string (default is utf-8)" + ) + + def setEncoding(value: String): this.type = set(this.encoding, value) + + val cleanPrefixPattern = new Param[String]( + this, + "cleanPrefixPattern", + "The pattern for the prefix. Can be a simple string or a regex pattern." + ) + + def setCleanPrefixPattern(value: String): this.type = set(this.cleanPrefixPattern, value) + + val cleanPostfixPattern = new Param[String]( + this, + "cleanPostfixPattern", + "The pattern for the postfix. Can be a simple string or a regex pattern." + ) + + def setCleanPostfixPattern(value: String): this.type = set(this.cleanPrefixPattern, value) + + /** cleanerMode can take the following values: + * - `bytes_string_to_string`: Converts a string representation of a byte string (e.g., containing escape sequences) to an Annotation structure using the specified encoding. + * + * */ + val cleanerMode: Param[String] = new Param[String]( + this, + "cleanerMode", + "possible values: " + + "clean, bytes_string_to_string, clean_non_ascii_chars, clean_ordered_bullets, clean_postfix," + + " clean_prefix, remove_punctuation, replace_unicode_characters") + + def setCleanerMode(value: String): this.type = { + value.trim.toLowerCase() match { + case "clean" => set(this.cleanerMode, value) + case "bytes_string_to_string" => set(this.cleanerMode, value) + case "clean_non_ascii_chars" => set(this.cleanerMode, value) + case "clean_ordered_bullets" => set(this.cleanerMode, value) + case "clean_postfix" => set(this.cleanerMode, value) + case "clean_prefix" => set(this.cleanerMode, value) + case "remove_punctuation" => set(this.cleanerMode, value) + case "replace_unicode_characters" => set(this.cleanerMode, value) + case "translate" => set(this.cleanerMode, value) + case _ => throw new IllegalArgumentException(s"Cleaner mode $value is not supported.") + } + set(this.cleanerMode, value) + } + + val extraWhitespace = new Param[Boolean]( + this, + "extraWhitespace", + "Whether to remove extra whitespace." + ) + + def setExtraWhitespace(value: Boolean): this.type = set(this.extraWhitespace, value) + + val dashes = new Param[Boolean]( + this, + "dashes", + "Whether to handle dashes in text." + ) + + def setDashes(value: Boolean): this.type = set(this.dashes, value) + + val bullets = new Param[Boolean]( + this, + "bullets", + "Whether to handle bullets in text." + ) + + def setBullets(value: Boolean): this.type = set(this.bullets, value) + + val trailingPunctuation = new Param[Boolean]( + this, + "trailingPunctuation", + "Whether to remove trailing punctuation from text." + ) + + def setTrailingPunctuation(value: Boolean): this.type = set(this.trailingPunctuation, value) + + val lowercase = new Param[Boolean]( + this, + "lowercase", + "Whether to convert text to lowercase." + ) + + def setLowercase(value: Boolean): this.type = set(this.lowercase, value) + + val ignoreCase = new Param[Boolean]( + this, + "ignoreCase", + "If true, ignores case in the pattern." + ) + + def setIgnoreCase(value: Boolean): this.type = set(this.ignoreCase, value) + + val strip = new Param[Boolean]( + this, + "strip", + "If true, removes leading or trailing whitespace from the cleaned string." + ) + + def setStrip(value: Boolean): this.type = set(this.strip, value) + + setDefault( + encoding -> "utf-8", + extraWhitespace -> false, + dashes -> false, + bullets -> false, + trailingPunctuation -> false, + lowercase -> false, + ignoreCase -> false, + strip -> true, + cleanerMode -> "translate" + ) + + override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { + require($(cleanerMode) != "undefined", "Extractor mode must be set.") + + if ($(cleanerMode) == "translate") { + return super.batchAnnotate(batchedAnnotations) + } + + batchedAnnotations.map { annotations => + $(cleanerMode) match { + case "clean" => annotations.map(buildAnnotation(clean)).toSeq + case "bytes_string_to_string" => annotations.map(buildAnnotation(bytesStringToString)).toSeq + case "clean_non_ascii_chars" => annotations.map(buildAnnotation(cleanNonAsciiChars)).toSeq + case "clean_ordered_bullets" => annotations.map(buildAnnotation(cleanOrderedBullets)).toSeq + case "clean_postfix" => annotations.map(buildAnnotation(cleanPostfix)).toSeq + case "clean_prefix" => annotations.map(buildAnnotation(cleanPrefix)).toSeq + case "remove_punctuation" => annotations.map(buildAnnotation(removePunctuation)).toSeq + case "replace_unicode_characters" => annotations.map(buildAnnotation(replaceUnicodeCharacters)).toSeq + } + } + } + + def buildAnnotation(transformation: String => String)(annotation: Annotation): Annotation = { + val cleanText = transformation(annotation.result) + Annotation( + annotatorType = outputAnnotatorType, + begin = 0, + end = cleanText.length, + result = cleanText, + metadata = Map() + ) + } + + /** + * Converts a string representation of a byte string (e.g., containing escape sequences) + * to an Annotation structure using the specified encoding. + * + * @param text The string representation of the byte string. + * @return The String containing the decoded result + */ + private def bytesStringToString(text: String): String = { + CleanerHelper.bytesStringToString(text, $(encoding)) + } + + private def clean(text: String): String = { + + var cleanedText = if ($(lowercase)) text.toLowerCase else text + cleanedText = if ($(trailingPunctuation)) cleanTrailingPunctuation(cleanedText) else cleanedText + cleanedText = if ($(dashes)) cleanDashes(cleanedText) else cleanedText + cleanedText = if ($(extraWhitespace)) cleanExtraWhitespace(cleanedText) else cleanedText + cleanedText = if ($(bullets)) cleanBullets(cleanedText) else cleanedText + + cleanedText.trim + } + + /** + * Cleans a prefix from a string based on a pattern. + * + * @param text The text to clean. + * @return The cleaned string. + */ + private def cleanPrefix(text: String): String = { + CleanerHelper.cleanPrefix(text, $(cleanPrefixPattern), $(ignoreCase), $(strip)) + } + + /** + * Cleans a postfix from a string based on a pattern. + * + * @param text The text to clean. + * @return The cleaned string. + */ + private def cleanPostfix(text: String): String = { + CleanerHelper.cleanPostfix(text, $(cleanPrefixPattern), $(ignoreCase), $(strip)) + } + +} + +object Cleaner + extends ReadablePretrainedMarianMTModel + with ReadMarianMTDLModel + with ReadSentencePieceModel \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala new file mode 100644 index 00000000000000..d84cd4073e076c --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala @@ -0,0 +1,365 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.cleaners + +import com.johnsnowlabs.nlp.AnnotatorType.{CHUNK, DOCUMENT} +import com.johnsnowlabs.nlp.{Annotation, AnnotatorModel, HasSimpleAnnotate} +import org.apache.spark.ml.param.{IntParam, Param} +import org.apache.spark.ml.util.Identifiable + +import scala.util.matching.Regex + +class Extractor(override val uid: String) + extends AnnotatorModel[Extractor] + with HasSimpleAnnotate[Extractor] { + + def this() = this(Identifiable.randomUID("Extractor")) + + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(DOCUMENT) + override val outputAnnotatorType: AnnotatorType = CHUNK + + private val EMAIL_DATETIMETZ_PATTERN = + "[A-Za-z]{3},\\s\\d{1,2}\\s[A-Za-z]{3}\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s[+-]\\d{4}" + private val EMAIL_ADDRESS_PATTERN = "(?i)[a-z0-9\\.\\-+_]+@[a-z0-9\\.\\-+_]+\\.[a-z]+" + + private val IPV4_PATTERN: String = + """(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}""" + private val IPV6_PATTERN: String = + """[a-z0-9]{4}::[a-z0-9]{4}:[a-z0-9]{4}:[a-z0-9]{4}:[a-z0-9]{4}%?[0-9]*""" + private val IP_ADDRESS_PATTERN: String = s"($IPV4_PATTERN|$IPV6_PATTERN)" + private val IP_ADDRESS_NAME_PATTERN = "[a-zA-Z0-9-]*\\.[a-zA-Z]*\\.[a-zA-Z]*" + + private val MAPI_ID_PATTERN = "[0-9]*\\.[0-9]*\\.[0-9]*\\.[0-9]*" + private val US_PHONE_NUMBERS_PATTERN = + "(?:\\+?(\\d{1,3}))?[-. (]*(\\d{3})?[-. )]*(\\d{3})[-. ]*(\\d{4})(?: *x(\\d+))?\\s*$" + + private val IMAGE_URL_PATTERN = + """(?i)https?://(?:[a-z0-9$_@.&+!*\\(\\),%-])+(?:/[a-z0-9$_@.&+!*\\(\\),%-]*)*\.(?:jpg|jpeg|png|gif|bmp|heic)""" + + val emailDateTimeTzPattern = new Param[String]( + this, + "emailDateTimeTzPattern", + "Specifies the date-time pattern for email timestamps, including time zone formatting.") + + /** @group setParam */ + def setEmailDateTimeTzPattern(value: String): this.type = set(emailDateTimeTzPattern, value) + + val emailAddress = + new Param[String](this, "emailAddress", "Specifies the pattern for email addresses.") + + val ipAddressPattern = + new Param[String](this, "ipAddressPattern", "Specifies the pattern for IP addresses.") + + /** @group setParam */ + def setIpAddressPattern(value: String): this.type = set(ipAddressPattern, value) + + val ipAddressNamePattern = new Param[String]( + this, + "ipAddressNamePattern", + "Specifies the pattern for IP addresses with names.") + + /** @group setParam */ + def setIpAddressNamePattern(value: String): this.type = set(ipAddressNamePattern, value) + + val mapiIdPattern = + new Param[String](this, "mapiIdPattern", "Specifies the pattern for MAPI IDs.") + + /** @group setParam */ + def setMapiIdPattern(value: String): this.type = set(mapiIdPattern, value) + + val usPhoneNumbersPattern = new Param[String]( + this, + "usPhoneNumbersPattern", + "Specifies the pattern for US phone numbers.") + + val imageUrlPattern = + new Param[String](this, "imageUrlPattern", "Specifies the pattern for image URLs.") + + /** @group setParam */ + def setImageUrlPattern(value: String): this.type = set(imageUrlPattern, value) + + val textPattern = + new Param[String](this, "textPattern", "Specifies the pattern for text after and before.") + + def setTextPattern(value: String): this.type = set(textPattern, value) + + val index = new IntParam( + this, + "index", + "Specifies the index of the pattern to extract in text after or before") + + /** @group setParam */ + def setIndex(value: Int): this.type = set(index, value) + + /** extractorMode can take the following values: + * - `email_date`: extract email date + * - `email_address`: extract email address + * - `ip_address`: extract ip address + * - `ip_address_name`: extract ip address with name + * - `mapi_id`: extract mapi id + * - `us_phone_numbers`: extract US phone numbers + * - `image_urls`: extract image URLs + * - `bullets`: extract ordered bullets + * - `text_after`: extract text after a pattern + * - `text_before`: extract text before a pattern + * @group param + */ + val extractorMode: Param[String] = new Param[String]( + this, + "extractorMode", + "possible values: " + + "email_date, email_address, ip_address, ip_address_name, mapi_id, us_phone_numbers, image_urls, bullets, text_after, text_before") + + /** @group setParam */ + def setExtractorMode(value: String): this.type = { + value.trim.toLowerCase() match { + case "email_date" => set(extractorMode, "email_date") + case "email_address" => set(extractorMode, "email_address") + case "ip_address" => set(extractorMode, "ip_address") + case "ip_address_name" => set(extractorMode, "ip_address_name") + case "mapi_id" => set(extractorMode, "mapi_id") + case "us_phone_numbers" => set(extractorMode, "us_phone_numbers") + case "image_urls" => set(extractorMode, "image_urls") + case "bullets" => set(extractorMode, "bullets") + case "text_after" => set(extractorMode, "text_after") + case "text_before" => set(extractorMode, "text_before") + case _ => throw new IllegalArgumentException(s"Extractor mode $value not supported.") + } + set(extractorMode, value) + } + + setDefault( + emailDateTimeTzPattern -> EMAIL_DATETIMETZ_PATTERN, + emailAddress -> EMAIL_ADDRESS_PATTERN, + ipAddressPattern -> IP_ADDRESS_PATTERN, + ipAddressNamePattern -> IP_ADDRESS_NAME_PATTERN, + mapiIdPattern -> MAPI_ID_PATTERN, + usPhoneNumbersPattern -> US_PHONE_NUMBERS_PATTERN, + imageUrlPattern -> IMAGE_URL_PATTERN, + index -> 0, + extractorMode -> "undefined") + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param annotations + * Annotations that correspond to inputAnnotationCols generated by previous annotators if any + * @return + * any number of annotations processed for every input annotation. Not necessary one to one + * relationship + */ + override def annotate(annotations: Seq[Annotation]): Seq[Annotation] = { + require($(extractorMode) != "undefined", "Extractor mode must be set.") + + $(extractorMode) match { + case "email_date" => extractRegexPattern(annotations, $(emailDateTimeTzPattern).r) + case "email_address" => extractRegexPattern(annotations, $(emailAddress).r) + case "ip_address" => extractRegexPattern(annotations, $(ipAddressPattern).r) + case "ip_address_name" => extractRegexPattern(annotations, $(ipAddressNamePattern).r) + case "mapi_id" => extractRegexPattern(annotations, $(mapiIdPattern).r) + case "us_phone_numbers" => extractRegexPattern(annotations, $(usPhoneNumbersPattern).r) + case "image_urls" => extractImageUrls(annotations, $(imageUrlPattern).r) + case "bullets" => + annotations.map { annotation => + extractOrderedBulletsAsAnnotation(annotation.result) + } + case "text_after" => + annotations.map { annotation => + extractTextAfter(annotation.result, $(textPattern), $(index)) + } + case "text_before" => + annotations.map { annotation => + extractTextBefore(annotation.result, $(textPattern), $(index)) + } + case _ => + throw new IllegalArgumentException(s"Extractor mode ${$(extractorMode)} not supported.") + } + + } + + private def extractImageUrls(annotations: Seq[Annotation], regex: Regex): Seq[Annotation] = { + annotations.flatMap { annotation => + regex.findAllMatchIn(annotation.result).map { matched => + val start = annotation.begin + matched.start + val end = annotation.begin + matched.end - 1 + Annotation(outputAnnotatorType, start, end, matched.matched, Map.empty) + } + } + } + + private def extractRegexPattern(annotations: Seq[Annotation], regex: Regex): Seq[Annotation] = { + annotations.flatMap { annotation => + regex.findAllMatchIn(annotation.result).map { matched => + val start = annotation.begin + matched.start + val end = annotation.begin + matched.end - 1 + Annotation(outputAnnotatorType, start, end, matched.matched, Map.empty) + } + } + } + + /** Extracts the start of bulleted text sections, considering numeric and alphanumeric types, + * and returns the result as an Annotation. + * + * @param text + * The input string. + * @return + * An Annotation object containing extracted bullet information. + * + * Example: + * ------- "This is a very important point" -> Annotation("bullet", 0, 0, "None,None,None", + * Map.empty) "1.1 This is a very important point" -> Annotation("bullet", 0, 3, "1,1,None", + * Map("section" -> "1", "sub_section" -> "1")) "a.1 This is a very important point" -> + * Annotation("bullet", 0, 3, "a,1,None", Map("section" -> "a", "sub_section" -> "1")) + */ + private def extractOrderedBulletsAsAnnotation(text: String): Annotation = { + var section: Option[String] = None + var subSection: Option[String] = None + var subSubSection: Option[String] = None + + val textParts = text.split("\\s+", 2) + + val defaultBegin = 0 + val defaultEnd = 0 + + if (textParts.isEmpty || textParts.head.count(_ == '.') == 0 || textParts.head.contains( + "..")) { + return Annotation( + annotatorType = outputAnnotatorType, + begin = defaultBegin, + end = defaultEnd, + result = "(None,None,None)", + metadata = Map.empty) + } + + val bulletPattern: Regex = "\\.".r + val bulletParts = bulletPattern.split(textParts.head).filter(_.nonEmpty) + + if (bulletParts.headOption.exists(_.length > 2)) { + return Annotation( + annotatorType = outputAnnotatorType, + begin = defaultBegin, + end = defaultEnd, + result = "(None,None,None)", + metadata = Map.empty) + } + + val begin = 0 + val end = textParts.head.length + + section = Some(bulletParts.head) + if (bulletParts.length > 1) { + subSection = Some(bulletParts(1)) + } + if (bulletParts.length > 2) { + subSubSection = Some(bulletParts(2)) + } + + val result = + s"(${section.getOrElse("None")},${subSection.getOrElse("None")},${subSubSection.getOrElse("None")})" + val metadata = Map( + "section" -> section.getOrElse("None"), + "sub_section" -> subSection.getOrElse("None"), + "sub_sub_section" -> subSubSection.getOrElse("None")).filterNot(_._2 == "None") + + Annotation( + annotatorType = outputAnnotatorType, + begin = begin, + end = end, + result = result, + metadata = metadata) + } + + /** Extracts text that occurs after the specified pattern and returns an Annotation. + * + * @param text + * The input text. + * @param pattern + * The regex pattern to search for. + * @param index + * The occurrence index of the pattern. + * @param strip + * If true, removes leading whitespace from the extracted string. + * @return + * Annotation with details of the extracted result. + */ + private def extractTextAfter( + text: String, + pattern: String, + index: Int = 0, + strip: Boolean = true): Annotation = { + val regexMatch = getIndexedMatch(text, pattern, index) + val begin = regexMatch.end + val afterText = text.substring(begin) + val result = if (strip) afterText.replaceAll("^\\s+", "") else afterText + + Annotation( + annotatorType = outputAnnotatorType, + begin = begin, + end = text.length, + result = result, + metadata = Map("index" -> index.toString)) + } + + /** Extracts text that occurs before the specified pattern and returns an Annotation. + * + * @param text + * The input text. + * @param pattern + * The regex pattern to search for. + * @param index + * The occurrence index of the pattern. + * @param strip + * If true, removes trailing whitespace from the extracted string. + * @return + * Annotation with details of the extracted result. + */ + private def extractTextBefore( + text: String, + pattern: String, + index: Int = 0, + strip: Boolean = true): Annotation = { + val regexMatch = getIndexedMatch(text, pattern, index) + val start = regexMatch.start + val beforeText = text.substring(0, start) + val result = if (strip) beforeText.replaceAll("\\s+$", "") else beforeText + + Annotation( + annotatorType = outputAnnotatorType, + begin = 0, + end = start, + result = result, + metadata = Map("index" -> index.toString)) + } + + private def getIndexedMatch(text: String, pattern: String, index: Int = 0): Regex.Match = { + if (index < 0) + throw new IllegalArgumentException( + s"The index is $index. Index must be a non-negative integer.") + + val regex = new Regex(pattern) + val matches = regex.findAllMatchIn(text).toSeq + + if (index >= matches.length) + throw new IllegalArgumentException( + s"Result with index $index was not found. The largest index was ${matches.length - 1}.") + + matches(index) + } + +} diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala new file mode 100644 index 00000000000000..bb89dc10cf0d1e --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala @@ -0,0 +1,224 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.cleaners.util + +import java.nio.charset.Charset +import java.util.regex.Pattern +import scala.util.matching.Regex + +object CleanerHelper { + + private val UNICODE_BULLETS: List[String] = List( + "\u0095", + "\u2022", + "\u2023", + "\u2043", + "\u3164", + "\u204C", + "\u204D", + "\u2219", + "\u25CB", + "\u25CF", + "\u25D8", + "\u25E6", + "\u2619", + "\u2765", + "\u2767", + "\u29BE", + "\u29BF", + "\u002D", + "๏‚ท", + "\\*", // Escaped for regex compatibility + "\u0095", + "ยท" + ) + + private val BULLETS_PATTERN = UNICODE_BULLETS.map(Pattern.quote).mkString("|") + private val UNICODE_BULLETS_RE: Regex = new Regex(s"(?:$BULLETS_PATTERN)") + + private val HTML_APOSTROPHE_ENTITY: String = "'" + private val HEXADECIMAL_ESCAPE_SEQUENCE: Regex = """\\x([0-9A-Fa-f]{2})""".r + + /** + * Parses a string containing escape sequences (e.g., `\x9f`) into a byte array. + * + * @param text The input string with escape sequences. + * @return A byte array representing the parsed bytes. + */ + def parseEscapedBytes(text: String): Array[Byte] = { + val RawByteCharset: Charset = Charset.forName("ISO-8859-1") + + // Replace escape sequences with their byte values + HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn(text, m => { + val hexValue = m.group(1) + Integer.parseInt(hexValue, 16).toChar.toString + }).getBytes(RawByteCharset) + } + + /** + * Formats an input encoding string (e.g., `utf-8`, `iso-8859-1`, etc). + * + * @param encoding The encoding string to be formatted. + * @return The formatted encoding string. + */ + def formatEncodingStr(encoding: String): String = { + var formattedEncoding = encoding.toLowerCase.replace("_", "-") + + // Special case for Arabic and Hebrew charsets with directional annotations + val annotatedEncodings = Set("iso-8859-6-i", "iso-8859-6-e", "iso-8859-8-i", "iso-8859-8-e") + if (annotatedEncodings.contains(formattedEncoding)) { + formattedEncoding = formattedEncoding.dropRight(2) + } + + formattedEncoding + } + + def cleanTrailingPunctuation(text: String): String = { + text.replaceAll("[.,:;]+$", "") + } + + def cleanDashes(text: String): String = { + val dashRegex: Regex = "[-\u2013]".r + dashRegex.replaceAllIn(text, " ").trim + } + + def cleanExtraWhitespace(text: String): String = { + // Replace all occurrences of '\xa0' (non-breaking space) with a regular space + val hexNbspReplaced = text.replaceAll("\\\\x[aA]0", " ") + + // Normalize other whitespace characters if needed + val normalizedText = hexNbspReplaced.replaceAll("\\p{Zs}", " ") + + // Collapse whitespace sequences into a single space + val whitespaceRegex: Regex = "\\s+".r + + whitespaceRegex.replaceAllIn(normalizedText, " ").trim + } + + def cleanBullets(text: String): String = { + // Manually create a regex that explicitly matches the bullet "\u2022" + val manualBulletRegex: Regex = new Regex(s"""^$UNICODE_BULLETS_RE\\s?""") + + // Debug the match + manualBulletRegex.findPrefixOf(text) match { + case Some(_) => + manualBulletRegex.replaceFirstIn(text, "").trim + case None => + text + } + } + + def cleanNonAsciiChars(text: String): String = { + val decodedText = HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn(text, m => + Integer.parseInt(m.group(1), 16).toChar.toString + ) + + val entityReplacedText = decodedText.replace(HTML_APOSTROPHE_ENTITY, "'") + entityReplacedText.replaceAll("[^\u0020-\u007E]", "") + } + + def cleanOrderedBullets(text: String): String = { + val textParts = text.split("\\s+", 2) // Splitting into two parts to avoid unnecessary joins + if (textParts.length < 2) return text + + val firstWord = textParts(0) + val remainingText = textParts(1) + + if (!firstWord.contains(".") || firstWord.contains("..")) return text + + val bulletParts = firstWord.split("\\.") + val cleanedBulletParts = if (bulletParts.last.isEmpty) bulletParts.dropRight(1) else bulletParts + + if (cleanedBulletParts.head.length > 2) text else remainingText.trim + + } + + def replaceUnicodeCharacters(text: String): String = { + val decodedText = HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn(text, m => { + val hexValue = m.group(1) + val byteValue = Integer.parseInt(hexValue, 16).toByte + new String(Array(byteValue), Charset.forName("ISO-8859-1")) + }) + + val fullyDecodedText = new String(decodedText.getBytes(Charset.forName("ISO-8859-1")), Charset.forName("Windows-1252")) + + fullyDecodedText + .replace("\u2018", "โ€˜") + .replace("\u2019", "โ€™") + .replace("\u201C", "โ€œ") + .replace("\u201D", "โ€") + .replace(HTML_APOSTROPHE_ENTITY, "'") + .replace("รข\u0080\u0099", "'") + .replace("รข\u0080โ€œ", "โ€”") + .replace("รข\u0080โ€", "โ€“") + .replace("รข\u0080ยฆ", "โ€ฆ") + } + + /** + * Removes punctuation from a given string. + * + * @params The input string. + * @return The string with punctuation removed. + */ + def removePunctuation(text: String): String = { + // \p{P} matches any kind of punctuation character in Unicode + val punctuationRegex = """\p{P}""".r + punctuationRegex.replaceAllIn(text, "") + } + + /** + * Cleans a prefix from a string based on a pattern. + * + * @param text The text to clean. + * @return The cleaned string. + */ + def cleanPrefix(text: String, pattern: String, ignoreCase: Boolean, strip: Boolean): String = { + val regexStr = + if (ignoreCase) s"(?i)^$pattern[\\p{Punct}\\s]*" + else s"^$pattern[\\p{Punct}\\s]*" + val regex = regexStr.r + + val cleanedText = regex.replaceAllIn(text, "") + + if (strip) cleanedText.replaceAll("^\\s+", "") else cleanedText + } + + /** + * Cleans a postfix from a string based on a pattern. + * + * @param text The text to clean. + * @return The cleaned string. + */ + def cleanPostfix(text: String, pattern: String, ignoreCase: Boolean, strip: Boolean): String = { + val regex = if (ignoreCase) s"(?i)$pattern$$".r else s"$pattern$$".r + val cleanedText = regex.replaceAllIn(text, "") + if (strip) cleanedText.trim else cleanedText + } + + /** + * Converts a string representation of a byte string (e.g., containing escape sequences) + * to an Annotation structure using the specified encoding. + * + * @param text The string representation of the byte string. + * @return The String containing the decoded result + */ + def bytesStringToString(text: String, encoding: String): String = { + val textBytes = parseEscapedBytes(text) + val formattedEncoding = formatEncodingStr(encoding) + new String(textBytes, Charset.forName(formattedEncoding)) + } + +} diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/CleanerTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/CleanerTestSpec.scala new file mode 100644 index 00000000000000..2eab07b26b7df0 --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/CleanerTestSpec.scala @@ -0,0 +1,174 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.cleaners + +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec + +class CleanerTestSpec extends AnyFlatSpec with SparkSessionTest { + + import spark.implicits._ + + "Cleaner" should "convert an output string that looks like a byte string to a string using the specified encoding" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("bytes_string_to_string") + + val testDf = + Seq("This is a test with regular text", "Hello รฐ\\x9f\\x98\\x80").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + "Cleaner" should "clean text" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("clean") + .setBullets(true) + .setExtraWhitespace(true) + .setDashes(true) + + val testDf = Seq("โ— An excellent point!", "ITEM 1A: RISK-FACTORS").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + "Cleaner" should "clean non-ascii characters" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("clean_non_ascii_chars") + + val testDf = Seq("\\x88This text contains ยฎnon-ascii characters!โ—").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + "Cleaner" should "clean ordered bullets" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("clean_ordered_bullets") + + val testDf = Seq( + "1.1 This is a very important point", + "a.1 This is a very important point", + "1.4.2 This is a very important point").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + it should "clean postfix" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("clean_postfix") + .setCleanPrefixPattern("(END|STOP)") + + val testDf = Seq("The end! END").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + it should "clean prefix" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("clean_prefix") + .setCleanPrefixPattern("(SUMMARY|DESCRIPTION):") + + val testDf = Seq("SUMMARY: This is the best summary of all time!").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + it should "remove punctuation" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("remove_punctuation") + + val testDf = Seq("$A lovely quote!โ€").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + it should "replace unicode quotes" taggedAs FastTest in { + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("replace_unicode_characters") + + val testDf = Seq( + """\x93A lovely quote!\x94""", + """\x91A lovely quote!\x92""", + """"\u201CA lovely quote!\u201D โ€” with a dash"""").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + + it should "translate text" taggedAs SlowTest in { + val cleaner = Cleaner + .pretrained() + .setInputCols("document") + .setOutputCol("cleaned") + + val testDf = Seq("This should go to French").toDS.toDF("text") + testDf.show(truncate = false) + + val pipeline = new Pipeline().setStages(Array(documentAssembler, cleaner)) + + val resultDf = pipeline.fit(testDf).transform(testDf) + resultDf.select("cleaned").show(truncate = false) + } + +} diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/ExtractorTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/ExtractorTestSpec.scala new file mode 100644 index 00000000000000..e9c3aaa683ed1e --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/ExtractorTestSpec.scala @@ -0,0 +1,350 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.cleaners + +import com.johnsnowlabs.nlp.AssertAnnotations +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers.convertToAnyShouldWrapper + +class ExtractorTestSpec extends AnyFlatSpec with SparkSessionTest { + + import spark.implicits._ + + val emlData = + "from ABC.DEF.local ([ba23::58b5:2236:45g2:88h2]) by\n \\n ABC.DEF.local2 ([ba23::58b5:2236:45g2:88h2%25]) with mapi id\\\n n 32.88.5467.123; Fri, 26 Mar 2021 11:04:09 +1200" + + "Extractor" should "be able to extract dates" taggedAs FastTest in { + val dateExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("date") + .setExtractorMode("email_date") + val pipeline = new Pipeline().setStages(Array(documentAssembler, dateExtractor)) + val testDf = Seq( + emlData, + "First date Fri, 26 Mar 2021 11:04:09 +1200 and then another date Wed, 26 Jul 2025 11:04:09 +1200").toDS + .toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "date") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array( + Seq("Fri, 26 Mar 2021 11:04:09 +1200"), + Seq("Fri, 26 Mar 2021 11:04:09 +1200", "Wed, 26 Jul 2025 11:04:09 +1200")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract email addresses" taggedAs FastTest in { + val emailExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("email") + .setExtractorMode("email_address") + val pipeline = new Pipeline().setStages(Array(documentAssembler, emailExtractor)) + val testDf = Seq( + "Me me@email.com and You \n ([ba23::58b5:2236:45g2:88h2]) (10.0.2.01)", + "Im Rabn ").toDS.toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "email") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("me@email.com", "You@email.com"), Seq("Im.Rabn@npf.gov.nr")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract IPv4 and IPv6 addresses" taggedAs FastTest in { + val ipAddressExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("ip") + .setExtractorMode("ip_address") + val pipeline = new Pipeline().setStages(Array(documentAssembler, ipAddressExtractor)) + val testDf = Seq("""from ABC.DEF.local ([ba23::58b5:2236:45g2:88h2]) by + \n ABC.DEF.local ([68.183.71.12]) with mapi id\ + n 32.88.5467.123; Fri, 26 Mar 2021 11:04:09 +1200""").toDS + .toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "ip") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("ba23::58b5:2236:45g2:88h2", "68.183.71.12")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract only IPv4 addresses" taggedAs FastTest in { + val ipAddressExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("ip") + .setExtractorMode("ip_address") + .setIpAddressPattern( + "(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)){3}") + val pipeline = new Pipeline().setStages(Array(documentAssembler, ipAddressExtractor)) + val testDf = + Seq("Me me@email.com and You ([ba23::58b5:2236:45g2:88h2]) (10.0.2.0)").toDS + .toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "ip") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("10.0.2.0")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract only IP address name" taggedAs FastTest in { + val ipAddressExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("ip") + .setExtractorMode("ip_address_name") + + val pipeline = new Pipeline().setStages(Array(documentAssembler, ipAddressExtractor)) + val testDf = Seq(emlData).toDS.toDF("text") + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "ip") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("ABC.DEF.local", "ABC.DEF.local")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract only MAPI IDs" taggedAs FastTest in { + val mapiIdExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("mapi_id") + .setExtractorMode("mapi_id") + val pipeline = new Pipeline().setStages(Array(documentAssembler, mapiIdExtractor)) + val testDf = Seq(emlData).toDS.toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "mapi_id") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("32.88.5467.123")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract US phone numbers" taggedAs FastTest in { + val usPhonesExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("us_phone") + .setExtractorMode("us_phone_numbers") + val pipeline = new Pipeline().setStages(Array(documentAssembler, usPhonesExtractor)) + val testDf = + Seq("215-867-5309", "Phone Number: +1 215.867.5309", "Phone Number: Just Kidding").toDS + .toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "us_phone") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("215-867-5309"), Seq("+1 215.867.5309"), Seq()) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract bullets" taggedAs FastTest in { + val bulletExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("bullets") + .setExtractorMode("bullets") + + val pipeline = new Pipeline().setStages(Array(documentAssembler, bulletExtractor)) + val testDf = Seq( + "1. Introduction:", + "a. Introduction:", + "20.3 Morse code โ—โ—โ—", + "5.3.1 Convolutional Networks", + "D.b.C Recurrent Neural Networks", + "2.b.1 Recurrent Neural Networks", + "eins. Neural Networks", + "bb.c Feed Forward Neural Networks", + "aaa.ccc Metrics", + "version = 3.8", + "1 2. 3 4", + "1) 2. 3 4", + "2", + "1..2.3 four", + "Fig. 2: The relationship", + "23 is everywhere", + "โ€ข bullet 1").toDS.toDF("text") + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "bullets") + val actualResult: Array[Seq[String]] = resultAnnotation.map(_.map(_.result)) + val expectedResult: Array[Seq[String]] = Array( + Seq("(1,None,None)"), + Seq("(a,None,None)"), + Seq("(20,3,None)"), + Seq("(5,3,1)"), + Seq("(D,b,C)"), + Seq("(2,b,1)"), + Seq("(None,None,None)"), + Seq("(bb,c,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)"), + Seq("(None,None,None)")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract image URLs" taggedAs FastTest in { + val imageUrlExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("image_urls") + .setExtractorMode("image_urls") + + val pipeline = new Pipeline().setStages(Array(documentAssembler, imageUrlExtractor)) + val testDf = Seq(""" + + + + """).toDS.toDF("text") + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "image_urls") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = + Array(Seq("https://example.com/images/photo1.jpg", "https://example.org/assets/icon.png")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract images for different cases" taggedAs FastTest in { + val imageUrlExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("image_urls") + .setExtractorMode("image_urls") + val pipeline = new Pipeline().setStages(Array(documentAssembler, imageUrlExtractor)) + val testDf = Seq( + "https://my-image.jpg", + "https://my-image.png with some text", + "https://my-image/with/some/path.png", + "some text https://my-image.jpg with another http://my-image.bmp", + "http://not-an-image.com", + "some text", + "some text https://my-image.JPG with ano100" + + "ther http://my-image.BMP", + "http://my-path-with-CAPS/my-image.JPG", + "http://my-path/my%20image.JPG", + "https://my-image.jpg#ref").toDS.toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "image_urls") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array( + Seq("https://my-image.jpg"), + Seq("https://my-image.png"), + Seq("https://my-image/with/some/path.png"), + Seq("https://my-image.jpg", "http://my-image.bmp"), + Seq(), + Seq(), + Seq("https://my-image.JPG", "http://my-image.BMP"), + Seq("http://my-path-with-CAPS/my-image.JPG"), + Seq("http://my-path/my%20image.JPG"), + Seq("https://my-image.jpg")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract text after" taggedAs FastTest in { + val textAfterExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("text_after") + .setExtractorMode("text_after") + .setTextPattern("SPEAKER \\d{1}:") + val pipeline = new Pipeline().setStages(Array(documentAssembler, textAfterExtractor)) + val testDf = Seq("SPEAKER 1: Look at me, I'm flying!").toDS.toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "text_after") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("Look at me, I'm flying!")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract text after with a pattern with punctuation" taggedAs FastTest in { + val textAfterExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("text_after") + .setExtractorMode("text_after") + .setTextPattern("BLAH;") + val pipeline = new Pipeline().setStages(Array(documentAssembler, textAfterExtractor)) + val testDf = Seq("Teacher: BLAH BLAH BLAH; Student: BLAH BLAH BLAH!").toDS.toDF("text") + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "text_after") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("Student: BLAH BLAH BLAH!")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract text before" taggedAs FastTest in { + val textAfterExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("text_before") + .setExtractorMode("text_before") + .setTextPattern("STOP") + val pipeline = new Pipeline().setStages(Array(documentAssembler, textAfterExtractor)) + val testDf = Seq("Here I am! STOP Look at me! STOP I'm flying! STOP").toDS.toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "text_before") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("Here I am!")) + + actualResult shouldEqual expectedResult + } + + it should "be able to extract text before with index" taggedAs FastTest in { + val textAfterExtractor = new Extractor() + .setInputCols("document") + .setOutputCol("text_before") + .setExtractorMode("text_before") + .setTextPattern("BLAH") + .setIndex(1) + val pipeline = new Pipeline().setStages(Array(documentAssembler, textAfterExtractor)) + val testDf = Seq("Teacher: BLAH BLAH BLAH; Student: BLAH BLAH BLAH!").toDS.toDF("text") + + val resultDf = pipeline.fit(testDf).transform(testDf) + + val resultAnnotation = AssertAnnotations.getActualResult(resultDf, "text_before") + val actualResult = resultAnnotation.map(_.map(_.result)) + val expectedResult = Array(Seq("Teacher: BLAH")) + + actualResult shouldEqual expectedResult + } + +} diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala new file mode 100644 index 00000000000000..82db8d8e6e83bf --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala @@ -0,0 +1,350 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.nlp.annotators.cleaners.util + +import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper.{ + cleanBullets, + cleanDashes, + cleanExtraWhitespace, + cleanNonAsciiChars, + cleanOrderedBullets, + cleanPostfix, + cleanPrefix, + cleanTrailingPunctuation, + removePunctuation, + replaceUnicodeCharacters +} +import com.johnsnowlabs.tags.FastTest +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.prop.TableDrivenPropertyChecks.forAll +import org.scalatest.prop.Tables.Table + +class CleanerHelperTestSpec extends AnyFlatSpec { + + "cleanTrailingPunctuation" should "remove a trailing symbols" taggedAs FastTest in { + val inputs = Seq("Hello.", "Hello,", "Hello:", "Hello;", "Hello,.", ";", "") + val expectedOutputs = Seq("Hello", "Hello", "Hello", "Hello", "Hello") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanTrailingPunctuation(input) + assert(actual == expected) + } + } + + it should "not remove punctuation if none exists" taggedAs FastTest in { + val inputs = Seq("Hello", "", "Hello, world!") + val expectedOutputs = Seq("Hello", "", "Hello, world!") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanTrailingPunctuation(input) + assert(actual == expected) + } + } + + "cleanDashes" should "replace a single dash with a space" taggedAs FastTest in { + val inputs = Seq( + "Hello-World", + "Hello---World", + "Hello\u2013World", + "Hello-World\u2013Scala", + "-Hello World-", + "---") + val expectedOutputs = + Seq("Hello World", "Hello World", "Hello World", "Hello World Scala", "Hello World", "") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanDashes(input) + assert(actual == expected) + } + } + + it should "handle strings with no dashes without modifying them" taggedAs FastTest in { + val inputs = Seq("Hello World", "") + val expectedOutputs = Seq("Hello World", "") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanDashes(input) + assert(actual == expected) + } + } + + "cleanExtraWhitespace" should "replace non-breaking spaces with a single space" taggedAs FastTest in { + val inputs = Seq( + "Hello\u00a0World", + "Hello\nWorld", + "Hello World", + "Hello\u00a0\n World", + " Hello World ", + " ", + "RISK\n\nFACTORS", + "Item\\xa01A", + " Risk factors ", + "Risk factors ") + val expectedOutputs = Seq( + "Hello World", + "Hello World", + "Hello World", + "Hello World", + "Hello World", + "", + "RISK FACTORS", + "Item 1A", + "Risk factors", + "Risk factors") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanExtraWhitespace(input) + assert(actual == expected) + } + } + + it should "handle strings with no whitespace without modifying them" taggedAs FastTest in { + val inputs = Seq("HelloWorld", "") + val expectedOutputs = Seq("HelloWorld", "") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanExtraWhitespace(input) + assert(actual == expected) + } + } + + "clean bullets" should "remove a leading bullet character" taggedAs FastTest in { + val inputs = Seq( + """โ— An excellent point!""", + """โ—โ— An excellent point!""", + """โ— An excellent point! โ—โ—โ—""", + """An excellent point!""", + """Morse code! โ—โ—โ—""") + + val expectedOutputs = Seq( + "An excellent point!", + """โ— An excellent point!""", + "An excellent point! โ—โ—โ—", + "An excellent point!", + "Morse code! โ—โ—โ—") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanBullets(input) + assert(actual == expected) + } + } + + it should "remove a leading bullet unicode characters" taggedAs FastTest in { + val inputs = Seq( + "\u2022 Item 1", + "\u2022 Item 2", + "\u2043Item with dash bullet", + "\u2022", + "\u2022\u2022 Multiple bullets") + + val expectedOutputs = + Seq("Item 1", "Item 2", "Item with dash bullet", "", "\u2022 Multiple bullets") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = cleanBullets(input) + assert(actual == expected) + } + } + + it should "handle empty strings" in { + val input = "" + val expected = "" + assert(cleanBullets(input) == expected) + } + + it should "replace unicode characters" in { + val inputs = Seq( + """\x93A lovely quote!\x94""", + """\x91A lovely quote!\x92""", + """Our dog's bowl.""") + val expectedOutputs = Seq("โ€œA lovely quote!โ€", "โ€˜A lovely quote!โ€™", "Our dog's bowl.") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + assert(replaceUnicodeCharacters(input) == expected) + } + } + + it should "clean non-ascii characters" taggedAs FastTest in { + val inputs = Seq( + """\x88This text contains non-ascii characters!\x88""", + """\x93A lovely quote!\x94""", + """โ— An excellent point! โ—โ—โ—""", + """Item\xa01A""", + """Our dog's bowl.""", + """5 w=E2=80=99s""") + + val expectedOutputs = Seq( + "This text contains non-ascii characters!", + "A lovely quote!", + " An excellent point! ", + "Item1A", + "Our dog's bowl.", + "5 w=E2=80=99s") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + assert(cleanNonAsciiChars(input) == expected) + } + } + + "cleanOrderedBullets" should "remove ordered bullets" taggedAs FastTest in { + val inputs = Seq( + "1. Introduction:", + "a. Introduction:", + "20.3 Morse code โ—โ—โ—", + "5.3.1 Convolutional Networks ", + "D.b.C Recurrent Neural Networks", + "2.b.1 Recurrent Neural Networks", + "eins. Neural Networks", + "bb.c Feed Forward Neural Networks", + "aaa.ccc Metrics", + " version = 3.8", + "1 2. 3 4", + "1) 2. 3 4", + "2,3. Morse code 3. โ—โ—โ—", + "1..2.3 four", + "Fig. 2: The relationship", + "23 is everywhere") + + val expectedOutputs = Seq( + "Introduction:", + "Introduction:", + "Morse code โ—โ—โ—", + "Convolutional Networks", + "Recurrent Neural Networks", + "Recurrent Neural Networks", + "eins. Neural Networks", + "Feed Forward Neural Networks", + "aaa.ccc Metrics", + " version = 3.8", + "1 2. 3 4", + "1) 2. 3 4", + "2,3. Morse code 3. โ—โ—โ—", + "1..2.3 four", + "Fig. 2: The relationship", + "23 is everywhere") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + assert(cleanOrderedBullets(input) == expected) + } + } + + "removePunctuation" should "remove punctuation" taggedAs FastTest in { + val inputs = Seq("""โ€œA lovely quote!โ€""", """โ€˜A lovely quote!โ€™""", """'()[]{};:'\",.?/\\-_""") + + val expectedOutputs = Seq("A lovely quote", "A lovely quote", "") + + inputs.zip(expectedOutputs).foreach { case (input, expected) => + val actual = removePunctuation(input) + assert(actual == expected) + } + } + + "cleanPrefix" should "remove the prefix and any following punctuation/whitespace" taggedAs FastTest in { + val testCases = Table( + ("description", "text", "pattern", "ignoreCase", "strip", "expected"), + ( + "Standard summary removal", + "SUMMARY: A great SUMMARY", + "(SUMMARY|DESC)", + false, + true, + "A great SUMMARY"), + ( + "Desc removal with case-sensitive match", + "DESC: A great SUMMARY", + "(SUMMARY|DESC)", + false, + true, + "A great SUMMARY"), + ( + "Without extra stripping", + "SUMMARY: A great SUMMARY", + "(SUMMARY|DESC)", + false, + false, + "A great SUMMARY"), + ( + "Removal with case ignored", + "desc: A great SUMMARY", + "(SUMMARY|DESC)", + true, + true, + "A great SUMMARY")) + + forAll(testCases) { (desc, text, pattern, ignoreCase, strip, expected) => + withClue(s"Failed in case: $desc") { + val actual = cleanPrefix(text, pattern, ignoreCase, strip) + assert(actual == expected) + } + } + } + + "cleanPostfix" should "remove the postfix and any following punctuation/whitespace" taggedAs FastTest in { + val testCases = Table( + ("description", "text", "pattern", "ignoreCase", "strip", "expected"), + ("Remove trailing 'END' with strip", "The END! END", "(END|STOP)", false, true, "The END!"), + ( + "Remove trailing 'STOP' with strip", + "The END! STOP", + "(END|STOP)", + false, + true, + "The END!"), + ( + "Keep trailing whitespace when not stripping", + "The END! END", + "(END|STOP)", + false, + false, + "The END! "), + ( + "Remove trailing 'end' ignoring case", + "The END! end", + "(END|STOP)", + true, + true, + "The END!")) + + forAll(testCases) { (description, text, pattern, ignoreCase, strip, expected) => + withClue(s"Failed in case: $description") { + val actual = cleanPostfix(text, pattern, ignoreCase, strip) + assert(actual == expected) + } + } + } + + "bytesStringToAnnotation" should "correctly decode a hex-encoded UTF-8 byte string containing Chinese characters" in { + val text = """\xe6\xaf\x8f\xe6\x97\xa5\xe6\x96\xb0\xe9\x97\xbb""" + val encoding = "utf-8" + val expected = "ๆฏๆ—ฅๆ–ฐ้—ป" + + val actual = CleanerHelper.bytesStringToString(text, encoding) + + assert(actual == expected) + } + + it should "correctly decode a hex-encoded UTF-8 byte string containing emoticons" taggedAs FastTest in { + val text = """Hello รฐ\x9f\x98\x80""" + val encoding = "utf-8" + val expected = "Hello ๐Ÿ˜€" + + val actual = CleanerHelper.bytesStringToString(text, encoding) + + assert(actual == expected) + } + + +} From 0dc22ebb3fc1115b9d077d4d0a115c74f4b5c5ae Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 17 Mar 2025 14:46:32 -0500 Subject: [PATCH 093/108] Adding missing bracket in SparkNLPReader and formatting some files --- .../ml/ai/DistilBertClassification.scala | 4 +- .../ml/ai/RoBertaClassification.scala | 4 +- .../ml/ai/XlmRoBertaClassification.scala | 4 +- .../dl/AlbertForMultipleChoice.scala | 232 ++++++++--------- .../dl/DistilBertForMultipleChoice.scala | 83 +++--- .../dl/XlmRoBertaForMultipleChoice.scala | 239 +++++++++--------- .../nlp/annotators/cleaners/Cleaner.scala | 119 ++++----- .../cleaners/util/CleanerHelper.scala | 122 +++++---- .../johnsnowlabs/reader/SparkNLPReader.scala | 104 ++++---- .../DistilBertForMultipleChoiceTestSpec.scala | 2 - .../cleaners/util/CleanerHelperTestSpec.scala | 1 - 11 files changed, 454 insertions(+), 460 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala b/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala index 9ecac49127535d..b6d4c2778190b5 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/DistilBertClassification.scala @@ -496,9 +496,7 @@ private[johnsnowlabs] class DistilBertClassification( val segmentTensors = OnnxTensor.createTensor(ortEnv, tokenTypeIds) val inputs = - Map( - "input_ids" -> tokenTensors, - "attention_mask" -> maskTensors).asJava + Map("input_ids" -> tokenTensors, "attention_mask" -> maskTensors).asJava try { val output = ortSession.run(inputs) diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala b/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala index de277ae71a3259..3743435a2f487f 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/RoBertaClassification.scala @@ -498,9 +498,7 @@ private[johnsnowlabs] class RoBertaClassification( val maskTensors = OnnxTensor.createTensor(ortEnv, attentionMask) val inputs = - Map( - "input_ids" -> tokenTensors, - "attention_mask" -> maskTensors).asJava + Map("input_ids" -> tokenTensors, "attention_mask" -> maskTensors).asJava try { val output = ortSession.run(inputs) diff --git a/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala b/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala index 6729b5240a5fa0..c0e1698108150f 100644 --- a/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala +++ b/src/main/scala/com/johnsnowlabs/ml/ai/XlmRoBertaClassification.scala @@ -490,9 +490,7 @@ private[johnsnowlabs] class XlmRoBertaClassification( val maskTensors = OnnxTensor.createTensor(ortEnv, attentionMask) val inputs = - Map( - "input_ids" -> tokenTensors, - "attention_mask" -> maskTensors).asJava + Map("input_ids" -> tokenTensors, "attention_mask" -> maskTensors).asJava try { val output = ortSession.run(inputs) diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala index 99d76187d362cf..5cfb4f4cb0eb2b 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoice.scala @@ -37,82 +37,82 @@ import org.apache.spark.ml.param.{IntParam, Param} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.SparkSession -/** AlbertForMultipleChoice can load ALBERT Models with a multiple choice classification head on top - * (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. - * - * Pretrained models can be loaded with `pretrained` of the companion object: - * {{{ - * val spanClassifier = AlbertForMultipleChoice.pretrained() - * .setInputCols(Array("document_question", "document_context")) - * .setOutputCol("answer") - * }}} - * The default model is `"albert_base_uncased_multiple_choice"`, if no name is provided. - * - * For available pretrained models please see the - * [[https://sparknlp.org/models?task=Multiple+Choice Models Hub]]. - * - * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To - * see which models are compatible and how to import them see - * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended - * examples, see - * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTestSpec.scala AlbertForMultipleChoiceTestSpec]]. - * - * ==Example== - * {{{ - * import spark.implicits._ - * import com.johnsnowlabs.nlp.base._ - * import com.johnsnowlabs.nlp.annotator._ - * import org.apache.spark.ml.Pipeline - * - * val document = new MultiDocumentAssembler() - * .setInputCols("question", "context") - * .setOutputCols("document_question", "document_context") - * - * val questionAnswering = AlbertForMultipleChoice.pretrained() - * .setInputCols(Array("document_question", "document_context")) - * .setOutputCol("answer") - * .setCaseSensitive(false) - * - * val pipeline = new Pipeline().setStages(Array( - * document, - * questionAnswering - * )) - * - * val data = Seq("The Eiffel Tower is located in which country?", "Germany, France, Italy").toDF("question", "context") - * val result = pipeline.fit(data).transform(data) - * - * result.select("answer.result").show(false) - * +---------------------+ - * |result | - * +---------------------+ - * |[France] | - * ++--------------------+ - * }}} - * - * @see - * [[AlbertForQuestionAnswering]] for Question Answering tasks - * @see - * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer - * based classifiers - * @param uid - * required uid for storing annotator to disk - * @groupname anno Annotator types - * @groupdesc anno - * Required input and expected output annotator types - * @groupname Ungrouped Members - * @groupname param Parameters - * @groupname setParam Parameter setters - * @groupname getParam Parameter getters - * @groupname Ungrouped Members - * @groupprio param 1 - * @groupprio anno 2 - * @groupprio Ungrouped 3 - * @groupprio setParam 4 - * @groupprio getParam 5 - * @groupdesc param - * A list of (hyper-)parameter keys this annotator can take. Users can set and get the - * parameter values through setters and getters, respectively. - */ +/** AlbertForMultipleChoice can load ALBERT Models with a multiple choice classification head on + * top (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val spanClassifier = AlbertForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * }}} + * The default model is `"albert_base_uncased_multiple_choice"`, if no name is provided. + * + * For available pretrained models please see the + * [[https://sparknlp.org/models?task=Multiple+Choice Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/AlbertForMultipleChoiceTestSpec.scala AlbertForMultipleChoiceTestSpec]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val document = new MultiDocumentAssembler() + * .setInputCols("question", "context") + * .setOutputCols("document_question", "document_context") + * + * val questionAnswering = AlbertForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * .setCaseSensitive(false) + * + * val pipeline = new Pipeline().setStages(Array( + * document, + * questionAnswering + * )) + * + * val data = Seq("The Eiffel Tower is located in which country?", "Germany, France, Italy").toDF("question", "context") + * val result = pipeline.fit(data).transform(data) + * + * result.select("answer.result").show(false) + * +---------------------+ + * |result | + * +---------------------+ + * |[France] | + * ++--------------------+ + * }}} + * + * @see + * [[AlbertForQuestionAnswering]] for Question Answering tasks + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ class AlbertForMultipleChoice(override val uid: String) extends AnnotatorModel[AlbertForMultipleChoice] @@ -121,21 +121,22 @@ class AlbertForMultipleChoice(override val uid: String) with WriteOnnxModel with WriteOpenvinoModel with WriteSentencePieceModel - with HasCaseSensitiveProperties - with HasEngine { + with HasCaseSensitiveProperties + with HasEngine { /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator - * type - */ + * type + */ def this() = this(Identifiable.randomUID("AlbertForMultipleChoice")) - override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) + override val inputAnnotatorTypes: Array[AnnotatorType] = + Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) override val outputAnnotatorType: AnnotatorType = AnnotatorType.CHUNK /** Max sentence length to process (Default: `128`) - * - * @group param - */ + * + * @group param + */ val maxSentenceLength = new IntParam(this, "maxSentenceLength", "Max sentence length to process") @@ -161,11 +162,11 @@ class AlbertForMultipleChoice(override val uid: String) /** @group setParam */ def setModelIfNotSet( - spark: SparkSession, - tensorflowWrapper: Option[TensorflowWrapper], - onnxWrapper: Option[OnnxWrapper], - openvinoWrapper: Option[OpenvinoWrapper], - spp: SentencePieceWrapper): AlbertForMultipleChoice = { + spark: SparkSession, + tensorflowWrapper: Option[TensorflowWrapper], + onnxWrapper: Option[OnnxWrapper], + openvinoWrapper: Option[OpenvinoWrapper], + spp: SentencePieceWrapper): AlbertForMultipleChoice = { if (_model.isEmpty) { _model = Some( spark.sparkContext.broadcast( @@ -174,10 +175,7 @@ class AlbertForMultipleChoice(override val uid: String) onnxWrapper, openvinoWrapper, spp, - tags = Map.empty[String, Int], - ) - ) - ) + tags = Map.empty[String, Int]))) } this @@ -187,31 +185,30 @@ class AlbertForMultipleChoice(override val uid: String) def getModelIfNotSet: AlbertClassification = _model.get.value /** Whether to lowercase tokens or not (Default: `false`). - * - * @group setParam - */ + * + * @group setParam + */ override def setCaseSensitive(value: Boolean): this.type = set(this.caseSensitive, value) setDefault( batchSize -> 8, maxSentenceLength -> 128, caseSensitive -> false, - choicesDelimiter -> "," - ) + choicesDelimiter -> ",") /** takes a document and annotations and produces new annotations of this annotator's annotation - * type - * - * @param batchedAnnotations - * Annotations in batches that correspond to inputAnnotationCols generated by previous - * annotators if any - * @return - * any number of annotations processed for every batch of input annotations. Not necessary - * one to one relationship - * - * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences - * that belong to the same original row !! (challenging) - */ + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + * + * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences + * that belong to the same original row !! (challenging) + */ override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { batchedAnnotations.map(annotations => { if (annotations.nonEmpty) { @@ -261,7 +258,7 @@ class AlbertForMultipleChoice(override val uid: String) } trait ReadablePretrainedAlbertForMultipleChoiceModel - extends ParamsAndFeaturesReadable[AlbertForMultipleChoice] + extends ParamsAndFeaturesReadable[AlbertForMultipleChoice] with HasPretrained[AlbertForMultipleChoice] { override val defaultModelName: Some[String] = Some("albert_base_uncased_multiple_choice") @@ -273,7 +270,10 @@ trait ReadablePretrainedAlbertForMultipleChoiceModel override def pretrained(name: String, lang: String): AlbertForMultipleChoice = super.pretrained(name, lang) - override def pretrained(name: String, lang: String, remoteLoc: String): AlbertForMultipleChoice = + override def pretrained( + name: String, + lang: String, + remoteLoc: String): AlbertForMultipleChoice = super.pretrained(name, lang, remoteLoc) } @@ -348,9 +348,9 @@ trait ReadAlbertForMultipleChoiceModel } } -/** This is the companion object of [[AlbertForMultipleChoice]]. Please refer to that class for the - * documentation. - */ +/** This is the companion object of [[AlbertForMultipleChoice]]. Please refer to that class for + * the documentation. + */ object AlbertForMultipleChoice - extends ReadablePretrainedAlbertForMultipleChoiceModel + extends ReadablePretrainedAlbertForMultipleChoiceModel with ReadAlbertForMultipleChoiceModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala index dedf00525a2360..5c4210d211a7ca 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoice.scala @@ -14,7 +14,6 @@ * limitations under the License. */ - package com.johnsnowlabs.nlp.annotators.classifier.dl import com.johnsnowlabs.ml.ai.DistilBertClassification @@ -37,23 +36,24 @@ import org.apache.spark.sql.SparkSession class DistilBertForMultipleChoice(override val uid: String) extends AnnotatorModel[DistilBertForMultipleChoice] with HasBatchedAnnotate[DistilBertForMultipleChoice] - with WriteOnnxModel - with WriteOpenvinoModel - with HasCaseSensitiveProperties - with HasEngine { + with WriteOnnxModel + with WriteOpenvinoModel + with HasCaseSensitiveProperties + with HasEngine { /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator - * type - */ + * type + */ def this() = this(Identifiable.randomUID("DistilBertForMultipleChoice")) - override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) + override val inputAnnotatorTypes: Array[AnnotatorType] = + Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) override val outputAnnotatorType: AnnotatorType = AnnotatorType.CHUNK /** Vocabulary used to encode the words to ids with WordPieceEncoder - * - * @group param - */ + * + * @group param + */ val vocabulary: MapFeature[String, Int] = new MapFeature(this, "vocabulary").setProtected() /** @group setParam */ @@ -70,9 +70,9 @@ class DistilBertForMultipleChoice(override val uid: String) } /** Max sentence length to process (Default: `512`) - * - * @group param - */ + * + * @group param + */ val maxSentenceLength = new IntParam(this, "maxSentenceLength", "Max sentence length to process") @@ -98,8 +98,7 @@ class DistilBertForMultipleChoice(override val uid: String) spark: SparkSession, tensorflowWrapper: Option[TensorflowWrapper], onnxWrapper: Option[OnnxWrapper], - openvinoWrapper: Option[OpenvinoWrapper], - ): DistilBertForMultipleChoice = { + openvinoWrapper: Option[OpenvinoWrapper]): DistilBertForMultipleChoice = { if (_model.isEmpty) { _model = Some( spark.sparkContext.broadcast( @@ -120,9 +119,9 @@ class DistilBertForMultipleChoice(override val uid: String) def getModelIfNotSet: DistilBertClassification = _model.get.value /** Whether to lowercase tokens or not (Default: `true`). - * - * @group setParam - */ + * + * @group setParam + */ override def setCaseSensitive(value: Boolean): this.type = set(this.caseSensitive, value) setDefault( @@ -132,18 +131,18 @@ class DistilBertForMultipleChoice(override val uid: String) choicesDelimiter -> ",") /** takes a document and annotations and produces new annotations of this annotator's annotation - * type - * - * @param batchedAnnotations - * Annotations in batches that correspond to inputAnnotationCols generated by previous - * annotators if any - * @return - * any number of annotations processed for every batch of input annotations. Not necessary - * one to one relationship - * - * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences - * that belong to the same original row !! (challenging) - */ + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + * + * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences + * that belong to the same original row !! (challenging) + */ override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { batchedAnnotations.map(annotations => { if (annotations.nonEmpty) { @@ -182,7 +181,7 @@ class DistilBertForMultipleChoice(override val uid: String) } trait ReadablePretrainedDistilBertForMultipleChoiceModel - extends ParamsAndFeaturesReadable[DistilBertForMultipleChoice] + extends ParamsAndFeaturesReadable[DistilBertForMultipleChoice] with HasPretrained[DistilBertForMultipleChoice] { override val defaultModelName: Some[String] = Some("distilbert_base_uncased_multiple_choice") @@ -194,7 +193,10 @@ trait ReadablePretrainedDistilBertForMultipleChoiceModel override def pretrained(name: String, lang: String): DistilBertForMultipleChoice = super.pretrained(name, lang) - override def pretrained(name: String, lang: String, remoteLoc: String): DistilBertForMultipleChoice = + override def pretrained( + name: String, + lang: String, + remoteLoc: String): DistilBertForMultipleChoice = super.pretrained(name, lang, remoteLoc) } @@ -204,7 +206,10 @@ trait ReadDistilBertForMultipleChoiceModel extends ReadOnnxModel with ReadOpenvi override val onnxFile: String = "distilbert_mc_classification_onnx" override val openvinoFile: String = "distilbert_mc_classification_openvino" - def readModel(instance: DistilBertForMultipleChoice, path: String, spark: SparkSession): Unit = { + def readModel( + instance: DistilBertForMultipleChoice, + path: String, + spark: SparkSession): Unit = { instance.getEngine match { case ONNX.name => val onnxWrapper = @@ -251,9 +256,9 @@ trait ReadDistilBertForMultipleChoiceModel extends ReadOnnxModel with ReadOpenvi } -/** This is the companion object of [[DistilBertForMultipleChoice]]. Please refer to that class for the - * documentation. - */ +/** This is the companion object of [[DistilBertForMultipleChoice]]. Please refer to that class + * for the documentation. + */ object DistilBertForMultipleChoice - extends ReadablePretrainedDistilBertForMultipleChoiceModel - with ReadDistilBertForMultipleChoiceModel \ No newline at end of file + extends ReadablePretrainedDistilBertForMultipleChoiceModel + with ReadDistilBertForMultipleChoiceModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala index 338230a35f8b81..cf13af8aba7f53 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/XlmRoBertaForMultipleChoice.scala @@ -37,82 +37,82 @@ import org.apache.spark.ml.param.{IntParam, Param} import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.SparkSession -/** RoBertaForMultipleChoice can load BERT Models with a multiple choice classification head on top - * (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. - * - * Pretrained models can be loaded with `pretrained` of the companion object: - * {{{ - * val spanClassifier = RoBertaForMultipleChoice.pretrained() - * .setInputCols(Array("document_question", "document_context")) - * .setOutputCol("answer") - * }}} - * The default model is `"bert_base_uncased_multiple_choice"`, if no name is provided. - * - * For available pretrained models please see the - * [[https://sparknlp.org/models?task=Multiple+Choice Models Hub]]. - * - * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To - * see which models are compatible and how to import them see - * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended - * examples, see - * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RoBertaForMultipleChoiceTestSpec.scala RoBertaForMultipleChoiceTestSpec]]. - * - * ==Example== - * {{{ - * import spark.implicits._ - * import com.johnsnowlabs.nlp.base._ - * import com.johnsnowlabs.nlp.annotator._ - * import org.apache.spark.ml.Pipeline - * - * val document = new MultiDocumentAssembler() - * .setInputCols("question", "context") - * .setOutputCols("document_question", "document_context") - * - * val questionAnswering = RoBertaForMultipleChoice.pretrained() - * .setInputCols(Array("document_question", "document_context")) - * .setOutputCol("answer") - * .setCaseSensitive(false) - * - * val pipeline = new Pipeline().setStages(Array( - * document, - * questionAnswering - * )) - * - * val data = Seq("The Eiffel Tower is located in which country?", "Germany, France, Italy").toDF("question", "context") - * val result = pipeline.fit(data).transform(data) - * - * result.select("answer.result").show(false) - * +---------------------+ - * |result | - * +---------------------+ - * |[France] | - * ++--------------------+ - * }}} - * - * @see - * [[BertForQuestionAnswering]] for Question Answering tasks - * @see - * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer - * based classifiers - * @param uid - * required uid for storing annotator to disk - * @groupname anno Annotator types - * @groupdesc anno - * Required input and expected output annotator types - * @groupname Ungrouped Members - * @groupname param Parameters - * @groupname setParam Parameter setters - * @groupname getParam Parameter getters - * @groupname Ungrouped Members - * @groupprio param 1 - * @groupprio anno 2 - * @groupprio Ungrouped 3 - * @groupprio setParam 4 - * @groupprio getParam 5 - * @groupdesc param - * A list of (hyper-)parameter keys this annotator can take. Users can set and get the - * parameter values through setters and getters, respectively. - */ +/** RoBertaForMultipleChoice can load BERT Models with a multiple choice classification head on + * top (a linear layer on top of the pooled output and a softmax) e.g. for RocStories/SWAG tasks. + * + * Pretrained models can be loaded with `pretrained` of the companion object: + * {{{ + * val spanClassifier = RoBertaForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * }}} + * The default model is `"bert_base_uncased_multiple_choice"`, if no name is provided. + * + * For available pretrained models please see the + * [[https://sparknlp.org/models?task=Multiple+Choice Models Hub]]. + * + * Models from the HuggingFace ๐Ÿค— Transformers library are also compatible with Spark NLP ๐Ÿš€. To + * see which models are compatible and how to import them see + * [[https://github.com/JohnSnowLabs/spark-nlp/discussions/5669]] and to see more extended + * examples, see + * [[https://github.com/JohnSnowLabs/spark-nlp/blob/master/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/RoBertaForMultipleChoiceTestSpec.scala RoBertaForMultipleChoiceTestSpec]]. + * + * ==Example== + * {{{ + * import spark.implicits._ + * import com.johnsnowlabs.nlp.base._ + * import com.johnsnowlabs.nlp.annotator._ + * import org.apache.spark.ml.Pipeline + * + * val document = new MultiDocumentAssembler() + * .setInputCols("question", "context") + * .setOutputCols("document_question", "document_context") + * + * val questionAnswering = RoBertaForMultipleChoice.pretrained() + * .setInputCols(Array("document_question", "document_context")) + * .setOutputCol("answer") + * .setCaseSensitive(false) + * + * val pipeline = new Pipeline().setStages(Array( + * document, + * questionAnswering + * )) + * + * val data = Seq("The Eiffel Tower is located in which country?", "Germany, France, Italy").toDF("question", "context") + * val result = pipeline.fit(data).transform(data) + * + * result.select("answer.result").show(false) + * +---------------------+ + * |result | + * +---------------------+ + * |[France] | + * ++--------------------+ + * }}} + * + * @see + * [[BertForQuestionAnswering]] for Question Answering tasks + * @see + * [[https://sparknlp.org/docs/en/annotators Annotators Main Page]] for a list of transformer + * based classifiers + * @param uid + * required uid for storing annotator to disk + * @groupname anno Annotator types + * @groupdesc anno + * Required input and expected output annotator types + * @groupname Ungrouped Members + * @groupname param Parameters + * @groupname setParam Parameter setters + * @groupname getParam Parameter getters + * @groupname Ungrouped Members + * @groupprio param 1 + * @groupprio anno 2 + * @groupprio Ungrouped 3 + * @groupprio setParam 4 + * @groupprio getParam 5 + * @groupdesc param + * A list of (hyper-)parameter keys this annotator can take. Users can set and get the + * parameter values through setters and getters, respectively. + */ class XlmRoBertaForMultipleChoice(override val uid: String) extends AnnotatorModel[XlmRoBertaForMultipleChoice] @@ -123,28 +123,28 @@ class XlmRoBertaForMultipleChoice(override val uid: String) with HasCaseSensitiveProperties with HasEngine { - /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator - * type - */ + * type + */ def this() = this(Identifiable.randomUID("XlmRoBertaForMultipleChoice")) /** Input Annotator Types: DOCUMENT, DOCUMENT - * - * @group anno - */ - override val inputAnnotatorTypes: Array[AnnotatorType] = Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) + * + * @group anno + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = + Array(AnnotatorType.DOCUMENT, AnnotatorType.DOCUMENT) /** Output Annotator Types: CHUNK - * - * @group anno - */ + * + * @group anno + */ override val outputAnnotatorType: AnnotatorType = AnnotatorType.CHUNK /** Max sentence length to process (Default: `128`) - * - * @group param - */ + * + * @group param + */ val maxSentenceLength = new IntParam(this, "maxSentenceLength", "Max sentence length to process") @@ -180,9 +180,7 @@ class XlmRoBertaForMultipleChoice(override val uid: String) onnxWrapper, openvinoWrapper, spp, - tags = Map.empty[String, Int]) - ) - ) + tags = Map.empty[String, Int]))) } this @@ -192,32 +190,30 @@ class XlmRoBertaForMultipleChoice(override val uid: String) def getModelIfNotSet: XlmRoBertaClassification = _model.get.value /** Whether to lowercase tokens or not (Default: `true`). - * - * @group setParam - */ + * + * @group setParam + */ override def setCaseSensitive(value: Boolean): this.type = set(this.caseSensitive, value) setDefault( batchSize -> 8, maxSentenceLength -> 128, caseSensitive -> true, - choicesDelimiter -> "," - ) - + choicesDelimiter -> ",") /** takes a document and annotations and produces new annotations of this annotator's annotation - * type - * - * @param batchedAnnotations - * Annotations in batches that correspond to inputAnnotationCols generated by previous - * annotators if any - * @return - * any number of annotations processed for every batch of input annotations. Not necessary - * one to one relationship - * - * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences - * that belong to the same original row !! (challenging) - */ + * type + * + * @param batchedAnnotations + * Annotations in batches that correspond to inputAnnotationCols generated by previous + * annotators if any + * @return + * any number of annotations processed for every batch of input annotations. Not necessary + * one to one relationship + * + * IMPORTANT: !MUST! return sequences of equal lengths !! IMPORTANT: !MUST! return sentences + * that belong to the same original row !! (challenging) + */ override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { batchedAnnotations.map(annotations => { if (annotations.nonEmpty) { @@ -262,7 +258,7 @@ class XlmRoBertaForMultipleChoice(override val uid: String) } trait ReadablePretrainedXmlRoBertaForMultipleChoiceModel - extends ParamsAndFeaturesReadable[XlmRoBertaForMultipleChoice] + extends ParamsAndFeaturesReadable[XlmRoBertaForMultipleChoice] with HasPretrained[XlmRoBertaForMultipleChoice] { override val defaultModelName: Some[String] = Some("bert_base_uncased_multiple_choice") @@ -274,12 +270,15 @@ trait ReadablePretrainedXmlRoBertaForMultipleChoiceModel override def pretrained(name: String, lang: String): XlmRoBertaForMultipleChoice = super.pretrained(name, lang) - override def pretrained(name: String, lang: String, remoteLoc: String): XlmRoBertaForMultipleChoice = + override def pretrained( + name: String, + lang: String, + remoteLoc: String): XlmRoBertaForMultipleChoice = super.pretrained(name, lang, remoteLoc) } trait ReadRoBertaForMultipleChoiceModelDLModel - extends ReadOnnxModel + extends ReadOnnxModel with ReadOpenvinoModel with ReadSentencePieceModel { this: ParamsAndFeaturesReadable[XlmRoBertaForMultipleChoice] => @@ -288,7 +287,10 @@ trait ReadRoBertaForMultipleChoiceModelDLModel override val openvinoFile: String = "xlm_roberta_mc_classification_openvino" override val sppFile: String = "xlmroberta_spp" - def readModel(instance: XlmRoBertaForMultipleChoice, path: String, spark: SparkSession): Unit = { + def readModel( + instance: XlmRoBertaForMultipleChoice, + path: String, + spark: SparkSession): Unit = { val spp = readSentencePieceModel(path, spark, "_xlmroberta_spp", sppFile) instance.getEngine match { case ONNX.name => @@ -333,7 +335,6 @@ trait ReadRoBertaForMultipleChoiceModelDLModel annotatorModel .setModelIfNotSet(spark, None, None, Some(ovWrapper), spModel) - case _ => throw new Exception(notSupportedEngineError) } @@ -344,8 +345,8 @@ trait ReadRoBertaForMultipleChoiceModelDLModel } /** This is the companion object of [[XlmRoBertaForMultipleChoice]]. Please refer to that class - * for the documentation. - */ + * for the documentation. + */ object XlmRoBertaForMultipleChoice - extends ReadablePretrainedXmlRoBertaForMultipleChoiceModel - with ReadRoBertaForMultipleChoiceModelDLModel \ No newline at end of file + extends ReadablePretrainedXmlRoBertaForMultipleChoiceModel + with ReadRoBertaForMultipleChoiceModelDLModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala index 71f6f692c12c1b..95e2648c75137d 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala @@ -31,8 +31,8 @@ import org.apache.spark.ml.util.Identifiable class Cleaner(override val uid: String) extends MarianTransformer { /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator - * type - */ + * type + */ def this() = this(Identifiable.randomUID("CLEANER")) /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator @@ -43,31 +43,28 @@ class Cleaner(override val uid: String) extends MarianTransformer { val encoding = new Param[String]( this, "encoding", - "The encoding to be used for decoding the byte string (default is utf-8)" - ) + "The encoding to be used for decoding the byte string (default is utf-8)") def setEncoding(value: String): this.type = set(this.encoding, value) val cleanPrefixPattern = new Param[String]( this, "cleanPrefixPattern", - "The pattern for the prefix. Can be a simple string or a regex pattern." - ) + "The pattern for the prefix. Can be a simple string or a regex pattern.") def setCleanPrefixPattern(value: String): this.type = set(this.cleanPrefixPattern, value) val cleanPostfixPattern = new Param[String]( this, "cleanPostfixPattern", - "The pattern for the postfix. Can be a simple string or a regex pattern." - ) + "The pattern for the postfix. Can be a simple string or a regex pattern.") def setCleanPostfixPattern(value: String): this.type = set(this.cleanPrefixPattern, value) /** cleanerMode can take the following values: - * - `bytes_string_to_string`: Converts a string representation of a byte string (e.g., containing escape sequences) to an Annotation structure using the specified encoding. - * - * */ + * - `bytes_string_to_string`: Converts a string representation of a byte string (e.g., + * containing escape sequences) to an Annotation structure using the specified encoding. + */ val cleanerMode: Param[String] = new Param[String]( this, "cleanerMode", @@ -91,59 +88,38 @@ class Cleaner(override val uid: String) extends MarianTransformer { set(this.cleanerMode, value) } - val extraWhitespace = new Param[Boolean]( - this, - "extraWhitespace", - "Whether to remove extra whitespace." - ) + val extraWhitespace = + new Param[Boolean](this, "extraWhitespace", "Whether to remove extra whitespace.") def setExtraWhitespace(value: Boolean): this.type = set(this.extraWhitespace, value) - val dashes = new Param[Boolean]( - this, - "dashes", - "Whether to handle dashes in text." - ) + val dashes = new Param[Boolean](this, "dashes", "Whether to handle dashes in text.") def setDashes(value: Boolean): this.type = set(this.dashes, value) - val bullets = new Param[Boolean]( - this, - "bullets", - "Whether to handle bullets in text." - ) + val bullets = new Param[Boolean](this, "bullets", "Whether to handle bullets in text.") def setBullets(value: Boolean): this.type = set(this.bullets, value) val trailingPunctuation = new Param[Boolean]( this, "trailingPunctuation", - "Whether to remove trailing punctuation from text." - ) + "Whether to remove trailing punctuation from text.") def setTrailingPunctuation(value: Boolean): this.type = set(this.trailingPunctuation, value) - val lowercase = new Param[Boolean]( - this, - "lowercase", - "Whether to convert text to lowercase." - ) + val lowercase = new Param[Boolean](this, "lowercase", "Whether to convert text to lowercase.") def setLowercase(value: Boolean): this.type = set(this.lowercase, value) - val ignoreCase = new Param[Boolean]( - this, - "ignoreCase", - "If true, ignores case in the pattern." - ) + val ignoreCase = new Param[Boolean](this, "ignoreCase", "If true, ignores case in the pattern.") def setIgnoreCase(value: Boolean): this.type = set(this.ignoreCase, value) val strip = new Param[Boolean]( this, "strip", - "If true, removes leading or trailing whitespace from the cleaned string." - ) + "If true, removes leading or trailing whitespace from the cleaned string.") def setStrip(value: Boolean): this.type = set(this.strip, value) @@ -156,8 +132,7 @@ class Cleaner(override val uid: String) extends MarianTransformer { lowercase -> false, ignoreCase -> false, strip -> true, - cleanerMode -> "translate" - ) + cleanerMode -> "translate") override def batchAnnotate(batchedAnnotations: Seq[Array[Annotation]]): Seq[Seq[Annotation]] = { require($(cleanerMode) != "undefined", "Extractor mode must be set.") @@ -169,13 +144,16 @@ class Cleaner(override val uid: String) extends MarianTransformer { batchedAnnotations.map { annotations => $(cleanerMode) match { case "clean" => annotations.map(buildAnnotation(clean)).toSeq - case "bytes_string_to_string" => annotations.map(buildAnnotation(bytesStringToString)).toSeq + case "bytes_string_to_string" => + annotations.map(buildAnnotation(bytesStringToString)).toSeq case "clean_non_ascii_chars" => annotations.map(buildAnnotation(cleanNonAsciiChars)).toSeq - case "clean_ordered_bullets" => annotations.map(buildAnnotation(cleanOrderedBullets)).toSeq + case "clean_ordered_bullets" => + annotations.map(buildAnnotation(cleanOrderedBullets)).toSeq case "clean_postfix" => annotations.map(buildAnnotation(cleanPostfix)).toSeq case "clean_prefix" => annotations.map(buildAnnotation(cleanPrefix)).toSeq case "remove_punctuation" => annotations.map(buildAnnotation(removePunctuation)).toSeq - case "replace_unicode_characters" => annotations.map(buildAnnotation(replaceUnicodeCharacters)).toSeq + case "replace_unicode_characters" => + annotations.map(buildAnnotation(replaceUnicodeCharacters)).toSeq } } } @@ -187,17 +165,17 @@ class Cleaner(override val uid: String) extends MarianTransformer { begin = 0, end = cleanText.length, result = cleanText, - metadata = Map() - ) + metadata = Map()) } - /** - * Converts a string representation of a byte string (e.g., containing escape sequences) - * to an Annotation structure using the specified encoding. - * - * @param text The string representation of the byte string. - * @return The String containing the decoded result - */ + /** Converts a string representation of a byte string (e.g., containing escape sequences) to an + * Annotation structure using the specified encoding. + * + * @param text + * The string representation of the byte string. + * @return + * The String containing the decoded result + */ private def bytesStringToString(text: String): String = { CleanerHelper.bytesStringToString(text, $(encoding)) } @@ -205,7 +183,8 @@ class Cleaner(override val uid: String) extends MarianTransformer { private def clean(text: String): String = { var cleanedText = if ($(lowercase)) text.toLowerCase else text - cleanedText = if ($(trailingPunctuation)) cleanTrailingPunctuation(cleanedText) else cleanedText + cleanedText = + if ($(trailingPunctuation)) cleanTrailingPunctuation(cleanedText) else cleanedText cleanedText = if ($(dashes)) cleanDashes(cleanedText) else cleanedText cleanedText = if ($(extraWhitespace)) cleanExtraWhitespace(cleanedText) else cleanedText cleanedText = if ($(bullets)) cleanBullets(cleanedText) else cleanedText @@ -213,22 +192,24 @@ class Cleaner(override val uid: String) extends MarianTransformer { cleanedText.trim } - /** - * Cleans a prefix from a string based on a pattern. - * - * @param text The text to clean. - * @return The cleaned string. - */ + /** Cleans a prefix from a string based on a pattern. + * + * @param text + * The text to clean. + * @return + * The cleaned string. + */ private def cleanPrefix(text: String): String = { CleanerHelper.cleanPrefix(text, $(cleanPrefixPattern), $(ignoreCase), $(strip)) } - /** - * Cleans a postfix from a string based on a pattern. - * - * @param text The text to clean. - * @return The cleaned string. - */ + /** Cleans a postfix from a string based on a pattern. + * + * @param text + * The text to clean. + * @return + * The cleaned string. + */ private def cleanPostfix(text: String): String = { CleanerHelper.cleanPostfix(text, $(cleanPrefixPattern), $(ignoreCase), $(strip)) } @@ -236,6 +217,6 @@ class Cleaner(override val uid: String) extends MarianTransformer { } object Cleaner - extends ReadablePretrainedMarianMTModel + extends ReadablePretrainedMarianMTModel with ReadMarianMTDLModel - with ReadSentencePieceModel \ No newline at end of file + with ReadSentencePieceModel diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala index bb89dc10cf0d1e..d343e3b458d44d 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala @@ -43,8 +43,7 @@ object CleanerHelper { "๏‚ท", "\\*", // Escaped for regex compatibility "\u0095", - "ยท" - ) + "ยท") private val BULLETS_PATTERN = UNICODE_BULLETS.map(Pattern.quote).mkString("|") private val UNICODE_BULLETS_RE: Regex = new Regex(s"(?:$BULLETS_PATTERN)") @@ -52,28 +51,34 @@ object CleanerHelper { private val HTML_APOSTROPHE_ENTITY: String = "'" private val HEXADECIMAL_ESCAPE_SEQUENCE: Regex = """\\x([0-9A-Fa-f]{2})""".r - /** - * Parses a string containing escape sequences (e.g., `\x9f`) into a byte array. - * - * @param text The input string with escape sequences. - * @return A byte array representing the parsed bytes. - */ + /** Parses a string containing escape sequences (e.g., `\x9f`) into a byte array. + * + * @param text + * The input string with escape sequences. + * @return + * A byte array representing the parsed bytes. + */ def parseEscapedBytes(text: String): Array[Byte] = { val RawByteCharset: Charset = Charset.forName("ISO-8859-1") // Replace escape sequences with their byte values - HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn(text, m => { - val hexValue = m.group(1) - Integer.parseInt(hexValue, 16).toChar.toString - }).getBytes(RawByteCharset) + HEXADECIMAL_ESCAPE_SEQUENCE + .replaceAllIn( + text, + m => { + val hexValue = m.group(1) + Integer.parseInt(hexValue, 16).toChar.toString + }) + .getBytes(RawByteCharset) } - /** - * Formats an input encoding string (e.g., `utf-8`, `iso-8859-1`, etc). - * - * @param encoding The encoding string to be formatted. - * @return The formatted encoding string. - */ + /** Formats an input encoding string (e.g., `utf-8`, `iso-8859-1`, etc). + * + * @param encoding + * The encoding string to be formatted. + * @return + * The formatted encoding string. + */ def formatEncodingStr(encoding: String): String = { var formattedEncoding = encoding.toLowerCase.replace("_", "-") @@ -122,9 +127,9 @@ object CleanerHelper { } def cleanNonAsciiChars(text: String): String = { - val decodedText = HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn(text, m => - Integer.parseInt(m.group(1), 16).toChar.toString - ) + val decodedText = HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn( + text, + m => Integer.parseInt(m.group(1), 16).toChar.toString) val entityReplacedText = decodedText.replace(HTML_APOSTROPHE_ENTITY, "'") entityReplacedText.replaceAll("[^\u0020-\u007E]", "") @@ -140,20 +145,25 @@ object CleanerHelper { if (!firstWord.contains(".") || firstWord.contains("..")) return text val bulletParts = firstWord.split("\\.") - val cleanedBulletParts = if (bulletParts.last.isEmpty) bulletParts.dropRight(1) else bulletParts + val cleanedBulletParts = + if (bulletParts.last.isEmpty) bulletParts.dropRight(1) else bulletParts if (cleanedBulletParts.head.length > 2) text else remainingText.trim } def replaceUnicodeCharacters(text: String): String = { - val decodedText = HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn(text, m => { - val hexValue = m.group(1) - val byteValue = Integer.parseInt(hexValue, 16).toByte - new String(Array(byteValue), Charset.forName("ISO-8859-1")) - }) - - val fullyDecodedText = new String(decodedText.getBytes(Charset.forName("ISO-8859-1")), Charset.forName("Windows-1252")) + val decodedText = HEXADECIMAL_ESCAPE_SEQUENCE.replaceAllIn( + text, + m => { + val hexValue = m.group(1) + val byteValue = Integer.parseInt(hexValue, 16).toByte + new String(Array(byteValue), Charset.forName("ISO-8859-1")) + }) + + val fullyDecodedText = new String( + decodedText.getBytes(Charset.forName("ISO-8859-1")), + Charset.forName("Windows-1252")) fullyDecodedText .replace("\u2018", "โ€˜") @@ -167,24 +177,26 @@ object CleanerHelper { .replace("รข\u0080ยฆ", "โ€ฆ") } - /** - * Removes punctuation from a given string. - * - * @params The input string. - * @return The string with punctuation removed. - */ + /** Removes punctuation from a given string. + * + * @params + * The input string. + * @return + * The string with punctuation removed. + */ def removePunctuation(text: String): String = { // \p{P} matches any kind of punctuation character in Unicode val punctuationRegex = """\p{P}""".r punctuationRegex.replaceAllIn(text, "") } - /** - * Cleans a prefix from a string based on a pattern. - * - * @param text The text to clean. - * @return The cleaned string. - */ + /** Cleans a prefix from a string based on a pattern. + * + * @param text + * The text to clean. + * @return + * The cleaned string. + */ def cleanPrefix(text: String, pattern: String, ignoreCase: Boolean, strip: Boolean): String = { val regexStr = if (ignoreCase) s"(?i)^$pattern[\\p{Punct}\\s]*" @@ -196,25 +208,27 @@ object CleanerHelper { if (strip) cleanedText.replaceAll("^\\s+", "") else cleanedText } - /** - * Cleans a postfix from a string based on a pattern. - * - * @param text The text to clean. - * @return The cleaned string. - */ + /** Cleans a postfix from a string based on a pattern. + * + * @param text + * The text to clean. + * @return + * The cleaned string. + */ def cleanPostfix(text: String, pattern: String, ignoreCase: Boolean, strip: Boolean): String = { val regex = if (ignoreCase) s"(?i)$pattern$$".r else s"$pattern$$".r val cleanedText = regex.replaceAllIn(text, "") if (strip) cleanedText.trim else cleanedText } - /** - * Converts a string representation of a byte string (e.g., containing escape sequences) - * to an Annotation structure using the specified encoding. - * - * @param text The string representation of the byte string. - * @return The String containing the decoded result - */ + /** Converts a string representation of a byte string (e.g., containing escape sequences) to an + * Annotation structure using the specified encoding. + * + * @param text + * The string representation of the byte string. + * @return + * The String containing the decoded result + */ def bytesStringToString(text: String, encoding: String): String = { val textBytes = parseEscapedBytes(text) val formattedEncoding = formatEncodingStr(encoding) diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index d2bc37b7ae85b2..b427a144742b21 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -212,59 +212,59 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM } /** Instantiates class to read PDF files. - * - * pdfPath: this is a path to a directory of PDF files or a path to an PDF file E.g. - * "path/pdfs/" - * - * ==Example== - * {{{ - * val pdfsPath = "home/user/pdfs-directory" - * val sparkNLPReader = new SparkNLPReader() - * val pdfDf = sparkNLPReader.pdf(pdfsPath) - * }}} - * - * ==Example 2== - * You can use SparkNLP for one line of code - * {{{ - * val pdfDf = SparkNLP.read.pdf(pdfsPath) - * }}} - * - * {{{ - * pdfDf.show(false) - * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ - * | path| modificationTime|length| text|height_dimension|width_dimension| content|exception|pagenum| - * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ - * |file:/content/pdf...|2025-01-15 20:48:...| 25803|This is a Title \...| 842| 596|[25 50 44 46 2D 3...| NULL| 0| - * |file:/content/pdf...|2025-01-15 20:48:...| 9487|This is a page.\n...| 841| 595|[25 50 44 46 2D 3...| NULL| 0| - * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ - * - * pdf_df.printSchema() - * root - * |-- path: string (nullable = true) - * |-- modificationTime: timestamp (nullable = true) - * |-- length: long (nullable = true) - * |-- text: string (nullable = true) - * |-- height_dimension: integer (nullable = true) - * |-- width_dimension: integer (nullable = true) - * |-- content: binary (nullable = true) - * |-- exception: string (nullable = true) - * |-- pagenum: integer (nullable = true) - * }}} - * - * @param params - * Parameter with custom configuration - */ + * + * pdfPath: this is a path to a directory of PDF files or a path to an PDF file E.g. + * "path/pdfs/" + * + * ==Example== + * {{{ + * val pdfsPath = "home/user/pdfs-directory" + * val sparkNLPReader = new SparkNLPReader() + * val pdfDf = sparkNLPReader.pdf(pdfsPath) + * }}} + * + * ==Example 2== + * You can use SparkNLP for one line of code + * {{{ + * val pdfDf = SparkNLP.read.pdf(pdfsPath) + * }}} + * + * {{{ + * pdfDf.show(false) + * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + * | path| modificationTime|length| text|height_dimension|width_dimension| content|exception|pagenum| + * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + * |file:/content/pdf...|2025-01-15 20:48:...| 25803|This is a Title \...| 842| 596|[25 50 44 46 2D 3...| NULL| 0| + * |file:/content/pdf...|2025-01-15 20:48:...| 9487|This is a page.\n...| 841| 595|[25 50 44 46 2D 3...| NULL| 0| + * +--------------------+--------------------+------+--------------------+----------------+---------------+--------------------+---------+-------+ + * + * pdf_df.printSchema() + * root + * |-- path: string (nullable = true) + * |-- modificationTime: timestamp (nullable = true) + * |-- length: long (nullable = true) + * |-- text: string (nullable = true) + * |-- height_dimension: integer (nullable = true) + * |-- width_dimension: integer (nullable = true) + * |-- content: binary (nullable = true) + * |-- exception: string (nullable = true) + * |-- pagenum: integer (nullable = true) + * }}} + * + * @param params + * Parameter with custom configuration + */ def pdf(pdfPath: String): DataFrame = { - val spark = ResourceHelper.spark - spark.conf.set("spark.sql.legacy.allowUntypedScalaUDF", "true") - val pdfToText = new PdfToText() - .setStoreSplittedPdf(getStoreSplittedPdf) - val binaryPdfDF = spark.read.format("binaryFile").load(pdfPath) - val pipelineModel = new Pipeline() - .setStages(Array(pdfToText)) - .fit(binaryPdfDF) + val spark = ResourceHelper.spark + spark.conf.set("spark.sql.legacy.allowUntypedScalaUDF", "true") + val pdfToText = new PdfToText() + .setStoreSplittedPdf(getStoreSplittedPdf) + val binaryPdfDF = spark.read.format("binaryFile").load(pdfPath) + val pipelineModel = new Pipeline() + .setStages(Array(pdfToText)) + .fit(binaryPdfDF) - pipelineModel.transform(binaryPdfDF) + pipelineModel.transform(binaryPdfDF) } private def getStoreSplittedPdf: Boolean = { @@ -275,6 +275,8 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM case _: IllegalArgumentException => false } splitPage + } + /** Instantiates class to read Excel files. * * docPath: this is a path to a directory of Excel files or a path to an HTML file E.g. diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala index 247b05d833b8dd..8648a3884a5302 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/classifier/dl/DistilBertForMultipleChoiceTestSpec.scala @@ -14,7 +14,6 @@ * limitations under the License. */ - package com.johnsnowlabs.nlp.annotators.classifier.dl import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations, MultiDocumentAssembler} @@ -34,7 +33,6 @@ class DistilBertForMultipleChoiceTestSpec extends AnyFlatSpec with SparkSessionT Seq(("The Eiffel Tower is located in which country?", "Germany, France, Italy")) .toDF("question", "context") - "DistilBertForMultipleChoiceTestSpec" should "answer a multiple choice question" taggedAs SlowTest in { val resultDf = pipelineModel.transform(testDataframe) resultDf.show(truncate = false) diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala index 82db8d8e6e83bf..391ada8e06306b 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelperTestSpec.scala @@ -346,5 +346,4 @@ class CleanerHelperTestSpec extends AnyFlatSpec { assert(actual == expected) } - } From 311f98803fb838518a83e3fc55a81083265f6ee5 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Tue, 18 Mar 2025 18:17:19 -0500 Subject: [PATCH 094/108] Adding misssing return dataframe for PDF reader in Python --- python/sparknlp/reader/sparknlp_reader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index cec206adc5185e..89178e44dc957f 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -272,6 +272,9 @@ def pdf(self, pdfPath): if not isinstance(pdfPath, str): raise TypeError("docPath must be a string") jdf = self._java_obj.pdf(pdfPath) + dataframe = self.getDataFrame(self.spark, jdf) + return dataframe + def xls(self, docPath): """Reads excel document files and returns a Spark DataFrame. From 9e7c2fc54cd7555f6d53aa99b273d67692025b0e Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 19 Mar 2025 15:23:32 -0500 Subject: [PATCH 095/108] Updating reader notebooks --- .../reader/SparkNLP_Excel_Reader_Demo.ipynb | 2 +- .../reader/SparkNLP_HTML_Reader_Demo.ipynb | 127 +----------------- .../SparkNLP_PDFToText_Annotator_Demo.ipynb | 33 +---- .../reader/SparkNLP_PDF_Reader_Demo.ipynb | 2 +- .../SparkNLP_PowerPoint_Reader_Demo.ipynb | 4 +- .../reader/SparkNLP_TXT_Reader_Demo.ipynb | 4 +- .../reader/SparkNLP_Word_Reader_Demo.ipynb | 2 +- 7 files changed, 11 insertions(+), 163 deletions(-) diff --git a/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb index 11152adfb89909..d5b3838b45c051 100644 --- a/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_Excel_Reader_Demo.ipynb @@ -30,7 +30,7 @@ "## Setup and Initialization\n", "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", - "Support for reading html files was introduced in Spark NLP 5.5.2. Please make sure you have upgraded to the latest Spark NLP release." + "Support for reading html files was introduced in Spark NLP 6.0.0. Please make sure you have upgraded to the latest Spark NLP release." ] }, { diff --git a/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb index 703630fb9d6fb9..955652e0474f98 100644 --- a/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_HTML_Reader_Demo.ipynb @@ -23,131 +23,6 @@ "- Versatile support for varied data ingestion scenarios." ] }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "xrvHhiTAdfGd", - "outputId": "4641f9f8-bcaf-4804-b909-7583c76f880d" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mounted at /content/drive\n" - ] - } - ], - "source": [ - "from google.colab import drive\n", - "drive.mount('/content/drive')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "id": "mjV3NcQ8eA52" - }, - "outputs": [], - "source": [ - "!cp drive/MyDrive/JSL/sparknlp/sparknlp.jar .\n", - "!cp drive/MyDrive/JSL/sparknlp/spark_nlp-5.5.3-py2.py3-none-any.whl ." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "pEmutNjReCgc", - "outputId": "999d0e5a-a849-4ff7-e03d-86ed753ec53d" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: pyspark in /usr/local/lib/python3.11/dist-packages (3.5.5)\n", - "Requirement already satisfied: py4j==0.10.9.7 in /usr/local/lib/python3.11/dist-packages (from pyspark) (0.10.9.7)\n" - ] - } - ], - "source": [ - "!pip install pyspark" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "3qjPeDjvfCpA", - "outputId": "d6ada02d-57f1-4a73-dbcc-f4e1691224f8" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Processing ./spark_nlp-5.5.3-py2.py3-none-any.whl\n", - "Installing collected packages: spark-nlp\n", - "Successfully installed spark-nlp-5.5.3\n" - ] - } - ], - "source": [ - "!pip install spark_nlp-5.5.3-py2.py3-none-any.whl" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "DczWop6QeE8F", - "outputId": "c267cdef-37a3-43a2-9198-f8fe74100f97" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Apache Spark version: 3.5.5\n" - ] - } - ], - "source": [ - "# import sparknlp\n", - "# # let's start Spark with Spark NLP\n", - "# spark = sparknlp.start()\n", - "\n", - "from pyspark.sql import SparkSession\n", - "\n", - "spark = SparkSession.builder \\\n", - " .appName(\"SparkNLP\") \\\n", - " .master(\"local[*]\") \\\n", - " .config(\"spark.driver.memory\", \"12G\") \\\n", - " .config(\"spark.serializer\", \"org.apache.spark.serializer.KryoSerializer\") \\\n", - " .config(\"spark.kryoserializer.buffer.max\", \"2000M\") \\\n", - " .config(\"spark.driver.maxResultSize\", \"0\") \\\n", - " .config(\"spark.jars\", \"./sparknlp.jar\") \\\n", - " .getOrCreate()\n", - "\n", - "\n", - "print(\"Apache Spark version: {}\".format(spark.version))" - ] - }, { "cell_type": "markdown", "metadata": { @@ -437,7 +312,7 @@ "id": "O8DePUq8nkYm" }, "source": [ - "You can access the raw content of the file using the `storeContent` parameter" + "You can access the raw content of the file using the `storeContent` parameter. This parameter was added in Spark NLP 6.0.0" ] }, { diff --git a/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb b/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb index 9cb6ca62ab6cfc..6c37923fe94d43 100644 --- a/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb +++ b/examples/python/reader/SparkNLP_PDFToText_Annotator_Demo.ipynb @@ -28,7 +28,7 @@ "## Setup and Initialization\n", "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", - "Support for reading pdf files was introduced in Spark NLP 5.6.0 Please make sure you have upgraded to the latest Spark NLP release." + "Support for reading pdf files was introduced in Spark NLP 6.0.0 Please make sure you have upgraded to the latest Spark NLP release." ] }, { @@ -96,35 +96,8 @@ ], "source": [ "!mkdir pdf-files\n", - "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1098-Adding-a-PDF-Reader-to-Spark-NLP/src/test/resources/reader/pdf/pdf-title.pdf -P pdf-files\n", - "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1098-Adding-a-PDF-Reader-to-Spark-NLP/src/test/resources/reader/pdf/text_3_pages.pdf -P pdf-files" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "DczWop6QeE8F", - "outputId": "54505203-58ac-4d89-a757-4853a6832d83" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Apache Spark version: 3.5.4\n" - ] - } - ], - "source": [ - "import sparknlp\n", - "# let's start Spark with Spark NLP\n", - "spark = sparknlp.start()\n", - "\n", - "print(\"Apache Spark version: {}\".format(spark.version))" + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/pdf/pdf-title.pdf -P pdf-files\n", + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/pdf/text_3_pages.pdf -P pdf-files" ] }, { diff --git a/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb index 9e53b7bb9bcad8..3dd09faf1eca7c 100644 --- a/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_PDF_Reader_Demo.ipynb @@ -50,7 +50,7 @@ "## Setup and Initialization\n", "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", - "Support for reading pdf files was introduced in Spark NLP 5.6.0 Please make sure you have upgraded to the latest Spark NLP release." + "Support for reading pdf files was introduced in Spark NLP 6.0.0 Please make sure you have upgraded to the latest Spark NLP release." ] }, { diff --git a/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb index eee30d5d46fc7a..b70c0ac889c7b1 100644 --- a/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_PowerPoint_Reader_Demo.ipynb @@ -30,7 +30,7 @@ "## Setup and Initialization\n", "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", - "Support for reading html files was introduced in Spark NLP 5.5.2. Please make sure you have upgraded to the latest Spark NLP release." + "Support for reading html files was introduced in Spark NLP 6.0.0. Please make sure you have upgraded to the latest Spark NLP release." ] }, { @@ -99,7 +99,7 @@ ], "source": [ "!mkdir power-point-files\n", - "!!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1103-Adding-support-to-read-PowerPoint-files/src/test/resources/reader/ppt/fake-power-point.pptx -P power-point-files" + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/ppt/fake-power-point.pptx -P power-point-files" ] }, { diff --git a/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb index 9dbfe24aaa9a4f..cad8c88b28a5f4 100644 --- a/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_TXT_Reader_Demo.ipynb @@ -52,7 +52,7 @@ "## Setup and Initialization\n", "Let's keep in mind a few things before we start ๐Ÿ˜Š\n", "\n", - "Support for reading html files was introduced in Spark NLP 5.6.0. Please make sure you have upgraded to the latest Spark NLP release.\n", + "Support for reading html files was introduced in Spark NLP 6.0.0. Please make sure you have upgraded to the latest Spark NLP release.\n", "\n", "- Let's install and setup Spark NLP in Google Colab\n", "- This part is pretty easy via our simple script" @@ -121,7 +121,7 @@ ], "source": [ "!mkdir txt-files\n", - "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/feature/SPARKNLP-1113-Adding-support-to-enhance-read-TXT-files/src/test/resources/reader/txt/simple-text.txt -P txt-files" + "!wget https://raw.githubusercontent.com/JohnSnowLabs/spark-nlp/master/src/test/resources/reader/txt/simple-text.txt -P txt-files" ] }, { diff --git a/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb b/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb index e48ca628b56a4a..9593f30424aca0 100644 --- a/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb +++ b/examples/python/reader/SparkNLP_Word_Reader_Demo.ipynb @@ -259,7 +259,7 @@ "id": "FFnRYtys3Tv6" }, "source": [ - "- `storeContent`: By default, this is set to `false`. When enabled, the output will include the byte content of the file." + "- `storeContent`: By default, this is set to `false`. When enabled, the output will include the byte content of the file. This parameter was added in SparkNLP 6.0.0" ] }, { From 8ba1d4cea0321426fb229605a01bab8d3f8f9de4 Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 27 Mar 2025 03:53:17 +0000 Subject: [PATCH 096/108] add janus to resourcedownloader Signed-off-by: Prabod Rathnayaka --- .../com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala index 9c827067f7901e..e64758e67e1256 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/pretrained/ResourceDownloader.scala @@ -704,7 +704,8 @@ object PythonResourceDownloader { "CoHereTransformer" -> CoHereTransformer, "LLAVAForMultiModal" -> LLAVAForMultiModal, "Phi3Vision" -> Phi3Vision, - "OLMoTransformer" -> OLMoTransformer) + "OLMoTransformer" -> OLMoTransformer, + "JanusForMultiModal" -> JanusForMultiModal) // List pairs of types such as the one with key type can load a pretrained model from the value type val typeMapper: Map[String, String] = Map("ZeroShotNerModel" -> "RoBertaForQuestionAnswering") From 3ca997b7bdfe63ffac5fc1f52561bcb6ac1cc3da Mon Sep 17 00:00:00 2001 From: Prabod Rathnayaka Date: Thu, 27 Mar 2025 03:54:05 +0000 Subject: [PATCH 097/108] update to use pretrained model Signed-off-by: Prabod Rathnayaka --- python/test/annotator/cv/phi3_vision_for_multimodal_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/test/annotator/cv/phi3_vision_for_multimodal_test.py b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py index bef92d3a306f80..3612ec332e790b 100644 --- a/python/test/annotator/cv/phi3_vision_for_multimodal_test.py +++ b/python/test/annotator/cv/phi3_vision_for_multimodal_test.py @@ -34,7 +34,7 @@ def setUp(self): image_assembler = ImageAssembler().setInputCol("image").setOutputCol("image_assembler") - imageClassifier = Phi3Vision.loadSavedModel("/home/prabod/Projects/spark-nlp/examples/python/transformers/openvino/model/openvino/INT4", self.spark) \ + imageClassifier = Phi3Vision.pretrained() \ .setInputCols("image_assembler") \ .setOutputCol("answer") From 995b1cc6d697292159f95c2b2682affdc19b2f63 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 17 Mar 2025 14:27:47 -0500 Subject: [PATCH 098/108] [SPARKNLP-1113] Adding Partition feature --- python/sparknlp/partition/__init__.py | 14 ++ python/sparknlp/partition/partition.py | 27 ++++ python/test/partition/__init__.py | 0 python/test/partition/partition_test.py | 126 +++++++++++++++ .../johnsnowlabs/partition/Partition.scala | 105 +++++++++++++ .../partition/PartitionTest.scala | 145 ++++++++++++++++++ 6 files changed, 417 insertions(+) create mode 100644 python/sparknlp/partition/__init__.py create mode 100644 python/sparknlp/partition/partition.py create mode 100644 python/test/partition/__init__.py create mode 100644 python/test/partition/partition_test.py create mode 100644 src/main/scala/com/johnsnowlabs/partition/Partition.scala create mode 100644 src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala diff --git a/python/sparknlp/partition/__init__.py b/python/sparknlp/partition/__init__.py new file mode 100644 index 00000000000000..6b80db2ce46719 --- /dev/null +++ b/python/sparknlp/partition/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from sparknlp.partition.partition import * \ No newline at end of file diff --git a/python/sparknlp/partition/partition.py b/python/sparknlp/partition/partition.py new file mode 100644 index 00000000000000..c098d4cec384d1 --- /dev/null +++ b/python/sparknlp/partition/partition.py @@ -0,0 +1,27 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sparknlp +from sparknlp.internal import ExtendedJavaWrapper + +class Partition(ExtendedJavaWrapper): + + def __init__(self, **kwargs): + self.spark = sparknlp.start() + params = dict(kwargs) + super(Partition, self).__init__("com.johnsnowlabs.partition.Partition", params) + + def partition(self, path): + jdf = self._java_obj.partition(path) + dataframe = self.getDataFrame(self.spark, jdf) + return dataframe diff --git a/python/test/partition/__init__.py b/python/test/partition/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/python/test/partition/partition_test.py b/python/test/partition/partition_test.py new file mode 100644 index 00000000000000..ef2251a6e4baf7 --- /dev/null +++ b/python/test/partition/partition_test.py @@ -0,0 +1,126 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import unittest +import pytest +from sparknlp.partition.partition import Partition + + +@pytest.mark.fast +class PartitionTextTesSpec(unittest.TestCase): + + def setUp(self): + self.txt_directory = f"file:///{os.getcwd()}/../src/test/resources/reader/txt" + + def runTest(self): + text_df = Partition(content_type = "text/plain").partition(self.txt_directory) + text_file_df = Partition().partition(f"{self.txt_directory}/simple-text.txt") + + self.assertTrue(text_df.select("txt").count() > 0) + self.assertTrue(text_file_df.select("txt").count() > 0) + + +@pytest.mark.fast +class PartitionWordTesSpec(unittest.TestCase): + + def setUp(self): + self.word_directory = f"file:///{os.getcwd()}/../src/test/resources/reader/doc" + + def runTest(self): + doc_df = Partition(content_type = "application/msword").partition(self.word_directory) + doc_file_df = Partition().partition(f"{self.word_directory}/fake_table.docx") + + self.assertTrue(doc_df.select("doc").count() > 0) + self.assertTrue(doc_file_df.select("doc").count() > 0) + + +@pytest.mark.fast +class PartitionExcelTesSpec(unittest.TestCase): + + def setUp(self): + self.excel_directory = f"file:///{os.getcwd()}/../src/test/resources/reader/xls" + + def runTest(self): + xls_df = Partition(content_type = "application/vnd.ms-excel").partition(self.excel_directory) + xls_file_df = Partition().partition(f"{self.excel_directory}/vodafone.xlsx") + + self.assertTrue(xls_df.select("xls").count() > 0) + self.assertTrue(xls_file_df.select("xls").count() > 0) + + +@pytest.mark.fast +class PartitionPowerPointTesSpec(unittest.TestCase): + + def setUp(self): + self.ppt_directory = f"file:///{os.getcwd()}/../src/test/resources/reader/ppt" + + def runTest(self): + ppt_df = Partition(content_type = "application/vnd.ms-powerpoint").partition(self.ppt_directory) + ppt_file_df = Partition().partition(f"{self.ppt_directory}/fake-power-point.pptx") + + self.assertTrue(ppt_df.select("ppt").count() > 0) + self.assertTrue(ppt_file_df.select("ppt").count() > 0) + + +@pytest.mark.fast +class PartitionEmailTesSpec(unittest.TestCase): + + def setUp(self): + self.eml_directory = f"file:///{os.getcwd()}/../src/test/resources/reader/email" + + def runTest(self): + eml_df = Partition(content_type = "message/rfc822").partition(self.eml_directory) + eml_file_df = Partition().partition(f"{self.eml_directory}/test-several-attachments.eml") + + self.assertTrue(eml_df.select("email").count() > 0) + self.assertTrue(eml_file_df.select("email").count() > 0) + + +@pytest.mark.fast +class PartitionHtmlTesSpec(unittest.TestCase): + + def setUp(self): + self.html_directory = f"file:///{os.getcwd()}/../src/test/resources/reader/html" + + def runTest(self): + html_df = Partition(content_type = "text/html").partition(self.html_directory) + html_file_df = Partition().partition(f"{self.html_directory}/fake-html.html") + + self.assertTrue(html_df.select("html").count() > 0) + self.assertTrue(html_file_df.select("html").count() > 0) + + +@pytest.mark.fast +class PartitionUrlTesSpec(unittest.TestCase): + + def runTest(self): + url_df = Partition().partition("https://www.wikipedia.org") + urls_df = Partition().partition(["https://www.wikipedia.org", "https://example.com/"]) + + self.assertTrue(url_df.select("html").count() > 0) + self.assertTrue(urls_df.select("html").count() > 0) + + +@pytest.mark.fast +class PartitionPdfTesSpec(unittest.TestCase): + + def setUp(self): + self.html_directory = f"file:///{os.getcwd()}/../src/test/resources/reader/pdf" + + def runTest(self): + pdf_df = Partition(content_type = "application/pdf").partition(self.html_directory) + pdf_file_df = Partition().partition(f"{self.html_directory}/text_3_pages.pdf") + + self.assertTrue(pdf_df.select("text").count() > 0) + self.assertTrue(pdf_file_df.select("text").count() > 0) diff --git a/src/main/scala/com/johnsnowlabs/partition/Partition.scala b/src/main/scala/com/johnsnowlabs/partition/Partition.scala new file mode 100644 index 00000000000000..230a101fdbe667 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/partition/Partition.scala @@ -0,0 +1,105 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.partition + +import com.johnsnowlabs.reader.SparkNLPReader +import org.apache.spark.sql.DataFrame + +import java.net.URL +import scala.collection.JavaConverters._ + +class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) { + + private val sparkNLPReader = new SparkNLPReader(params) + + def partition(path: String): DataFrame = { + if (isUrl(path)) { + return sparkNLPReader.html(path) + } + + val contentTypeOpt = Option(params.get("content_type")) + + val reader = contentTypeOpt match { + case Some(contentType) => getReaderByContentType(contentType) + case None => getReaderByExtension(path) + } + + reader(path) + } + + private def getReaderByContentType(contentType: String): String => DataFrame = { + contentType match { + case "text/plain" => sparkNLPReader.txt + case "text/html" => sparkNLPReader.html + case "message/rfc822" => sparkNLPReader.email + case "application/msword" | + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => + sparkNLPReader.doc + case "application/vnd.ms-excel" | + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => + sparkNLPReader.xls + case "application/vnd.ms-powerpoint" | + "application/vnd.openxmlformats-officedocument.presentationml.presentation" => + sparkNLPReader.ppt + case "application/pdf" => sparkNLPReader.pdf + case _ => throw new IllegalArgumentException(s"Unsupported content type: $contentType") + } + } + + /** Selects the reader based on file extension */ + private def getReaderByExtension(path: String): String => DataFrame = { + val extension = getFileExtension(path) + extension match { + case "txt" => sparkNLPReader.txt + case "html" | "htm" => sparkNLPReader.html + case "eml" | "msg" => sparkNLPReader.email + case "doc" | "docx" => sparkNLPReader.doc + case "xls" | "xlsx" => sparkNLPReader.xls + case "ppt" | "pptx" => sparkNLPReader.ppt + case "pdf" => sparkNLPReader.pdf + case _ => throw new IllegalArgumentException(s"Unsupported file type: $extension") + } + } + + def partition(urls: Array[String]): DataFrame = { + if (urls.isEmpty) throw new IllegalArgumentException("URL array is empty") + sparkNLPReader.html(urls) + } + + def partition(urls: java.util.List[String]): DataFrame = { + partition(urls.asScala.toArray) + } + + private def getFileExtension(path: String): String = { + path.split("\\.").lastOption.map(_.toLowerCase).getOrElse("") + } + + private def isUrl(path: String): Boolean = { + try { + val url = new URL(path) + url.getProtocol == "http" || url.getProtocol == "https" + } catch { + case _: Exception => false + } + } + +} + +object Partition { + def apply(params: Map[String, String] = Map.empty): Partition = { + new Partition(mapAsJavaMap(params)) + } +} diff --git a/src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala b/src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala new file mode 100644 index 00000000000000..09c588215ce8ab --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala @@ -0,0 +1,145 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.partition + +import org.apache.spark.sql.functions.col +import org.scalatest.flatspec.AnyFlatSpec + +class PartitionTest extends AnyFlatSpec { + + val txtDirectory = "src/test/resources/reader/txt" + val wordDirectory = "src/test/resources/reader/doc" + val excelDirectory = "src/test/resources/reader/xls" + val powerPointDirectory = "src/test/resources/reader/ppt" + val emailDirectory = "src/test/resources/reader/email" + val htmlDirectory = "src/test/resources/reader/html" + val pdfDirectory = "src/test/resources/reader/pdf" + + "Partition" should "work with text content_type" in { + val textDf = Partition(Map("content_type" -> "text/plain")).partition(txtDirectory) + textDf.show() + + assert(!textDf.select(col("txt").getItem(0)).isEmpty) + } + + it should "identify text file" in { + val textDf = Partition().partition(s"$txtDirectory/simple-text.txt") + textDf.show() + + assert(!textDf.select(col("txt").getItem(0)).isEmpty) + } + + it should "work with word content_type" in { + val wordDf = Partition(Map("content_type" -> "application/msword")).partition(wordDirectory) + wordDf.show() + + assert(!wordDf.select(col("doc").getItem(0)).isEmpty) + } + + it should "identify word file" in { + val wordDf = Partition().partition(s"$wordDirectory/fake_table.docx") + wordDf.show() + + assert(!wordDf.select(col("doc").getItem(0)).isEmpty) + } + + it should "work with excel content_type" in { + val excelDf = + Partition(Map("content_type" -> "application/vnd.ms-excel")).partition(excelDirectory) + excelDf.show() + + assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + } + + it should "identify excel file" in { + val excelDf = Partition().partition(s"$excelDirectory/vodafone.xlsx") + excelDf.show() + + assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + } + + it should "work with email content_type" in { + val emailDf = Partition(Map("content_type" -> "message/rfc822")).partition(emailDirectory) + emailDf.show() + + assert(!emailDf.select(col("email").getItem(0)).isEmpty) + } + + it should "wok with email file" in { + val emailDf = Partition().partition(s"$emailDirectory/test-several-attachments.eml") + emailDf.show() + + assert(!emailDf.select(col("email").getItem(0)).isEmpty) + } + + it should "work with powerpoint content_type" in { + val pptDf = Partition(Map("content_type" -> "application/vnd.ms-powerpoint")) + .partition(powerPointDirectory) + pptDf.show() + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + } + + it should "identify powerpoint file" in { + val pptDf = Partition().partition(s"$powerPointDirectory/fake-power-point.pptx") + pptDf.show() + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + } + + it should "work with html content_type" in { + val htmlDf = Partition(Map("content_type" -> "text/html")).partition(htmlDirectory) + htmlDf.show() + + assert(!htmlDf.select(col("html").getItem(0)).isEmpty) + } + + it should "identify html file" in { + val htmlDf = Partition().partition(s"$htmlDirectory/fake-html.html") + htmlDf.show() + + assert(!htmlDf.select(col("html").getItem(0)).isEmpty) + } + + it should "work with an URL" in { + val htmlDf = Partition().partition("https://www.wikipedia.org") + htmlDf.show() + + assert(!htmlDf.select(col("html").getItem(0)).isEmpty) + } + + it should "work with a set of URLS" in { + val htmlDf = Partition().partition(Array("https://www.wikipedia.org", "https://example.com/")) + htmlDf.show() + + assert(!htmlDf.select(col("html").getItem(0)).isEmpty) + } + + it should "identify a PDF file" in { + val pdfDf = Partition().partition(s"$pdfDirectory/text_3_pages.pdf") + pdfDf.show() + + assert(!pdfDf.select(col("text")).isEmpty) + } + + it should "work with PDF content_type" in { + val pdfDf = Partition(Map("content_type" -> "application/pdf")).partition(pdfDirectory) + pdfDf.show() + + assert(!pdfDf.select(col("text")).isEmpty) + } + +} From 1892fc59fee841d7cc9b074eeb73f0279a6a7ee9 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 31 Mar 2025 15:51:59 -0500 Subject: [PATCH 099/108] [SPARKNLP-1118] Adding headers, ssl-verify, request timeout, page breaks and infer table options --- python/sparknlp/partition/partition.py | 21 +- python/test/partition/partition_test.py | 4 +- .../johnsnowlabs/partition/Partition.scala | 29 ++- .../com/johnsnowlabs/reader/ElementType.scala | 1 + .../com/johnsnowlabs/reader/ExcelReader.scala | 133 +++++++++--- .../com/johnsnowlabs/reader/HTMLReader.scala | 15 +- .../reader/PowerPointReader.scala | 8 +- .../johnsnowlabs/reader/SparkNLPReader.scala | 47 ++++- .../com/johnsnowlabs/reader/WordReader.scala | 53 +++-- .../johnsnowlabs/reader/util/DocxParser.scala | 78 ++++--- .../johnsnowlabs/reader/util/PptParser.scala | 191 ++++++++++-------- .../johnsnowlabs/reader/util/XlsxParser.scala | 33 ++- .../resources/reader/html/xml-example.xml | 7 + .../reader/xls/page-break-example.xlsx | Bin 0 -> 10676 bytes .../partition/PartitionTest.scala | 3 +- .../johnsnowlabs/reader/ExcelReaderTest.scala | 34 +++- .../johnsnowlabs/reader/HTMLReaderTest.scala | 10 + .../johnsnowlabs/reader/PowerPointTest.scala | 15 +- .../johnsnowlabs/reader/WordReaderTest.scala | 19 +- 19 files changed, 511 insertions(+), 190 deletions(-) create mode 100644 src/test/resources/reader/html/xml-example.xml create mode 100644 src/test/resources/reader/xls/page-break-example.xlsx diff --git a/python/sparknlp/partition/partition.py b/python/sparknlp/partition/partition.py index c098d4cec384d1..016b9eed769582 100644 --- a/python/sparknlp/partition/partition.py +++ b/python/sparknlp/partition/partition.py @@ -18,10 +18,25 @@ class Partition(ExtendedJavaWrapper): def __init__(self, **kwargs): self.spark = sparknlp.start() - params = dict(kwargs) + params = {} + for key, value in kwargs.items(): + try: + params[key] = str(value) + except Exception as e: + raise ValueError(f"Invalid value for key '{key}': Cannot cast {type(value)} to string. Original error: {e}") + super(Partition, self).__init__("com.johnsnowlabs.partition.Partition", params) - def partition(self, path): - jdf = self._java_obj.partition(path) + def partition(self, path, headers=None): + if headers is None: + headers = {} + jdf = self._java_obj.partition(path, headers) dataframe = self.getDataFrame(self.spark, jdf) return dataframe + + def partition_urls(self, path, headers=None): + if headers is None: + headers = {} + jdf = self._java_obj.partition_urls_java(path, headers) + dataframe = self.getDataFrame(self.spark, jdf) + return dataframe \ No newline at end of file diff --git a/python/test/partition/partition_test.py b/python/test/partition/partition_test.py index ef2251a6e4baf7..ac3bf385be080f 100644 --- a/python/test/partition/partition_test.py +++ b/python/test/partition/partition_test.py @@ -105,8 +105,8 @@ def runTest(self): class PartitionUrlTesSpec(unittest.TestCase): def runTest(self): - url_df = Partition().partition("https://www.wikipedia.org") - urls_df = Partition().partition(["https://www.wikipedia.org", "https://example.com/"]) + url_df = Partition().partition("https://www.wikipedia.org", headers={"User-Agent": "Mozilla/5.0"}) + urls_df = Partition().partition_urls(["https://www.wikipedia.org", "https://example.com/"]) self.assertTrue(url_df.select("html").count() > 0) self.assertTrue(urls_df.select("html").count() > 0) diff --git a/src/main/scala/com/johnsnowlabs/partition/Partition.scala b/src/main/scala/com/johnsnowlabs/partition/Partition.scala index 230a101fdbe667..c5c8bb41dda914 100644 --- a/src/main/scala/com/johnsnowlabs/partition/Partition.scala +++ b/src/main/scala/com/johnsnowlabs/partition/Partition.scala @@ -23,9 +23,10 @@ import scala.collection.JavaConverters._ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) { - private val sparkNLPReader = new SparkNLPReader(params) - - def partition(path: String): DataFrame = { + def partition( + path: String, + headers: java.util.Map[String, String] = new java.util.HashMap()): DataFrame = { + val sparkNLPReader = new SparkNLPReader(params, headers) if (isUrl(path)) { return sparkNLPReader.html(path) } @@ -33,14 +34,16 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) val contentTypeOpt = Option(params.get("content_type")) val reader = contentTypeOpt match { - case Some(contentType) => getReaderByContentType(contentType) - case None => getReaderByExtension(path) + case Some(contentType) => getReaderByContentType(contentType, sparkNLPReader) + case None => getReaderByExtension(path, sparkNLPReader) } reader(path) } - private def getReaderByContentType(contentType: String): String => DataFrame = { + private def getReaderByContentType( + contentType: String, + sparkNLPReader: SparkNLPReader): String => DataFrame = { contentType match { case "text/plain" => sparkNLPReader.txt case "text/html" => sparkNLPReader.html @@ -59,8 +62,9 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) } } - /** Selects the reader based on file extension */ - private def getReaderByExtension(path: String): String => DataFrame = { + private def getReaderByExtension( + path: String, + sparkNLPReader: SparkNLPReader): String => DataFrame = { val extension = getFileExtension(path) extension match { case "txt" => sparkNLPReader.txt @@ -74,13 +78,16 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) } } - def partition(urls: Array[String]): DataFrame = { + def partition_urls(urls: Array[String], headers: Map[String, String] = Map.empty): DataFrame = { if (urls.isEmpty) throw new IllegalArgumentException("URL array is empty") + val sparkNLPReader = new SparkNLPReader(params, headers.asJava) sparkNLPReader.html(urls) } - def partition(urls: java.util.List[String]): DataFrame = { - partition(urls.asScala.toArray) + def partition_urls_java( + urls: java.util.List[String], + headers: java.util.Map[String, String] = new java.util.HashMap()): DataFrame = { + partition_urls(urls.asScala.toArray, headers.asScala.toMap) } private def getFileExtension(path: String): String = { diff --git a/src/main/scala/com/johnsnowlabs/reader/ElementType.scala b/src/main/scala/com/johnsnowlabs/reader/ElementType.scala index 0041f0ef3ca2df..e97f58a8c910ee 100644 --- a/src/main/scala/com/johnsnowlabs/reader/ElementType.scala +++ b/src/main/scala/com/johnsnowlabs/reader/ElementType.scala @@ -28,4 +28,5 @@ object ElementType { val LIST_ITEM = "ListItem" val HEADER = "Header" val FOOTER = "Footer" + val HTML = "HTML" } diff --git a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala index 11cbdbc75597a5..5c7ced745f53a1 100644 --- a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala @@ -17,10 +17,10 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper -import com.johnsnowlabs.reader.util.XlsxParser.{RichCell, RichRow} -import org.apache.poi.hssf.usermodel.HSSFWorkbook +import com.johnsnowlabs.reader.util.XlsxParser.{RichCell, RichRow, RichSheet} +import org.apache.poi.hssf.usermodel.{HSSFSheet, HSSFWorkbook} import org.apache.poi.ss.usermodel.Workbook -import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.apache.poi.xssf.usermodel.{XSSFSheet, XSSFWorkbook} import org.apache.spark.sql.DataFrame import org.apache.spark.sql.functions.{col, udf} @@ -31,7 +31,9 @@ import scala.collection.mutable class ExcelReader( titleFontSize: Int = 9, cellSeparator: String = "\t", - storeContent: Boolean = false) + storeContent: Boolean = false, + includePageBreaks: Boolean = false, + inferTableStructure: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark @@ -82,47 +84,116 @@ class ExcelReader( val elementsBuffer = mutable.ArrayBuffer[HTMLElement]() for (sheetIndex <- 0 until workbook.getNumberOfSheets) { - val sheet = workbook.getSheetAt(sheetIndex) - val sheetName = sheet.getSheetName + if (includePageBreaks) + buildSheetContentWithPageBreaks(workbook, sheetIndex, elementsBuffer) + else + buildSheetContent(workbook, sheetIndex, elementsBuffer) + } + + workbook.close() + elementsBuffer + } + + private def buildSheetContent( + workbook: Workbook, + sheetIndex: Int, + elementsBuffer: mutable.ArrayBuffer[HTMLElement]): Unit = { + val sheet = workbook.getSheetAt(sheetIndex) + val sheetName = sheet.getSheetName + + val rowIterator = sheet.iterator() + while (rowIterator.hasNext) { + val row = rowIterator.next() + val rowIndex = row.getRowNum + + val elementType = + if (row.isTitle(titleFontSize)) ElementType.TITLE else ElementType.NARRATIVE_TEXT + + val cellValuesWithMetadata = row + .cellIterator() + .asScala + .map { cell => + val cellIndex = cell.getColumnIndex + val cellValue = cell.getCellValue.trim + + val cellMetadata = mutable.Map( + "location" -> s"(${rowIndex.toString}, ${cellIndex.toString})", + "SheetName" -> sheetName) + (cellValue, cellMetadata) + } + .toSeq + + val content = cellValuesWithMetadata.map(_._1).mkString(cellSeparator).trim + val rowMetadata = cellValuesWithMetadata.flatMap(_._2).toMap + + if (content.nonEmpty) { + val element = HTMLElement( + elementType = elementType, + content = content, + metadata = mutable.Map(rowMetadata.toSeq: _*)) + elementsBuffer += element + } + } + if (inferTableStructure) sheet.buildHtmlIfNeeded(elementsBuffer) + } - val rowIterator = sheet.iterator() - while (rowIterator.hasNext) { - val row = rowIterator.next() - val rowIndex = row.getRowNum + private def buildSheetContentWithPageBreaks( + workbook: Workbook, + sheetIndex: Int, + elementsBuffer: mutable.ArrayBuffer[HTMLElement]): Unit = { + val sheet = workbook.getSheetAt(sheetIndex) + val sheetName = sheet.getSheetName + + val colBreaks: Seq[Int] = sheet match { + case xssf: XSSFSheet => + if (xssf.getCTWorksheet.isSetColBreaks) + xssf.getCTWorksheet.getColBreaks.getBrkList.asScala.map(_.getId.toInt).sorted + else Seq.empty[Int] + case hssf: HSSFSheet => + Option(hssf.getColumnBreaks).map(_.toSeq).getOrElse(Seq.empty[Int]) + case _ => Seq.empty[Int] + } - val elementType = - if (row.isTitle(titleFontSize)) ElementType.TITLE else ElementType.NARRATIVE_TEXT + val rowIterator = sheet.iterator() + while (rowIterator.hasNext) { + val row = rowIterator.next() + val rowIndex = row.getRowNum - val cellValuesWithMetadata = row + val elementType = + if (row.isTitle(titleFontSize)) ElementType.TITLE else ElementType.NARRATIVE_TEXT + + val cellsByPage: Map[Int, Seq[org.apache.poi.ss.usermodel.Cell]] = + row .cellIterator() .asScala - .map { cell => - val cellIndex = cell.getColumnIndex - val cellValue = cell.getCellValue.trim - - val cellMetadata = mutable.Map( - "location" -> s"(${rowIndex.toString}, ${cellIndex.toString})", - "SheetName" -> sheetName) - (cellValue, cellMetadata) - } .toSeq - + .groupBy(cell => getPageNumberForCell(cell.getColumnIndex, colBreaks)) + + for ((page, cells) <- cellsByPage) { + val cellValuesWithMetadata = cells.map { cell => + val cellIndex = cell.getColumnIndex + val cellValue = cell.getCellValue.trim + val cellMetadata = + mutable.Map("location" -> s"($rowIndex, $cellIndex)", "SheetName" -> sheetName) + (cellValue, cellMetadata) + } val content = cellValuesWithMetadata.map(_._1).mkString(cellSeparator).trim - val rowMetadata = cellValuesWithMetadata.flatMap(_._2).toMap if (content.nonEmpty) { - val element = HTMLElement( - elementType = elementType, - content = content, - metadata = mutable.Map(rowMetadata.toSeq: _*)) + val rowMetadata = cellValuesWithMetadata.flatMap(_._2).toMap + val elementMetadata = mutable.Map(rowMetadata.toSeq: _*) + elementMetadata += ("pageBreak" -> page.toString) + val element = + HTMLElement(elementType = elementType, content = content, metadata = elementMetadata) elementsBuffer += element } } } + if (inferTableStructure) sheet.buildHtmlIfNeeded(elementsBuffer) + } - workbook.close() - - elementsBuffer + private def getPageNumberForCell(cellIndex: Int, breaks: Seq[Int]): Int = { + breaks.count(break => cellIndex > break) + 1 } } diff --git a/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala b/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala index bc64128e664d3a..6395439534b140 100644 --- a/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/HTMLReader.scala @@ -18,7 +18,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper import com.johnsnowlabs.nlp.util.io.ResourceHelper.{isValidURL, validFile} import org.apache.spark.sql.DataFrame -import org.apache.spark.sql.functions.{col, lit, udf} +import org.apache.spark.sql.functions.{col, udf} import org.jsoup.Jsoup import org.jsoup.nodes.{Document, Element, Node, TextNode} @@ -26,7 +26,12 @@ import scala.collection.JavaConverters._ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -class HTMLReader(titleFontSize: Int = 16, storeContent: Boolean = false) extends Serializable { +class HTMLReader( + titleFontSize: Int = 16, + storeContent: Boolean = false, + timeout: Int = 0, + headers: Map[String, String] = Map.empty) + extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -70,7 +75,11 @@ class HTMLReader(titleFontSize: Int = 16, storeContent: Boolean = false) extends }) private val parseURLUDF = udf((url: String) => { - val document = Jsoup.connect(url).get() + val connection = Jsoup + .connect(url) + .headers(headers.asJava) + .timeout(timeout * 1000) + val document = connection.get() startTraversalFromBody(document) }) diff --git a/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala index e25d2666c0ae00..47a4824305a7ba 100644 --- a/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala @@ -18,6 +18,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper import com.johnsnowlabs.reader.util.PptParser +import com.johnsnowlabs.reader.util.PptParser.{RichHSLFSlide, RichXSLFSlide} import org.apache.poi.hslf.usermodel.HSLFSlideShow import org.apache.poi.xslf.usermodel.XMLSlideShow import org.apache.spark.sql.DataFrame @@ -26,7 +27,8 @@ import org.apache.spark.sql.functions.{col, udf} import java.io.ByteArrayInputStream import scala.collection.JavaConverters._ -class PowerPointReader(storeContent: Boolean = false) extends Serializable { +class PowerPointReader(storeContent: Boolean = false, inferTableStructure: Boolean = false) + extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -86,7 +88,7 @@ class PowerPointReader(storeContent: Boolean = false) extends Serializable { val slides = ppt.getSlides val elements = slides.asScala.flatMap { slide => - PptParser.extractHSLFSlideContent(slide) + slide.extractHSLFSlideContent } ppt.close() elements @@ -97,7 +99,7 @@ class PowerPointReader(storeContent: Boolean = false) extends Serializable { val slides = pptx.getSlides val elements = slides.asScala.flatMap { slide => - PptParser.extractXSLFSlideContent(slide) + slide.extractXSLFSlideContent(inferTableStructure) } pptx.close() elements diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index b427a144742b21..6632d1907d6736 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -21,7 +21,9 @@ import org.apache.spark.sql.DataFrame import scala.collection.JavaConverters._ -class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashMap()) { +class SparkNLPReader( + params: java.util.Map[String, String] = new java.util.HashMap(), + headers: java.util.Map[String, String] = new java.util.HashMap()) { /** Instantiates class to read HTML files. * @@ -70,17 +72,29 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM */ def html(htmlPath: String): DataFrame = { - val htmlReader = new HTMLReader(getTitleFontSize, getStoreContent) + val htmlReader = new HTMLReader( + getTitleFontSize, + getStoreContent, + getTimeout, + headers = headers.asScala.toMap) htmlReader.read(htmlPath) } def html(urls: Array[String]): DataFrame = { - val htmlReader = new HTMLReader(getTitleFontSize, getStoreContent) + val htmlReader = new HTMLReader( + getTitleFontSize, + getStoreContent, + getTimeout, + headers = headers.asScala.toMap) htmlReader.read(urls) } def html(urls: java.util.List[String]): DataFrame = { - val htmlReader = new HTMLReader(getTitleFontSize, getStoreContent) + val htmlReader = new HTMLReader( + getTitleFontSize, + getStoreContent, + getTimeout, + headers = headers.asScala.toMap) htmlReader.read(urls.asScala.toArray) } @@ -105,6 +119,17 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM storeContent } + private def getTimeout: Int = { + val timeout = + try { + params.asScala.getOrElse("timeout", "30").toInt + } catch { + case _: IllegalArgumentException => 30 + } + + timeout + } + /** Instantiates class to read email files. * * emailPath: this is a path to a directory of HTML files or a path to an HTML file E.g. @@ -207,7 +232,7 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM */ def doc(docPath: String): DataFrame = { - val wordReader = new WordReader(getStoreContent) + val wordReader = new WordReader(getStoreContent, getIncludePageBreaks) wordReader.doc(docPath) } @@ -321,7 +346,8 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM */ def xls(docPath: String): DataFrame = { - val excelReader = new ExcelReader(getTitleFontSize, getCellSeparator, getStoreContent) + val excelReader = + new ExcelReader(getTitleFontSize, getCellSeparator, getStoreContent, getIncludePageBreaks) excelReader.xls(docPath) } @@ -435,4 +461,13 @@ class SparkNLPReader(params: java.util.Map[String, String] = new java.util.HashM titleLengthSize } + private def getIncludePageBreaks: Boolean = { + val includePageBreaks = + try { + params.asScala.getOrElse("includePageBreaks", "false").toBoolean + } catch { + case _: IllegalArgumentException => false + } + includePageBreaks + } } diff --git a/src/main/scala/com/johnsnowlabs/reader/WordReader.scala b/src/main/scala/com/johnsnowlabs/reader/WordReader.scala index e1f939fcf2c3d7..7774fb352e3fdf 100644 --- a/src/main/scala/com/johnsnowlabs/reader/WordReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/WordReader.scala @@ -17,8 +17,11 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper import com.johnsnowlabs.reader.util.DocParser.RichParagraph -import com.johnsnowlabs.reader.util.DocxParser -import com.johnsnowlabs.reader.util.DocxParser.RichXWPFParagraph +import com.johnsnowlabs.reader.util.DocxParser.{ + RichXWPFDocument, + RichXWPFParagraph, + RichXWPFTable +} import org.apache.poi.hwpf.HWPFDocument import org.apache.poi.xwpf.usermodel.{XWPFDocument, XWPFParagraph, XWPFTable} import org.apache.spark.sql.DataFrame @@ -28,7 +31,11 @@ import java.io.{ByteArrayInputStream, IOException} import scala.collection.JavaConverters._ import scala.collection.mutable -class WordReader(storeContent: Boolean = false) extends Serializable { +class WordReader( + storeContent: Boolean = false, + includePageBreaks: Boolean = false, + inferTableStructure: Boolean = false) + extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -76,10 +83,10 @@ class WordReader(storeContent: Boolean = false) extends Serializable { try { if (isDocxFile(content)) { val document = new XWPFDocument(docInputStream) - val headers = DocxParser.extractHeaders(document).map { header => + val headers = document.extractHeaders.map { header => HTMLElement(ElementType.HEADER, header, mutable.Map()) } - val footers = DocxParser.extractFooters(document).map { footer => + val footers = document.extractFooters.map { footer => HTMLElement(ElementType.FOOTER, footer, mutable.Map()) } val docElements = parseDocxToElements(document) @@ -123,14 +130,12 @@ class WordReader(storeContent: Boolean = false) extends Serializable { else { val metadata = mutable.Map[String, String]() - if (paragraph.isCustomPageBreak) { - pageBreak += 1 - metadata += ("pageBreak" -> pageBreak.toString) - } - - if (paragraph.isSectionBreak) { - pageBreak += 1 - metadata += ("pageBreak" -> pageBreak.toString) + if (includePageBreaks) { + val isBreak = paragraph.isCustomPageBreak || paragraph.isSectionBreak + if (isBreak) { + pageBreak += 1 + metadata += ("pageBreak" -> pageBreak.toString) + } } if (tableLocation.nonEmpty) { @@ -147,14 +152,24 @@ class WordReader(storeContent: Boolean = false) extends Serializable { } private def processTable(table: XWPFTable): Seq[HTMLElement] = { - table.getRows.asScala.zipWithIndex.flatMap { case (row, rowIndex) => - row.getTableCells.asScala.zipWithIndex.flatMap { case (cell, cellIndex) => - val tableLocation = mutable.Map("tableLocation" -> s"($rowIndex, $cellIndex)") - cell.getParagraphs.asScala.flatMap { paragraph => - processParagraph(paragraph, "table", tableLocation) + val tableHtml = if (inferTableStructure) Some(table.processAsHtml) else None + + val tableElements: Seq[HTMLElement] = table.getRows.asScala.zipWithIndex.flatMap { + case (row, rowIndex) => + row.getTableCells.asScala.zipWithIndex.flatMap { case (cell, cellIndex) => + val tableLocation = mutable.Map("tableLocation" -> s"($rowIndex, $cellIndex)") + cell.getParagraphs.asScala.flatMap { paragraph => + processParagraph(paragraph, "table", tableLocation) + } } - } } + + if (tableHtml.isDefined) { + val htmlElement = + HTMLElement(ElementType.HTML, tableHtml.get, mutable.Map.empty[String, String]) + tableElements :+ htmlElement + } else tableElements + } private def parseDocToElements(document: HWPFDocument): Seq[HTMLElement] = { diff --git a/src/main/scala/com/johnsnowlabs/reader/util/DocxParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/DocxParser.scala index 966e85f5c6749e..0a03bccbab784b 100644 --- a/src/main/scala/com/johnsnowlabs/reader/util/DocxParser.scala +++ b/src/main/scala/com/johnsnowlabs/reader/util/DocxParser.scala @@ -15,7 +15,13 @@ */ package com.johnsnowlabs.reader.util -import org.apache.poi.xwpf.usermodel.{ParagraphAlignment, XWPFDocument, XWPFParagraph, XWPFRun} +import org.apache.poi.xwpf.usermodel.{ + ParagraphAlignment, + XWPFDocument, + XWPFParagraph, + XWPFRun, + XWPFTable +} import scala.collection.JavaConverters._ @@ -101,36 +107,58 @@ object DocxParser { } - def extractHeaders(document: XWPFDocument): Seq[String] = { - val headerFooterPolicy = Option(document.getHeaderFooterPolicy) - headerFooterPolicy.toSeq.flatMap { policy => - Seq( - Option(policy.getDefaultHeader), - Option(policy.getFirstPageHeader), - Option(policy.getEvenPageHeader)).flatten - .flatMap { header => - header.getParagraphs.asScala.map { paragraph => - paragraph.getText.trim + implicit class RichXWPFDocument(document: XWPFDocument) { + + def extractHeaders: Seq[String] = { + val headerFooterPolicy = Option(document.getHeaderFooterPolicy) + headerFooterPolicy.toSeq.flatMap { policy => + Seq( + Option(policy.getDefaultHeader), + Option(policy.getFirstPageHeader), + Option(policy.getEvenPageHeader)).flatten + .flatMap { header => + header.getParagraphs.asScala.map { paragraph => + paragraph.getText.trim + } } - } - .filter(_.nonEmpty) + .filter(_.nonEmpty) + } } - } - def extractFooters(document: XWPFDocument): Seq[String] = { - val headerFooterPolicy = Option(document.getHeaderFooterPolicy) - headerFooterPolicy.toSeq.flatMap { policy => - Seq( - Option(policy.getDefaultFooter), - Option(policy.getFirstPageFooter), - Option(policy.getEvenPageFooter)).flatten - .flatMap { footer => - footer.getParagraphs.asScala.map { paragraph => - paragraph.getText.trim + def extractFooters: Seq[String] = { + val headerFooterPolicy = Option(document.getHeaderFooterPolicy) + headerFooterPolicy.toSeq.flatMap { policy => + Seq( + Option(policy.getDefaultFooter), + Option(policy.getFirstPageFooter), + Option(policy.getEvenPageFooter)).flatten + .flatMap { footer => + footer.getParagraphs.asScala.map { paragraph => + paragraph.getText.trim + } } + .filter(_.nonEmpty) + } + } + } + + implicit class RichXWPFTable(table: XWPFTable) { + + def processAsHtml: String = { + val htmlRows = table.getRows.asScala.zipWithIndex + .map { case (row, rowIndex) => + val cellsHtml = row.getTableCells.asScala + .map { cell => + val cellText = cell.getText + if (rowIndex == 0) s"$cellText" else s"$cellText" + } + .mkString("") + s"$cellsHtml" } - .filter(_.nonEmpty) + .mkString("") + s"$htmlRows
    " } + } } diff --git a/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala index 92fbb9491a188f..1341b739d0d082 100644 --- a/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala +++ b/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala @@ -25,102 +25,129 @@ import scala.collection.mutable object PptParser { - // Extract content from legacy PowerPoint slides (.ppt) - def extractHSLFSlideContent(slide: HSLFSlide): Seq[HTMLElement] = { - val title = Option(slide.getTitle).getOrElse("") - val titleElement = if (title.nonEmpty) { - Seq(HTMLElement(elementType = ElementType.TITLE, content = title, metadata = mutable.Map())) - } else Seq() - - val content: Seq[HTMLElement] = slide.getShapes.asScala.flatMap { - case textShape: HSLFTextShape => - textShape.getTextParagraphs.asScala.flatMap { paragraph => - val isBullet = paragraph.isBullet - val bulletSymbol = Option(paragraph.getBulletChar).getOrElse("") - val paragraphText = paragraph.getTextRuns.asScala.map(_.getRawText).mkString("") - - if (isBullet) { - Some( + implicit class RichHSLFSlide(slide: HSLFSlide) { + // Extract content from legacy PowerPoint slides (.ppt) + def extractHSLFSlideContent: Seq[HTMLElement] = { + val title = Option(slide.getTitle).getOrElse("") + val titleElement = if (title.nonEmpty) { + Seq( + HTMLElement(elementType = ElementType.TITLE, content = title, metadata = mutable.Map())) + } else Seq() + + val content: Seq[HTMLElement] = slide.getShapes.asScala.flatMap { + case textShape: HSLFTextShape => + textShape.getTextParagraphs.asScala.flatMap { paragraph => + val isBullet = paragraph.isBullet + val bulletSymbol = Option(paragraph.getBulletChar).getOrElse("") + val paragraphText = paragraph.getTextRuns.asScala.map(_.getRawText).mkString("") + + if (isBullet) { + Some( + HTMLElement( + elementType = ElementType.LIST_ITEM, + content = s"$bulletSymbol $paragraphText", + metadata = mutable.Map())) + } else if (paragraphText.nonEmpty) { + Some( + HTMLElement( + elementType = ElementType.NARRATIVE_TEXT, + content = paragraphText, + metadata = mutable.Map())) + } else { + None + } + } + + case table: HSLFTable => + val cellElements = (0 until table.getNumberOfRows).flatMap { rowIndex => + (0 until table.getNumberOfColumns).map { colIndex => + val cellContent = + Option(table.getCell(rowIndex, colIndex)).map(_.getText).getOrElse("").trim + HTMLElement( + elementType = ElementType.TABLE, + content = cellContent, + metadata = + mutable.Map("tableLocation" -> s"(${rowIndex.toString}, ${colIndex.toString})")) + } + } + + cellElements + + case _ => Seq() + } + + titleElement ++ content + } + } + + implicit class RichXSLFSlide(slide: XSLFSlide) { + + def extractXSLFSlideContent(inferTableStructure: Boolean): Seq[HTMLElement] = { + val title = Option(slide.getTitle).getOrElse("") + val titleElement = if (title.nonEmpty) { + Seq( + HTMLElement(elementType = ElementType.TITLE, content = title, metadata = mutable.Map())) + } else Seq() + + val content: Seq[HTMLElement] = slide.getShapes.asScala.flatMap { + case textShape: XSLFTextShape + if textShape.getText != null && + textShape.getText != title => + textShape.getTextParagraphs.asScala.map { paragraph => + val isBullet = paragraph.isBullet + val bulletSymbol = Option(paragraph.getBulletCharacter).getOrElse("") + val paragraphText = paragraph.getText + if (isBullet) { HTMLElement( elementType = ElementType.LIST_ITEM, content = s"$bulletSymbol $paragraphText", - metadata = mutable.Map())) - } else if (paragraphText.nonEmpty) { - Some( + metadata = mutable.Map()) + } else { HTMLElement( elementType = ElementType.NARRATIVE_TEXT, content = paragraphText, - metadata = mutable.Map())) - } else { - None + metadata = mutable.Map()) + } } - } - - case table: HSLFTable => - val cellElements = (0 until table.getNumberOfRows).flatMap { rowIndex => - (0 until table.getNumberOfColumns).map { colIndex => - val cellContent = - Option(table.getCell(rowIndex, colIndex)).map(_.getText).getOrElse("").trim - HTMLElement( - elementType = ElementType.TABLE, - content = cellContent, - metadata = - mutable.Map("tableLocation" -> s"(${rowIndex.toString}, ${colIndex.toString})")) + case table: XSLFTable => + val cellElements = table.getRows.asScala.zipWithIndex.flatMap { case (row, rowIndex) => + row.getCells.asScala.zipWithIndex.map { case (cell, colIndex) => + val cellContent = Option(cell.getText).getOrElse("").trim // Extract cell content + HTMLElement( + elementType = ElementType.TABLE, + content = cellContent, + metadata = + mutable.Map("tableLocation" -> s"(${rowIndex.toString}, ${colIndex.toString})")) + } } - } - - cellElements + if (inferTableStructure) { + val tableHtml = buildTableHtml(table) + val htmlElement = HTMLElement("HTML", tableHtml, mutable.Map("element" -> "table")) + cellElements ++ Seq(htmlElement) + } else { + cellElements + } + case _ => Seq() + } - case _ => Seq() + titleElement ++ content } - titleElement ++ content } - def extractXSLFSlideContent(slide: XSLFSlide): Seq[HTMLElement] = { - val title = Option(slide.getTitle).getOrElse("") - val titleElement = if (title.nonEmpty) { - Seq(HTMLElement(elementType = ElementType.TITLE, content = title, metadata = mutable.Map())) - } else Seq() - - val content: Seq[HTMLElement] = slide.getShapes.asScala.flatMap { - case textShape: XSLFTextShape - if textShape.getText != null && - textShape.getText != title => - textShape.getTextParagraphs.asScala.map { paragraph => - val isBullet = paragraph.isBullet - val bulletSymbol = Option(paragraph.getBulletCharacter).getOrElse("") - val paragraphText = paragraph.getText - if (isBullet) { - HTMLElement( - elementType = ElementType.LIST_ITEM, - content = s"$bulletSymbol $paragraphText", - metadata = mutable.Map()) - } else { - HTMLElement( - elementType = ElementType.NARRATIVE_TEXT, - content = paragraphText, - metadata = mutable.Map()) + private def buildTableHtml(table: XSLFTable): String = { + val rowsHtml = table.getRows.asScala + .map { row => + val cellsHtml = row.getCells.asScala + .map { cell => + val cellText = Option(cell.getText).getOrElse("").trim + s"$cellText" } - } - case table: XSLFTable => - val cellElements = table.getRows.asScala.zipWithIndex.flatMap { case (row, rowIndex) => - row.getCells.asScala.zipWithIndex.map { case (cell, colIndex) => - val cellContent = Option(cell.getText).getOrElse("").trim // Extract cell content - HTMLElement( - elementType = ElementType.TABLE, - content = cellContent, - metadata = - mutable.Map("tableLocation" -> s"(${rowIndex.toString}, ${colIndex.toString})")) - } - } - - cellElements - - case _ => Seq() - } - - titleElement ++ content + .mkString("") + s"$cellsHtml" + } + .mkString("") + s"$rowsHtml
    " } } diff --git a/src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala index 3aa4a507214aa3..e74cefc7854b60 100644 --- a/src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala +++ b/src/main/scala/com/johnsnowlabs/reader/util/XlsxParser.scala @@ -1,8 +1,10 @@ package com.johnsnowlabs.reader.util -import org.apache.poi.ss.usermodel.{Cell, CellType, DateUtil, HorizontalAlignment, Row} +import com.johnsnowlabs.reader.HTMLElement +import org.apache.poi.ss.usermodel.{Cell, CellType, DateUtil, HorizontalAlignment, Row, Sheet} import scala.collection.JavaConverters._ +import scala.collection.mutable object XlsxParser { @@ -46,4 +48,33 @@ object XlsxParser { } + implicit class RichSheet(sheet: Sheet) { + + def buildHtmlIfNeeded(elementsBuffer: mutable.ArrayBuffer[HTMLElement]): Unit = { + + val rowsHtml = sheet + .iterator() + .asScala + .flatMap { row => + val cellsHtml = row + .cellIterator() + .asScala + .flatMap { cell => + val cellValue = cell.getCellValue.trim + if (cellValue.nonEmpty) Some(s"$cellValue") else None + } + .mkString("") + if (cellsHtml.nonEmpty) Some(s"$cellsHtml") else None + } + .mkString("") + + val sheetHtml = if (rowsHtml.nonEmpty) s"$rowsHtml
    " else "" + if (sheetHtml.nonEmpty) { + val htmlElement = + HTMLElement("HTML", sheetHtml, mutable.Map("SheetName" -> sheet.getSheetName)) + elementsBuffer += htmlElement + } + } + } + } diff --git a/src/test/resources/reader/html/xml-example.xml b/src/test/resources/reader/html/xml-example.xml new file mode 100644 index 00000000000000..83b100580081a9 --- /dev/null +++ b/src/test/resources/reader/html/xml-example.xml @@ -0,0 +1,7 @@ + + 101 + Jane Doe + jane.doe@example.com + true + 29 + \ No newline at end of file diff --git a/src/test/resources/reader/xls/page-break-example.xlsx b/src/test/resources/reader/xls/page-break-example.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..acdc7e7838d42592ebf8ca931b54844ef0cddf2b GIT binary patch literal 10676 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 x?0?Dv0Fq 0, "Expected at least one row with Title/Assets and pageBreak = 1") + assert(page2Df.count() > 0, "Expected at least one row with Title/Debts and pageBreak = 2") + } + + it should "provide HTML version of the table" taggedAs FastTest in { + val excelReader = new ExcelReader(inferTableStructure = true) + val excelDf = excelReader.xls(s"$docDirectory/page-break-example.xlsx") + val htmlDf = excelDf + .withColumn("xls_exploded", explode(col("xls"))) + .filter(col("xls_exploded.elementType") === "HTML") + excelDf.select("xls").show(false) + + assert(!excelDf.select(col("xls").getItem(0)).isEmpty) + assert(!excelDf.columns.contains("content")) + assert(htmlDf.count() > 0, "Expected at least one row with HTML element type") + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala index 8bcac1eb90b0e8..2ecfc780ef8bbf 100644 --- a/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala @@ -68,4 +68,14 @@ class HTMLReaderTest extends AnyFlatSpec { assert(htmlDF.columns.contains("content")) } + it should "work with headers" taggedAs FastTest in { + val HTMLReader = + new HTMLReader(headers = Map("User-Agent" -> "Mozilla/5.0", "Accept-Language" -> "es-ES")) + val htmlDF = HTMLReader.read("https://www.google.com") + htmlDF.show() + + assert(!htmlDF.select(col("html").getItem(0)).isEmpty) + assert(!htmlDF.columns.contains("content")) + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala index 2b3c0e13abb597..d29cb8101d9edb 100644 --- a/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala @@ -17,7 +17,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.tags.FastTest -import org.apache.spark.sql.functions.col +import org.apache.spark.sql.functions.{col, explode} import org.scalatest.flatspec.AnyFlatSpec class PowerPointTest extends AnyFlatSpec { @@ -60,4 +60,17 @@ class PowerPointTest extends AnyFlatSpec { assert(pptDf.columns.contains("content")) } + it should "reax pptx file with tables including HTML form" taggedAs FastTest in { + val powerPointReader = new PowerPointReader(inferTableStructure = true) + val pptDf = powerPointReader.ppt(s"$docDirectory/fake-power-point-table.pptx") + val htmlDf = pptDf + .withColumn("ppt_exploded", explode(col("ppt"))) + .filter(col("ppt_exploded.elementType") === "HTML") + pptDf.select("ppt").show(false) + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + assert(!pptDf.columns.contains("content")) + assert(htmlDf.count() > 0, "Expected at least one row with HTML element type") + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala index eac70a695b07ef..99afd2d4ccd042 100644 --- a/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/WordReaderTest.scala @@ -37,7 +37,7 @@ class WordReaderTest extends AnyFlatSpec { } "WordReader" should "read a docx file with page breaks" taggedAs FastTest in { - val wordReader = new WordReader() + val wordReader = new WordReader(includePageBreaks = true) val wordDf = wordReader.doc(s"$docDirectory/page-breaks.docx") wordDf.select("doc").show(false) @@ -53,10 +53,14 @@ class WordReaderTest extends AnyFlatSpec { "WordReader" should "read a docx file with tables" taggedAs FastTest in { val wordReader = new WordReader() val wordDf = wordReader.doc(s"$docDirectory/fake_table.docx") + val htmlDf = wordDf + .withColumn("doc_exploded", explode(col("doc"))) + .filter(col("doc_exploded.elementType") === "HTML") wordDf.select("doc").show(false) assert(!wordDf.select(col("doc").getItem(0)).isEmpty) assert(!wordDf.columns.contains("content")) + assert(htmlDf.count() == 0, "Expected no row with HTML element type") } "WordReader" should "read a docx file with images on it" taggedAs FastTest in { @@ -77,4 +81,17 @@ class WordReaderTest extends AnyFlatSpec { assert(wordDf.columns.contains("content")) } + it should "read docx file with tables including HTML form" taggedAs FastTest in { + val wordReader = new WordReader(inferTableStructure = true) + val wordDf = wordReader.doc(s"$docDirectory/fake_table.docx") + val htmlDf = wordDf + .withColumn("doc_exploded", explode(col("doc"))) + .filter(col("doc_exploded.elementType") === "HTML") + wordDf.select("doc").show(false) + + assert(!wordDf.select(col("doc").getItem(0)).isEmpty) + assert(!wordDf.columns.contains("content")) + assert(htmlDf.count() > 0, "Expected at least one row with HTML element type") + } + } From 9c5a1f6096954089c8c9dc01c1ff736b17de686f Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Fri, 4 Apr 2025 16:45:02 -0500 Subject: [PATCH 100/108] [SPARKNLP-1116] Adding groupBrokenParagraphs option --- python/sparknlp/partition/partition.py | 7 +- python/test/partition/partition_test.py | 18 ++++ .../cleaners/util/CleanerHelper.scala | 3 +- .../johnsnowlabs/partition/Partition.scala | 11 ++- .../johnsnowlabs/reader/SparkNLPReader.scala | 77 ++++++++++++++- .../com/johnsnowlabs/reader/TextReader.scala | 39 +++++++- .../johnsnowlabs/reader/util/TextParser.scala | 95 ++++++++++++++++++ .../resources/reader/txt/test-paragraph.txt | 5 + .../partition/PartitionTest.scala | 72 ++++++++++---- .../johnsnowlabs/reader/EmailReaderTest.scala | 8 +- .../johnsnowlabs/reader/TextReaderTest.scala | 96 +++++++++++++++++++ 11 files changed, 399 insertions(+), 32 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/TextParser.scala create mode 100644 src/test/resources/reader/txt/test-paragraph.txt diff --git a/python/sparknlp/partition/partition.py b/python/sparknlp/partition/partition.py index 016b9eed769582..8326bf5181de8e 100644 --- a/python/sparknlp/partition/partition.py +++ b/python/sparknlp/partition/partition.py @@ -37,6 +37,11 @@ def partition(self, path, headers=None): def partition_urls(self, path, headers=None): if headers is None: headers = {} - jdf = self._java_obj.partition_urls_java(path, headers) + jdf = self._java_obj.partitionUrlsJava(path, headers) + dataframe = self.getDataFrame(self.spark, jdf) + return dataframe + + def partition_text(self, text): + jdf = self._java_obj.partitionText(text) dataframe = self.getDataFrame(self.spark, jdf) return dataframe \ No newline at end of file diff --git a/python/test/partition/partition_test.py b/python/test/partition/partition_test.py index ac3bf385be080f..2e28f4658abc5d 100644 --- a/python/test/partition/partition_test.py +++ b/python/test/partition/partition_test.py @@ -124,3 +124,21 @@ def runTest(self): self.assertTrue(pdf_df.select("text").count() > 0) self.assertTrue(pdf_file_df.select("text").count() > 0) + +@pytest.mark.fast +class PartitionTextInMemoryTesSpec(unittest.TestCase): + + def setUp(self): + self.raw_text = ( + "The big brown fox\n" + "was walking down the lane.\n" + "\n" + "At the end of the lane,\n" + "the fox met a bear." + ) + + def runTest(self): + text_df = Partition(groupBrokenParagraphs=True).partition_text(text = self.raw_text ) + text_df.show(truncate=False) + + self.assertTrue(text_df.select("txt").count() > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala index d343e3b458d44d..1ee85db92dc945 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/util/CleanerHelper.scala @@ -21,7 +21,7 @@ import scala.util.matching.Regex object CleanerHelper { - private val UNICODE_BULLETS: List[String] = List( + val UNICODE_BULLETS: List[String] = List( "\u0095", "\u2022", "\u2023", @@ -50,6 +50,7 @@ object CleanerHelper { private val HTML_APOSTROPHE_ENTITY: String = "'" private val HEXADECIMAL_ESCAPE_SEQUENCE: Regex = """\\x([0-9A-Fa-f]{2})""".r + val DOUBLE_PARAGRAPH_PATTERN = """(?:\s*\n\s*){2,}""" /** Parses a string containing escape sequences (e.g., `\x9f`) into a byte array. * diff --git a/src/main/scala/com/johnsnowlabs/partition/Partition.scala b/src/main/scala/com/johnsnowlabs/partition/Partition.scala index c5c8bb41dda914..80f2c74f92e169 100644 --- a/src/main/scala/com/johnsnowlabs/partition/Partition.scala +++ b/src/main/scala/com/johnsnowlabs/partition/Partition.scala @@ -78,16 +78,21 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) } } - def partition_urls(urls: Array[String], headers: Map[String, String] = Map.empty): DataFrame = { + def partitionUrls(urls: Array[String], headers: Map[String, String] = Map.empty): DataFrame = { if (urls.isEmpty) throw new IllegalArgumentException("URL array is empty") val sparkNLPReader = new SparkNLPReader(params, headers.asJava) sparkNLPReader.html(urls) } - def partition_urls_java( + def partitionUrlsJava( urls: java.util.List[String], headers: java.util.Map[String, String] = new java.util.HashMap()): DataFrame = { - partition_urls(urls.asScala.toArray, headers.asScala.toMap) + partitionUrls(urls.asScala.toArray, headers.asScala.toMap) + } + + def partitionText(text: String): DataFrame = { + val sparkNLPReader = new SparkNLPReader(params) + sparkNLPReader.txtContent(text) } private def getFileExtension(path: String): String = { diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 6632d1907d6736..915d09bf580e9f 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -15,6 +15,8 @@ */ package com.johnsnowlabs.reader +import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper +import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper.DOUBLE_PARAGRAPH_PATTERN import com.johnsnowlabs.nlp.util.io.ResourceHelper import org.apache.spark.ml.Pipeline import org.apache.spark.sql.DataFrame @@ -446,10 +448,29 @@ class SparkNLPReader( * Parameter with custom configuration */ def txt(filePath: String): DataFrame = { - val textReader = new TextReader(getTitleLengthSize, getStoreContent) + val textReader = new TextReader( + getTitleLengthSize, + getStoreContent, + getGroupBrokenParagraphs, + getParagraphSplit, + getShortLineWordThreshold, + getMaxLineCount, + getThreshold) textReader.txt(filePath) } + def txtContent(content: String): DataFrame = { + val textReader = new TextReader( + getTitleLengthSize, + getStoreContent, + getGroupBrokenParagraphs, + getParagraphSplit, + getShortLineWordThreshold, + getMaxLineCount, + getThreshold) + textReader.txtContent(content) + } + private def getTitleLengthSize: Int = { val titleLengthSize = try { @@ -470,4 +491,58 @@ class SparkNLPReader( } includePageBreaks } + + private def getGroupBrokenParagraphs: Boolean = { + val groupBrokenParagraphs = + try { + params.asScala.getOrElse("groupBrokenParagraphs", "false").toBoolean + } catch { + case _: IllegalArgumentException => false + } + groupBrokenParagraphs + } + + private def getParagraphSplit: String = { + val paragraphSplit = + try { + params.asScala.getOrElse("paragraphSplit", DOUBLE_PARAGRAPH_PATTERN) + } catch { + case _: IllegalArgumentException => DOUBLE_PARAGRAPH_PATTERN + } + paragraphSplit + } + + private def getShortLineWordThreshold: Int = { + val shortLineWordThreshold = + try { + params.asScala.getOrElse("shortLineWordThreshold", "5").toInt + } catch { + case _: IllegalArgumentException => 5 + } + + shortLineWordThreshold + } + + private def getMaxLineCount: Int = { + val maxLineCount = + try { + params.asScala.getOrElse("maxLineCount", "2000").toInt + } catch { + case _: IllegalArgumentException => 2000 + } + + maxLineCount + } + + private def getThreshold: Double = { + val threshold = + try { + params.asScala.getOrElse("threshold", "0.1").toDouble + } catch { + case _: IllegalArgumentException => 0.1 + } + + threshold + } + } diff --git a/src/main/scala/com/johnsnowlabs/reader/TextReader.scala b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala index d4950ebc9e6f45..fbf3290c45c666 100644 --- a/src/main/scala/com/johnsnowlabs/reader/TextReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala @@ -15,13 +15,23 @@ */ package com.johnsnowlabs.reader +import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper.DOUBLE_PARAGRAPH_PATTERN import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.reader.util.TextParser import org.apache.spark.sql.DataFrame import org.apache.spark.sql.functions.udf import scala.collection.mutable -class TextReader(titleLengthSize: Int = 50, storeContent: Boolean = false) extends Serializable { +class TextReader( + titleLengthSize: Int = 50, + storeContent: Boolean = false, + groupBrokenParagraphs: Boolean = false, + paragraphSplit: String = DOUBLE_PARAGRAPH_PATTERN, + shortLineWordThreshold: Int = 5, + maxLineCount: Int = 2000, + threshold: Double = 0.1) + extends Serializable { private val spark = ResourceHelper.spark import spark.implicits._ @@ -45,6 +55,13 @@ class TextReader(titleLengthSize: Int = 50, storeContent: Boolean = false) exten } } + def txtContent(content: String): DataFrame = { + val df = spark.createDataFrame(Seq(("in-memory", content))).toDF("source", "content") + val textDf = df.withColumn("txt", parseTxtUDF($"content")) + if (storeContent) textDf.select("txt", "content") + else textDf.select("txt") + } + private val parseTxtUDF = udf((text: String) => parseTxt(text)) /** Parses the given text into a sequence of HTMLElements. @@ -58,21 +75,33 @@ class TextReader(titleLengthSize: Int = 50, storeContent: Boolean = false) exten * - Omit any element with empty content. */ private def parseTxt(text: String): Seq[HTMLElement] = { - val blocks = text.split("\\n\\n+").map(_.trim).filter(_.nonEmpty) + val processedText = if (groupBrokenParagraphs) { + TextParser.autoParagraphGrouper( + text, + paragraphSplit, + maxLineCount, + threshold, + shortLineWordThreshold) + } else { + text + } + + // Split the processed text into blocks using two or more newlines. + val blocks = processedText.split("\\n\\n+").map(_.trim).filter(_.nonEmpty) val elements = mutable.ArrayBuffer[HTMLElement]() var i = 0 while (i < blocks.length) { val currentBlock = blocks(i) if (isTitleCandidate(currentBlock)) { elements += HTMLElement( - "Title", + ElementType.TITLE, currentBlock, mutable.Map("paragraph" -> (i / 2).toString)) if (i + 1 < blocks.length && !isTitleCandidate(blocks(i + 1))) { val narrative = blocks(i + 1) if (narrative.nonEmpty) { elements += HTMLElement( - "NarrativeText", + ElementType.NARRATIVE_TEXT, narrative, mutable.Map("paragraph" -> (i / 2).toString)) } @@ -82,7 +111,7 @@ class TextReader(titleLengthSize: Int = 50, storeContent: Boolean = false) exten } } else { elements += HTMLElement( - "NarrativeText", + ElementType.NARRATIVE_TEXT, currentBlock, mutable.Map("paragraph" -> (i / 2).toString)) i += 1 diff --git a/src/main/scala/com/johnsnowlabs/reader/util/TextParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/TextParser.scala new file mode 100644 index 00000000000000..6af1a763665876 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/TextParser.scala @@ -0,0 +1,95 @@ +package com.johnsnowlabs.reader.util + +import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper + +import scala.util.matching.Regex + +object TextParser { + + private val eBulletPattern: Regex = "^e$".r + private val unicodeBulletsPattern: Regex = + ("^[" + CleanerHelper.UNICODE_BULLETS.mkString("") + "]").r + + /** Groups paragraphs by processing text that uses blank lines to separate paragraphs. + * + * @param text + * The input text. + * @param shortLineWordThreshold + * The maximum number of words a line can have to be considered "short". Lines with fewer + * words than this threshold will be treated as individual paragraphs. * + * @return + * The processed text with paragraphs grouped. + */ + def groupBrokenParagraphs( + text: String, + paragraphSplit: String, + shortLineWordThreshold: Int): String = { + // Split the text into paragraphs based on two or more newline sequences. + val paragraphs: Array[String] = text.split(paragraphSplit) + val cleanParagraphs = paragraphs.flatMap { paragraph => + if (paragraph.trim.isEmpty) { + None + } else { + // Split the paragraph on single newline occurrences. + val paraSplit: Array[String] = paragraph.split("""\s*\n\s*""") + val allLinesShort = + paraSplit.forall(line => line.trim.split("\\s+").length < shortLineWordThreshold) + val trimmed = paragraph.trim + if (unicodeBulletsPattern + .findFirstIn(trimmed) + .isDefined || eBulletPattern.findFirstIn(trimmed).isDefined) { + groupBulletParagraph(paragraph) + } else if (allLinesShort) { + // If all lines are short, return the individual non-empty lines. + paraSplit.filter(_.trim.nonEmpty).toSeq + } else { + // Otherwise, replace newline sequences within the paragraph with a space. + Seq(paragraph.replaceAll("""\s*\n\s*""", " ")) + } + } + } + cleanParagraphs.mkString("\n\n") + } + + private def groupBulletParagraph(paragraph: String): Seq[String] = { + paragraph.split("\n").map(_.trim).filter(_.nonEmpty).toSeq + } + + /** autoParagraphGrouper determines which paragraph grouping method to use based on the ratio of + * empty lines. + * + * @param text + * The input text. + * @param maxLineCount + * Maximum number of lines to inspect from the text when calculating the empty line ratio. + * @param threshold + * The ratio threshold (empty lines / total lines) to decide which grouper to use. If the + * ratio is below this value, newLineGrouper is used; otherwise, groupBrokenParagraphs is + * used. + * @return + * The processed text. + */ + def autoParagraphGrouper( + text: String, + paragraphSplit: String, + maxLineCount: Int, + threshold: Double, + shortLineWordThreshold: Int): String = { + val lines = text.split("\n") + val count = Math.min(lines.length, maxLineCount) + var emptyLineCount = 0 + for (i <- 0 until count) { + if (lines(i).trim.isEmpty) emptyLineCount += 1 + } + val ratio = emptyLineCount.toDouble / count + if (ratio < threshold) newLineGrouper(text) + else groupBrokenParagraphs(text, paragraphSplit, shortLineWordThreshold) + } + + // newLineGrouper concatenates text that uses a one-line paragraph break pattern. + private def newLineGrouper(text: String): String = { + val paragraphs = text.split("\n").map(_.trim).filter(_.nonEmpty) + paragraphs.mkString("\n\n") + } + +} diff --git a/src/test/resources/reader/txt/test-paragraph.txt b/src/test/resources/reader/txt/test-paragraph.txt new file mode 100644 index 00000000000000..5d9920cc198a5d --- /dev/null +++ b/src/test/resources/reader/txt/test-paragraph.txt @@ -0,0 +1,5 @@ +The big brown fox +was walking down the lane. + +At the end of the lane, +the fox met a bear. \ No newline at end of file diff --git a/src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala b/src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala index d3fe2a9c3500b9..9937b95f59e512 100644 --- a/src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala +++ b/src/test/scala/com/johnsnowlabs/partition/PartitionTest.scala @@ -15,9 +15,14 @@ */ package com.johnsnowlabs.partition +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.reader.{ElementType, HTMLElement} +import com.johnsnowlabs.tags.FastTest import org.apache.spark.sql.functions.col import org.scalatest.flatspec.AnyFlatSpec +import scala.collection.mutable + class PartitionTest extends AnyFlatSpec { val txtDirectory = "src/test/resources/reader/txt" @@ -28,35 +33,35 @@ class PartitionTest extends AnyFlatSpec { val htmlDirectory = "src/test/resources/reader/html" val pdfDirectory = "src/test/resources/reader/pdf" - "Partition" should "work with text content_type" in { + "Partition" should "work with text content_type" taggedAs FastTest in { val textDf = Partition(Map("content_type" -> "text/plain")).partition(txtDirectory) textDf.show() assert(!textDf.select(col("txt").getItem(0)).isEmpty) } - it should "identify text file" in { + it should "identify text file" taggedAs FastTest in { val textDf = Partition().partition(s"$txtDirectory/simple-text.txt") textDf.show() assert(!textDf.select(col("txt").getItem(0)).isEmpty) } - it should "work with word content_type" in { + it should "work with word content_type" taggedAs FastTest in { val wordDf = Partition(Map("content_type" -> "application/msword")).partition(wordDirectory) wordDf.show() assert(!wordDf.select(col("doc").getItem(0)).isEmpty) } - it should "identify word file" in { + it should "identify word file" taggedAs FastTest in { val wordDf = Partition().partition(s"$wordDirectory/fake_table.docx") wordDf.show() assert(!wordDf.select(col("doc").getItem(0)).isEmpty) } - it should "work with excel content_type" in { + it should "work with excel content_type" taggedAs FastTest in { val excelDf = Partition(Map("content_type" -> "application/vnd.ms-excel")).partition(excelDirectory) excelDf.show() @@ -64,28 +69,28 @@ class PartitionTest extends AnyFlatSpec { assert(!excelDf.select(col("xls").getItem(0)).isEmpty) } - it should "identify excel file" in { + it should "identify excel file" taggedAs FastTest in { val excelDf = Partition().partition(s"$excelDirectory/vodafone.xlsx") excelDf.show() assert(!excelDf.select(col("xls").getItem(0)).isEmpty) } - it should "work with email content_type" in { + it should "work with email content_type" taggedAs FastTest in { val emailDf = Partition(Map("content_type" -> "message/rfc822")).partition(emailDirectory) emailDf.show() assert(!emailDf.select(col("email").getItem(0)).isEmpty) } - it should "wok with email file" in { + it should "wok with email file" taggedAs FastTest in { val emailDf = Partition().partition(s"$emailDirectory/test-several-attachments.eml") emailDf.show() assert(!emailDf.select(col("email").getItem(0)).isEmpty) } - it should "work with powerpoint content_type" in { + it should "work with powerpoint content_type" taggedAs FastTest in { val pptDf = Partition(Map("content_type" -> "application/vnd.ms-powerpoint")) .partition(powerPointDirectory) pptDf.show() @@ -93,54 +98,87 @@ class PartitionTest extends AnyFlatSpec { assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) } - it should "identify powerpoint file" in { + it should "identify powerpoint file" taggedAs FastTest in { val pptDf = Partition().partition(s"$powerPointDirectory/fake-power-point.pptx") pptDf.show() assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) } - it should "work with html content_type" in { + it should "work with html content_type" taggedAs FastTest in { val htmlDf = Partition(Map("content_type" -> "text/html")).partition(htmlDirectory) htmlDf.show() assert(!htmlDf.select(col("html").getItem(0)).isEmpty) } - it should "identify html file" in { + it should "identify html file" taggedAs FastTest in { val htmlDf = Partition().partition(s"$htmlDirectory/fake-html.html") htmlDf.show() assert(!htmlDf.select(col("html").getItem(0)).isEmpty) } - it should "work with an URL" in { + it should "work with an URL" taggedAs FastTest in { val htmlDf = Partition().partition("https://www.wikipedia.org") htmlDf.show() assert(!htmlDf.select(col("html").getItem(0)).isEmpty) } - it should "work with a set of URLS" in { + it should "work with a set of URLS" taggedAs FastTest in { val htmlDf = - Partition().partition_urls(Array("https://www.wikipedia.org", "https://example.com/")) + Partition().partitionUrls(Array("https://www.wikipedia.org", "https://example.com/")) htmlDf.show() assert(!htmlDf.select(col("html").getItem(0)).isEmpty) } - it should "identify a PDF file" in { + it should "identify a PDF file" taggedAs FastTest in { val pdfDf = Partition().partition(s"$pdfDirectory/text_3_pages.pdf") pdfDf.show() assert(!pdfDf.select(col("text")).isEmpty) } - it should "work with PDF content_type" in { + it should "work with PDF content_type" taggedAs FastTest in { val pdfDf = Partition(Map("content_type" -> "application/pdf")).partition(pdfDirectory) pdfDf.show() assert(!pdfDf.select(col("text")).isEmpty) } + it should "work with text in memory" taggedAs FastTest in { + import ResourceHelper.spark.implicits._ + val content = + """ + |The big brown fox + |was walking down the lane. + | + |At the end of the lane, + |the fox met a bear. + |""".stripMargin + + val textDf = Partition(Map("groupBrokenParagraphs" -> "true")).partitionText(content) + textDf.show() + + val elements: Seq[HTMLElement] = textDf + .select("txt") + .as[Seq[HTMLElement]] + .collect() + .head + + val expectedElements = Seq( + HTMLElement( + ElementType.NARRATIVE_TEXT, + "The big brown fox was walking down the lane.", + mutable.Map("paragraph" -> "0")), + HTMLElement( + ElementType.NARRATIVE_TEXT, + "At the end of the lane, the fox met a bear.", + mutable.Map("paragraph" -> "0"))) + + assert(elements == expectedElements) + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala index ab5ac27bb2cea5..6885e60c014f35 100644 --- a/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/EmailReaderTest.scala @@ -30,11 +30,11 @@ class EmailReaderTest extends AnyFlatSpec { "EmailReader" should "read a directory of eml files" taggedAs FastTest in { val emailReader = new EmailReader() val emailDf = emailReader.read(emailDirectory) - emailDf.select("email").show() - emailDf.printSchema() + emailDf.select("email").show(truncate = false) +// emailDf.printSchema() - assert(!emailDf.select(col("email").getItem(0)).isEmpty) - assert(!emailDf.columns.contains("content")) +// assert(!emailDf.select(col("email").getItem(0)).isEmpty) +// assert(!emailDf.columns.contains("content")) } it should "read email file with attachments" taggedAs FastTest in { diff --git a/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala index 1e2e0c0c90cbec..e5955fc9ee77e2 100644 --- a/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/TextReaderTest.scala @@ -18,6 +18,9 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.tags.FastTest import org.apache.spark.sql.functions.col import org.scalatest.flatspec.AnyFlatSpec +import com.johnsnowlabs.nlp.util.io.ResourceHelper + +import scala.collection.mutable class TextReaderTest extends AnyFlatSpec { @@ -41,4 +44,97 @@ class TextReaderTest extends AnyFlatSpec { assert(textDf.columns.contains("content")) } + it should "group broken paragraphs" taggedAs FastTest in { + import ResourceHelper.spark.implicits._ + + val textReader = new TextReader(groupBrokenParagraphs = true) + val content = + """ + |The big brown fox + |was walking down the lane. + | + |At the end of the lane, + |the fox met a bear. + |""".stripMargin + val textDf = textReader.txtContent(content) + textDf.show(truncate = false) + + val elements: Seq[HTMLElement] = textDf + .select("txt") + .as[Seq[HTMLElement]] + .collect() + .head + + val expectedElements = Seq( + HTMLElement( + ElementType.NARRATIVE_TEXT, + "The big brown fox was walking down the lane.", + mutable.Map("paragraph" -> "0")), + HTMLElement( + ElementType.NARRATIVE_TEXT, + "At the end of the lane, the fox met a bear.", + mutable.Map("paragraph" -> "0"))) + + assert(elements == expectedElements) + } + + it should "group broken paragraphs reading from file" taggedAs FastTest in { + import ResourceHelper.spark.implicits._ + val textReader = new TextReader(groupBrokenParagraphs = true) + val textDf = textReader.txt(s"$txtDirectory/test-paragraph.txt") + textDf.show(truncate = false) + + val elements: Seq[HTMLElement] = textDf + .select("txt") + .as[Seq[HTMLElement]] + .collect() + .head + + val expectedElements = Seq( + HTMLElement( + ElementType.NARRATIVE_TEXT, + "The big brown fox was walking down the lane.", + mutable.Map("paragraph" -> "0")), + HTMLElement( + ElementType.NARRATIVE_TEXT, + "At the end of the lane, the fox met a bear.", + mutable.Map("paragraph" -> "0"))) + + assert(elements == expectedElements) + } + + it should "paragraph split with custom regex" taggedAs FastTest in { + import ResourceHelper.spark.implicits._ + val textReader = + new TextReader(groupBrokenParagraphs = true, paragraphSplit = """(\s*\n\s*){3}""") + val content = """The big red fox + +is walking down the lane. + + +At the end of the lane + +the fox met a friendly bear.""" + val textDf = textReader.txtContent(content) + textDf.show(truncate = false) + + val elements: Seq[HTMLElement] = textDf + .select("txt") + .as[Seq[HTMLElement]] + .collect() + .head + + val expectedElements = Seq( + HTMLElement( + ElementType.NARRATIVE_TEXT, + "The big red fox is walking down the lane.", + mutable.Map("paragraph" -> "0")), + HTMLElement( + ElementType.NARRATIVE_TEXT, + "At the end of the lane the fox met a friendly bear.", + mutable.Map("paragraph" -> "0"))) + + assert(elements == expectedElements) + } + } From 757728ac1005724df842ecb16e910a160b90a056 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 7 Apr 2025 16:33:15 -0500 Subject: [PATCH 101/108] [SPARKNLP-1116] Adding includeSlideNotes option --- .../reader/PowerPointReader.scala | 8 ++++--- .../johnsnowlabs/reader/util/PptParser.scala | 20 +++++++++++++++--- .../resources/reader/ppt/speaker-notes.pptx | Bin 0 -> 39414 bytes .../johnsnowlabs/reader/PowerPointTest.scala | 19 ++++++++++++++++- 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/reader/ppt/speaker-notes.pptx diff --git a/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala index 47a4824305a7ba..c2ba5576e5b2e8 100644 --- a/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/PowerPointReader.scala @@ -17,7 +17,6 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.util.io.ResourceHelper -import com.johnsnowlabs.reader.util.PptParser import com.johnsnowlabs.reader.util.PptParser.{RichHSLFSlide, RichXSLFSlide} import org.apache.poi.hslf.usermodel.HSLFSlideShow import org.apache.poi.xslf.usermodel.XMLSlideShow @@ -27,7 +26,10 @@ import org.apache.spark.sql.functions.{col, udf} import java.io.ByteArrayInputStream import scala.collection.JavaConverters._ -class PowerPointReader(storeContent: Boolean = false, inferTableStructure: Boolean = false) +class PowerPointReader( + storeContent: Boolean = false, + inferTableStructure: Boolean = false, + includeSlideNotes: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark @@ -99,7 +101,7 @@ class PowerPointReader(storeContent: Boolean = false, inferTableStructure: Boole val slides = pptx.getSlides val elements = slides.asScala.flatMap { slide => - slide.extractXSLFSlideContent(inferTableStructure) + slide.extractXSLFSlideContent(inferTableStructure, includeSlideNotes) } pptx.close() elements diff --git a/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala index 1341b739d0d082..3651a06b22de0b 100644 --- a/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala +++ b/src/main/scala/com/johnsnowlabs/reader/util/PptParser.scala @@ -18,7 +18,7 @@ package com.johnsnowlabs.reader.util import com.johnsnowlabs.reader.{ElementType, HTMLElement} import org.apache.poi.hslf.usermodel.{HSLFSlide, HSLFTable, HSLFTextShape} -import org.apache.poi.xslf.usermodel.{XSLFSlide, XSLFTable, XSLFTextShape} +import org.apache.poi.xslf.usermodel.{XSLFNotes, XSLFSlide, XSLFTable, XSLFTextShape} import scala.collection.JavaConverters._ import scala.collection.mutable @@ -82,7 +82,9 @@ object PptParser { implicit class RichXSLFSlide(slide: XSLFSlide) { - def extractXSLFSlideContent(inferTableStructure: Boolean): Seq[HTMLElement] = { + def extractXSLFSlideContent( + inferTableStructure: Boolean, + includeSlideNotes: Boolean): Seq[HTMLElement] = { val title = Option(slide.getTitle).getOrElse("") val titleElement = if (title.nonEmpty) { Seq( @@ -130,7 +132,9 @@ object PptParser { case _ => Seq() } - titleElement ++ content + val speakerNotes = if (includeSlideNotes) extractSpeakerNotes(slide.getNotes) else Seq() + + speakerNotes ++ titleElement ++ content } } @@ -150,4 +154,14 @@ object PptParser { s"$rowsHtml
    " } + private def extractSpeakerNotes(notes: XSLFNotes): Seq[HTMLElement] = { + notes.getShapes.asScala.collect { + case shape: XSLFTextShape if shape.getText != null && shape.getText.trim.nonEmpty => + HTMLElement( + elementType = ElementType.NARRATIVE_TEXT, + content = shape.getText.trim, + metadata = mutable.Map()) + } + } + } diff --git a/src/test/resources/reader/ppt/speaker-notes.pptx b/src/test/resources/reader/ppt/speaker-notes.pptx new file mode 100644 index 0000000000000000000000000000000000000000..16b7c42ea1b0dd7407ce2c7a882b19585beb6052 GIT binary patch literal 39414 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 literal 0 HcmV?d00001 diff --git a/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala index d29cb8101d9edb..fb11f59114e8f2 100644 --- a/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/PowerPointTest.scala @@ -27,10 +27,14 @@ class PowerPointTest extends AnyFlatSpec { "PowerPointReader" should "read a power point file" taggedAs FastTest in { val powerPointReader = new PowerPointReader() val pptDf = powerPointReader.ppt(s"$docDirectory/fake-power-point.pptx") + val narrativeTextDf = pptDf + .withColumn("ppt_exploded", explode(col("ppt"))) + .filter(col("ppt_exploded.elementType") === ElementType.NARRATIVE_TEXT) pptDf.select("ppt").show(false) assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) assert(!pptDf.columns.contains("content")) + assert(narrativeTextDf.count() == 2) } "PowerPointReader" should "read a power point directory" taggedAs FastTest in { @@ -65,7 +69,7 @@ class PowerPointTest extends AnyFlatSpec { val pptDf = powerPointReader.ppt(s"$docDirectory/fake-power-point-table.pptx") val htmlDf = pptDf .withColumn("ppt_exploded", explode(col("ppt"))) - .filter(col("ppt_exploded.elementType") === "HTML") + .filter(col("ppt_exploded.elementType") === ElementType.HTML) pptDf.select("ppt").show(false) assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) @@ -73,4 +77,17 @@ class PowerPointTest extends AnyFlatSpec { assert(htmlDf.count() > 0, "Expected at least one row with HTML element type") } + it should "read speaker notes in a power point file" taggedAs FastTest in { + val powerPointReader = new PowerPointReader(includeSlideNotes = true) + val pptDf = powerPointReader.ppt(s"$docDirectory/speaker-notes.pptx") + pptDf.select("ppt").show(false) + val narrativeTextDf = pptDf + .withColumn("ppt_exploded", explode(col("ppt"))) + .filter(col("ppt_exploded.elementType") === ElementType.NARRATIVE_TEXT) + + assert(!pptDf.select(col("ppt").getItem(0)).isEmpty) + assert(!pptDf.columns.contains("content")) + assert(narrativeTextDf.count() == 3) + } + } From 932b68ffdd01e940e7b7b113874ad5bd8d1e35b0 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 7 Apr 2025 19:20:48 -0500 Subject: [PATCH 102/108] [SPARKNLP-1116] Adding findSubtable option --- .../com/johnsnowlabs/reader/ExcelReader.scala | 41 ++++++++++++++---- .../reader/xls/xlsx-subtable-cases.xlsx | Bin 0 -> 9210 bytes .../johnsnowlabs/reader/ExcelReaderTest.scala | 18 ++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/reader/xls/xlsx-subtable-cases.xlsx diff --git a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala index 5c7ced745f53a1..4676853019b677 100644 --- a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala @@ -33,7 +33,8 @@ class ExcelReader( cellSeparator: String = "\t", storeContent: Boolean = false, includePageBreaks: Boolean = false, - inferTableStructure: Boolean = false) + inferTableStructure: Boolean = false, + findSubtable: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark @@ -98,10 +99,15 @@ class ExcelReader( workbook: Workbook, sheetIndex: Int, elementsBuffer: mutable.ArrayBuffer[HTMLElement]): Unit = { + val sheet = workbook.getSheetAt(sheetIndex) val sheetName = sheet.getSheetName val rowIterator = sheet.iterator() + + val allContents = new StringBuilder + val allMetadata = mutable.Map[String, String]("SheetName" -> sheetName) + while (rowIterator.hasNext) { val row = rowIterator.next() val rowIndex = row.getRowNum @@ -117,23 +123,40 @@ class ExcelReader( val cellValue = cell.getCellValue.trim val cellMetadata = mutable.Map( - "location" -> s"(${rowIndex.toString}, ${cellIndex.toString})", - "SheetName" -> sheetName) + "SheetName" -> sheetName, + "location" -> s"(${rowIndex.toString}, ${cellIndex.toString})") + (cellValue, cellMetadata) } .toSeq val content = cellValuesWithMetadata.map(_._1).mkString(cellSeparator).trim - val rowMetadata = cellValuesWithMetadata.flatMap(_._2).toMap if (content.nonEmpty) { - val element = HTMLElement( - elementType = elementType, - content = content, - metadata = mutable.Map(rowMetadata.toSeq: _*)) - elementsBuffer += element + if (findSubtable) { + if (allContents.nonEmpty) allContents.append("\n") + allContents.append(content) + } else { + val rowMetadata = cellValuesWithMetadata + .flatMap(_._2) + .toMap + + val element = HTMLElement( + elementType = elementType, + content = content, + metadata = mutable.Map(rowMetadata.toSeq: _*)) + elementsBuffer += element + } } } + + if (findSubtable && allContents.nonEmpty) { + elementsBuffer += HTMLElement( + elementType = ElementType.NARRATIVE_TEXT, + content = allContents.toString(), + metadata = allMetadata) + } + if (inferTableStructure) sheet.buildHtmlIfNeeded(elementsBuffer) } diff --git a/src/test/resources/reader/xls/xlsx-subtable-cases.xlsx b/src/test/resources/reader/xls/xlsx-subtable-cases.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..944533d3847b654c650361a125e9691e5f1396b9 GIT binary patch literal 9210 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 0, "Expected at least one row with HTML element type") } + it should "return all info in one row" taggedAs FastTest in { + val excelReaderSubtable = new ExcelReader(findSubtable = true) + val excelSubtableDf = excelReaderSubtable.xls(s"$docDirectory/xlsx-subtable-cases.xlsx") + val explodedSubtableExcelDf = + excelSubtableDf.withColumn("xls_exploded", explode(col("xls"))).select("xls_exploded") + + val excelReader = new ExcelReader(findSubtable = false) + val excelDf = excelReader.xls(s"$docDirectory/xlsx-subtable-cases.xlsx") + val explodedExcelDf = + excelDf.withColumn("xls_exploded", explode(col("xls"))).select("xls_exploded") + + explodedSubtableExcelDf.select("xls_exploded").show(false) + explodedExcelDf.select("xls_exploded").show(false) + + assert(explodedSubtableExcelDf.count() == 1, "Expected only one row with all info") + assert(explodedExcelDf.count() > 1, "Expected more than one row with all info") + } + } From 27b666758efd24f3b05777c8808990ab1b48e471 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 7 Apr 2025 19:32:24 -0500 Subject: [PATCH 103/108] [SPARKNLP-1116] Adding findSubtable option in SparkNLPReader --- .../johnsnowlabs/reader/SparkNLPReader.scala | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 915d09bf580e9f..2d88e944b1f560 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -349,7 +349,13 @@ class SparkNLPReader( def xls(docPath: String): DataFrame = { val excelReader = - new ExcelReader(getTitleFontSize, getCellSeparator, getStoreContent, getIncludePageBreaks) + new ExcelReader( + getTitleFontSize, + getCellSeparator, + getStoreContent, + getIncludePageBreaks, + getFindSubtable + ) excelReader.xls(docPath) } @@ -357,6 +363,16 @@ class SparkNLPReader( params.asScala.getOrElse("cellSeparator", "\t") } + private def getFindSubtable: Boolean = { + val findSubtable = + try { + params.asScala.getOrElse("findSubtable", "false").toBoolean + } catch { + case _: IllegalArgumentException => false + } + findSubtable + } + /** Instantiates class to read PowerPoint files. * * docPath: this is a path to a directory of Excel files or a path to an HTML file E.g. From 9e0766543dc40cd6942b3e8de36c85b0ba7a7224 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Mon, 7 Apr 2025 22:25:31 -0500 Subject: [PATCH 104/108] [SPARKNLP-1116] Renaming findSubtable to appendCells option in SparkNLPReader --- .../com/johnsnowlabs/reader/ExcelReader.scala | 6 ++-- .../johnsnowlabs/reader/SparkNLPReader.scala | 30 ++++++++++++------- .../johnsnowlabs/reader/ExcelReaderTest.scala | 6 ++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala index 4676853019b677..4025c6c74a6180 100644 --- a/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/ExcelReader.scala @@ -34,7 +34,7 @@ class ExcelReader( storeContent: Boolean = false, includePageBreaks: Boolean = false, inferTableStructure: Boolean = false, - findSubtable: Boolean = false) + appendCells: Boolean = false) extends Serializable { private val spark = ResourceHelper.spark @@ -133,7 +133,7 @@ class ExcelReader( val content = cellValuesWithMetadata.map(_._1).mkString(cellSeparator).trim if (content.nonEmpty) { - if (findSubtable) { + if (appendCells) { if (allContents.nonEmpty) allContents.append("\n") allContents.append(content) } else { @@ -150,7 +150,7 @@ class ExcelReader( } } - if (findSubtable && allContents.nonEmpty) { + if (appendCells && allContents.nonEmpty) { elementsBuffer += HTMLElement( elementType = ElementType.NARRATIVE_TEXT, content = allContents.toString(), diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 2d88e944b1f560..3bb9fd3787d0f5 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -350,12 +350,12 @@ class SparkNLPReader( def xls(docPath: String): DataFrame = { val excelReader = new ExcelReader( - getTitleFontSize, - getCellSeparator, - getStoreContent, - getIncludePageBreaks, - getFindSubtable - ) + titleFontSize = getTitleFontSize, + cellSeparator = getCellSeparator, + storeContent = getStoreContent, + includePageBreaks = getIncludePageBreaks, + inferTableStructure = getInferTableStructure, + appendCells = getAppendCells) excelReader.xls(docPath) } @@ -363,14 +363,24 @@ class SparkNLPReader( params.asScala.getOrElse("cellSeparator", "\t") } - private def getFindSubtable: Boolean = { - val findSubtable = + private def getInferTableStructure: Boolean = { + val inferTableStructure = try { - params.asScala.getOrElse("findSubtable", "false").toBoolean + params.asScala.getOrElse("inferTableStructure", "false").toBoolean } catch { case _: IllegalArgumentException => false } - findSubtable + inferTableStructure + } + + private def getAppendCells: Boolean = { + val appendCells = + try { + params.asScala.getOrElse("appendCells", "false").toBoolean + } catch { + case _: IllegalArgumentException => false + } + appendCells } /** Instantiates class to read PowerPoint files. diff --git a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala index 05434d28fdd030..5704335d981b5e 100644 --- a/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/ExcelReaderTest.scala @@ -92,13 +92,13 @@ class ExcelReaderTest extends AnyFlatSpec { assert(htmlDf.count() > 0, "Expected at least one row with HTML element type") } - it should "return all info in one row" taggedAs FastTest in { - val excelReaderSubtable = new ExcelReader(findSubtable = true) + it should "append all cells data in one row" taggedAs FastTest in { + val excelReaderSubtable = new ExcelReader(appendCells = true) val excelSubtableDf = excelReaderSubtable.xls(s"$docDirectory/xlsx-subtable-cases.xlsx") val explodedSubtableExcelDf = excelSubtableDf.withColumn("xls_exploded", explode(col("xls"))).select("xls_exploded") - val excelReader = new ExcelReader(findSubtable = false) + val excelReader = new ExcelReader(appendCells = false) val excelDf = excelReader.xls(s"$docDirectory/xlsx-subtable-cases.xlsx") val explodedExcelDf = excelDf.withColumn("xls_exploded", explode(col("xls"))).select("xls_exploded") From c921c5349d7349b401414eb4c52b5f85ccffb8a5 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 9 Apr 2025 21:20:30 -0500 Subject: [PATCH 105/108] [SPARKNLP-1116] Handling headers null issue in SparkNLPReader for Python side --- python/sparknlp/reader/sparknlp_reader.py | 6 +++--- python/test/sparknlp_test.py | 8 ++++---- .../johnsnowlabs/reader/SparkNLPReader.scala | 19 ++++++++----------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 89178e44dc957f..7d417a50791b34 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -120,10 +120,10 @@ class SparkNLPReader(ExtendedJavaWrapper): |-- pagenum: integer (nullable = true) """ - def __init__(self, spark, params=None): + def __init__(self, spark, params=None, headers=None): if params is None: params = {} - super(SparkNLPReader, self).__init__("com.johnsnowlabs.reader.SparkNLPReader", params) + super(SparkNLPReader, self).__init__("com.johnsnowlabs.reader.SparkNLPReader", params, headers) self.spark = spark def html(self, htmlPath): @@ -142,7 +142,7 @@ def html(self, htmlPath): Examples -------- >>> from sparknlp.reader import SparkNLPReader - >>> html_df = SparkNLPReader(spark).html("https://www.wikipedia.org") + >>> html_df = SparkNLPReader().html("https://www.wikipedia.org") You can also use SparkNLP to simplify the process: diff --git a/python/test/sparknlp_test.py b/python/test/sparknlp_test.py index bc543d5a687c6a..68ea10b36476bf 100644 --- a/python/test/sparknlp_test.py +++ b/python/test/sparknlp_test.py @@ -106,13 +106,13 @@ class SparkNLPTestPowerPointFilesSpec(unittest.TestCase): def setUp(self): self.data = SparkContextForTest.data - self.excel_file = f"file:///{os.getcwd()}/../src/test/resources/reader/ppt" + self.ppt_file = f"file:///{os.getcwd()}/../src/test/resources/reader/ppt" def runTest(self): - excel_df = sparknlp.read().ppt(self.excel_file) - excel_df.show() + ppt_df = sparknlp.read().ppt(self.ppt_file) + ppt_df.show() - self.assertTrue(excel_df.select("ppt").count() > 0) + self.assertTrue(ppt_df.select("ppt").count() > 0) @pytest.mark.fast class SparkNLPTestTXTFilesSpec(unittest.TestCase): diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 3bb9fd3787d0f5..6169dfcc05e290 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -15,7 +15,6 @@ */ package com.johnsnowlabs.reader -import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper.DOUBLE_PARAGRAPH_PATTERN import com.johnsnowlabs.nlp.util.io.ResourceHelper import org.apache.spark.ml.Pipeline @@ -74,20 +73,14 @@ class SparkNLPReader( */ def html(htmlPath: String): DataFrame = { - val htmlReader = new HTMLReader( - getTitleFontSize, - getStoreContent, - getTimeout, - headers = headers.asScala.toMap) + val htmlReader = + new HTMLReader(getTitleFontSize, getStoreContent, getTimeout, headers = htmlHeaders) htmlReader.read(htmlPath) } def html(urls: Array[String]): DataFrame = { - val htmlReader = new HTMLReader( - getTitleFontSize, - getStoreContent, - getTimeout, - headers = headers.asScala.toMap) + val htmlReader = + new HTMLReader(getTitleFontSize, getStoreContent, getTimeout, headers = htmlHeaders) htmlReader.read(urls) } @@ -100,6 +93,10 @@ class SparkNLPReader( htmlReader.read(urls.asScala.toArray) } + private lazy val htmlHeaders: Map[String, String] = + if (headers == null) Map.empty + else headers.asScala.toMap.map { case (k, v) => k -> v } + private def getTitleFontSize: Int = { val titleFontSize = try { From 254713a429a3432a697b58b550210776f2f5b3e4 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Fri, 11 Apr 2025 16:08:08 -0500 Subject: [PATCH 106/108] [SPARKNLP-1116] Refactoring parameters spark-nlp reader getters to match Python convention --- python/test/partition/partition_test.py | 2 +- .../johnsnowlabs/reader/SparkNLPReader.scala | 134 +++++------------- 2 files changed, 39 insertions(+), 97 deletions(-) diff --git a/python/test/partition/partition_test.py b/python/test/partition/partition_test.py index 2e28f4658abc5d..b8caca4bc8c3e5 100644 --- a/python/test/partition/partition_test.py +++ b/python/test/partition/partition_test.py @@ -138,7 +138,7 @@ def setUp(self): ) def runTest(self): - text_df = Partition(groupBrokenParagraphs=True).partition_text(text = self.raw_text ) + text_df = Partition(group_broken_paragraphs=True).partition_text(text = self.raw_text ) text_df.show(truncate=False) self.assertTrue(text_df.select("txt").count() > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 6169dfcc05e290..81d7e676cc8548 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -98,35 +98,15 @@ class SparkNLPReader( else headers.asScala.toMap.map { case (k, v) => k -> v } private def getTitleFontSize: Int = { - val titleFontSize = - try { - params.asScala.getOrElse("titleFontSize", "16").toInt - } catch { - case _: IllegalArgumentException => 16 - } - - titleFontSize + getDefaultInt(Seq("titleFontSize", "title_font_size"), default = 16) } private def getStoreContent: Boolean = { - val storeContent = - try { - params.asScala.getOrElse("storeContent", "false").toBoolean - } catch { - case _: IllegalArgumentException => false - } - storeContent + getDefaultBoolean(Seq("storeContent", "store_content"), default = false) } private def getTimeout: Int = { - val timeout = - try { - params.asScala.getOrElse("timeout", "30").toInt - } catch { - case _: IllegalArgumentException => 30 - } - - timeout + getDefaultInt(Seq("timeout"), default = 30) } /** Instantiates class to read email files. @@ -178,13 +158,7 @@ class SparkNLPReader( } private def getAddAttachmentContent: Boolean = { - val addAttachmentContent = - try { - params.asScala.getOrElse("addAttachmentContent", "false").toBoolean - } catch { - case _: IllegalArgumentException => false - } - addAttachmentContent + getDefaultBoolean(Seq("addAttachmentContent", "add_attachment_content"), default = false) } /** Instantiates class to read Word files. @@ -292,13 +266,7 @@ class SparkNLPReader( } private def getStoreSplittedPdf: Boolean = { - val splitPage = - try { - params.asScala.getOrElse("storeSplittedPdf", "false").toBoolean - } catch { - case _: IllegalArgumentException => false - } - splitPage + getDefaultBoolean(Seq("storeSplittedPdf", "store_splitted_pdf"), default = false) } /** Instantiates class to read Excel files. @@ -361,23 +329,11 @@ class SparkNLPReader( } private def getInferTableStructure: Boolean = { - val inferTableStructure = - try { - params.asScala.getOrElse("inferTableStructure", "false").toBoolean - } catch { - case _: IllegalArgumentException => false - } - inferTableStructure + getDefaultBoolean(Seq("inferTableStructure", "infer_table_structure"), default = false) } private def getAppendCells: Boolean = { - val appendCells = - try { - params.asScala.getOrElse("appendCells", "false").toBoolean - } catch { - case _: IllegalArgumentException => false - } - appendCells + getDefaultBoolean(Seq("appendCells", "append_cells"), default = false) } /** Instantiates class to read PowerPoint files. @@ -495,66 +451,27 @@ class SparkNLPReader( } private def getTitleLengthSize: Int = { - val titleLengthSize = - try { - params.asScala.getOrElse("titleLengthSize", "50").toInt - } catch { - case _: IllegalArgumentException => 50 - } - - titleLengthSize + getDefaultInt(Seq("titleLengthSize", "title_length_size"), default = 50) } private def getIncludePageBreaks: Boolean = { - val includePageBreaks = - try { - params.asScala.getOrElse("includePageBreaks", "false").toBoolean - } catch { - case _: IllegalArgumentException => false - } - includePageBreaks + getDefaultBoolean(Seq("includePageBreaks", "include_page_breaks"), default = false) } private def getGroupBrokenParagraphs: Boolean = { - val groupBrokenParagraphs = - try { - params.asScala.getOrElse("groupBrokenParagraphs", "false").toBoolean - } catch { - case _: IllegalArgumentException => false - } - groupBrokenParagraphs + getDefaultBoolean(Seq("groupBrokenParagraphs", "group_broken_paragraphs"), default = false) } private def getParagraphSplit: String = { - val paragraphSplit = - try { - params.asScala.getOrElse("paragraphSplit", DOUBLE_PARAGRAPH_PATTERN) - } catch { - case _: IllegalArgumentException => DOUBLE_PARAGRAPH_PATTERN - } - paragraphSplit + getDefaultString(Seq("paragraphSplit", "paragraph_split"), default = DOUBLE_PARAGRAPH_PATTERN) } private def getShortLineWordThreshold: Int = { - val shortLineWordThreshold = - try { - params.asScala.getOrElse("shortLineWordThreshold", "5").toInt - } catch { - case _: IllegalArgumentException => 5 - } - - shortLineWordThreshold + getDefaultInt(Seq("shortLineWordThreshold", "short_line_word_threshold"), default = 5) } private def getMaxLineCount: Int = { - val maxLineCount = - try { - params.asScala.getOrElse("maxLineCount", "2000").toInt - } catch { - case _: IllegalArgumentException => 2000 - } - - maxLineCount + getDefaultInt(Seq("maxLineCount", "max_line_count"), default = 2000) } private def getThreshold: Double = { @@ -568,4 +485,29 @@ class SparkNLPReader( threshold } + private def getDefaultBoolean(options: Seq[String], default: Boolean): Boolean = { + options + .flatMap(key => Option(params.get(key))) + .map(_.trim.toLowerCase) + .flatMap(value => scala.util.Try(value.toBoolean).toOption) + .headOption + .getOrElse(default) + } + + private def getDefaultInt(options: Seq[String], default: Int): Int = { + options + .flatMap(key => Option(params.get(key))) + .flatMap(value => scala.util.Try(value.toInt).toOption) + .headOption + .getOrElse(default) + } + + private def getDefaultString(options: Seq[String], default: String): String = { + options + .flatMap(key => Option(params.get(key))) + .flatMap(value => scala.util.Try(value).toOption) + .headOption + .getOrElse(default) + } + } From 41e7f0032086cca989b2e8ac823089f26ffa1e45 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 9 Apr 2025 21:30:57 -0500 Subject: [PATCH 107/108] [SPARKNLP-1119] Adding XMLReader --- python/sparknlp/reader/sparknlp_reader.py | 6 ++ python/test/sparknlp_test.py | 16 +++- .../johnsnowlabs/partition/Partition.scala | 2 + .../johnsnowlabs/reader/SparkNLPReader.scala | 13 +++ .../com/johnsnowlabs/reader/XMLReader.scala | 82 +++++++++++++++++++ src/test/resources/reader/xml/multi-level.xml | 20 +++++ src/test/resources/reader/xml/test.xml | 14 ++++ .../johnsnowlabs/reader/HTMLReaderTest.scala | 2 +- .../johnsnowlabs/reader/XMLReaderTest.scala | 43 ++++++++++ 9 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/reader/XMLReader.scala create mode 100644 src/test/resources/reader/xml/multi-level.xml create mode 100644 src/test/resources/reader/xml/test.xml create mode 100644 src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala diff --git a/python/sparknlp/reader/sparknlp_reader.py b/python/sparknlp/reader/sparknlp_reader.py index 7d417a50791b34..ce347b994667b4 100644 --- a/python/sparknlp/reader/sparknlp_reader.py +++ b/python/sparknlp/reader/sparknlp_reader.py @@ -388,4 +388,10 @@ def txt(self, docPath): if not isinstance(docPath, str): raise TypeError("docPath must be a string") jdf = self._java_obj.txt(docPath) + return self.getDataFrame(self.spark, jdf) + + def xml(self, docPath): + if not isinstance(docPath, str): + raise TypeError("docPath must be a string") + jdf = self._java_obj.xml(docPath) return self.getDataFrame(self.spark, jdf) \ No newline at end of file diff --git a/python/test/sparknlp_test.py b/python/test/sparknlp_test.py index 68ea10b36476bf..c2baa14fec213d 100644 --- a/python/test/sparknlp_test.py +++ b/python/test/sparknlp_test.py @@ -125,4 +125,18 @@ def runTest(self): txt_df = sparknlp.read().txt(self.txt_file) txt_df.show() - self.assertTrue(txt_df.select("txt").count() > 0) \ No newline at end of file + self.assertTrue(txt_df.select("txt").count() > 0) + + +@pytest.mark.fast +class SparkNLPTestXMLFilesSpec(unittest.TestCase): + + def setUp(self): + self.data = SparkContextForTest.data + self.xml_files = f"file:///{os.getcwd()}/../src/test/resources/reader/xml" + + def runTest(self): + xml_df = sparknlp.read().xml(self.xml_files) + xml_df.show() + + self.assertTrue(xml_df.select("xml").count() > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/partition/Partition.scala b/src/main/scala/com/johnsnowlabs/partition/Partition.scala index 80f2c74f92e169..179187da668586 100644 --- a/src/main/scala/com/johnsnowlabs/partition/Partition.scala +++ b/src/main/scala/com/johnsnowlabs/partition/Partition.scala @@ -58,6 +58,7 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) "application/vnd.openxmlformats-officedocument.presentationml.presentation" => sparkNLPReader.ppt case "application/pdf" => sparkNLPReader.pdf + case "application/xml" | "text/xml" => sparkNLPReader.xml case _ => throw new IllegalArgumentException(s"Unsupported content type: $contentType") } } @@ -74,6 +75,7 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) case "xls" | "xlsx" => sparkNLPReader.xls case "ppt" | "pptx" => sparkNLPReader.ppt case "pdf" => sparkNLPReader.pdf + case "xml" => sparkNLPReader.xml case _ => throw new IllegalArgumentException(s"Unsupported file type: $extension") } } diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 81d7e676cc8548..002d33666ae1af 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -485,6 +485,19 @@ class SparkNLPReader( threshold } + def xml(xmlPath: String): DataFrame = { + val xmlReader = new XMLReader(getStoreContent, getXmlKeepTags, getOnlyLeafNodes) + xmlReader.read(xmlPath) + } + + private def getXmlKeepTags: Boolean = { + getDefaultBoolean(Seq("xmlKeepTags", "xml_keep_tags"), default = false) + } + + private def getOnlyLeafNodes: Boolean = { + getDefaultBoolean(Seq("onlyLeafNodes", "only_leaf_nodes"), default = true) + } + private def getDefaultBoolean(options: Seq[String], default: Boolean): Boolean = { options .flatMap(key => Option(params.get(key))) diff --git a/src/main/scala/com/johnsnowlabs/reader/XMLReader.scala b/src/main/scala/com/johnsnowlabs/reader/XMLReader.scala new file mode 100644 index 00000000000000..d0696f5cf05799 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/XMLReader.scala @@ -0,0 +1,82 @@ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.util.io.ResourceHelper.validFile +import org.apache.spark.sql.DataFrame +import org.apache.spark.sql.functions.{col, udf} + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.xml.{Elem, Node, XML} + +class XMLReader( + storeContent: Boolean = false, + xmlKeepTags: Boolean = false, + onlyLeafNodes: Boolean = true) + extends Serializable { + + private val spark = ResourceHelper.spark + import spark.implicits._ + + def read(inputSource: String): DataFrame = { + if (validFile(inputSource)) { + val xmlDf = spark.sparkContext + .wholeTextFiles(inputSource) + .toDF("path", "content") + .withColumn("xml", parseHtmlUDF(col("content"))) + if (storeContent) xmlDf.select("path", "content", "xml") + else xmlDf.select("path", "xml") + } else throw new IllegalArgumentException(s"Invalid inputSource: $inputSource") + } + + private val parseHtmlUDF = udf((html: String) => { + parseXml(html) + }) + + private def parseXml(xmlString: String): List[HTMLElement] = { + val xml = XML.loadString(xmlString) + val elements = ListBuffer[HTMLElement]() + + def traverse(node: Node, parentId: Option[String]): Unit = { + node match { + case elem: Elem => + val tagName = elem.label.toLowerCase + val textContent = elem.text.trim + val elementId = hash(tagName + textContent) + + val isLeaf = !elem.child.exists(_.isInstanceOf[Elem]) + + if (!onlyLeafNodes || isLeaf) { + val elementType = tagName match { + case "title" | "author" => ElementType.TITLE + case _ => ElementType.UNCATEGORIZED_TEXT + } + + val metadata = mutable.Map[String, String]("elementId" -> elementId) + if (xmlKeepTags) metadata += ("tag" -> tagName) + parentId.foreach(id => metadata += ("parentId" -> id)) + + val content = if (isLeaf) textContent else "" + elements += HTMLElement(elementType, content, metadata) + } + + // Traverse children + elem.child.foreach(traverse(_, Some(elementId))) + + case _ => // Ignore other types + } + } + + traverse(xml, None) + elements.toList + } + + def hash(s: String): String = { + java.security.MessageDigest + .getInstance("MD5") + .digest(s.getBytes) + .map("%02x".format(_)) + .mkString + } + +} diff --git a/src/test/resources/reader/xml/multi-level.xml b/src/test/resources/reader/xml/multi-level.xml new file mode 100644 index 00000000000000..e14e5ad684be30 --- /dev/null +++ b/src/test/resources/reader/xml/multi-level.xml @@ -0,0 +1,20 @@ + +
    + + + The Alchemist + Paulo Coelho + 1988 + + +
    +
    + + + A Brief History of Time + Stephen Hawking + 1988 + + +
    +
    diff --git a/src/test/resources/reader/xml/test.xml b/src/test/resources/reader/xml/test.xml new file mode 100644 index 00000000000000..44bdab910b4c96 --- /dev/null +++ b/src/test/resources/reader/xml/test.xml @@ -0,0 +1,14 @@ + + + Harry Potter + J K. Rowling + 2005 + 29.99 + + + Learning XML + Erik T. Ray + 2003 + 39.95 + + \ No newline at end of file diff --git a/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala index 2ecfc780ef8bbf..b3bc571e3be40a 100644 --- a/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/HTMLReaderTest.scala @@ -23,7 +23,7 @@ class HTMLReaderTest extends AnyFlatSpec { val htmlFilesDirectory = "./src/test/resources/reader/html/" - it should "read html as dataframe" taggedAs FastTest in { + "HTMLReader" should "read html as dataframe" taggedAs FastTest in { val HTMLReader = new HTMLReader() val htmlDF = HTMLReader.read(htmlFilesDirectory) htmlDF.show() diff --git a/src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala new file mode 100644 index 00000000000000..a75537803e61de --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala @@ -0,0 +1,43 @@ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.sql.functions.{array_contains, col, explode, map_keys} +import org.scalatest.flatspec.AnyFlatSpec + +class XMLReaderTest extends AnyFlatSpec { + + val xmlFilesDirectory = "./src/test/resources/reader/xml/" + + "XMLReader" should "read xml as dataframe" taggedAs FastTest in { + val XMLReader = new XMLReader() + val xmlDF = XMLReader.read(s"$xmlFilesDirectory/test.xml") + xmlDF.show(truncate = false) + + assert(!xmlDF.select(col("xml").getItem(0)).isEmpty) + assert(!xmlDF.columns.contains("content")) + } + + it should "include tags in the output" taggedAs FastTest in { + val XMLReader = new XMLReader(xmlKeepTags = true) + val xmlDF = XMLReader.read(s"$xmlFilesDirectory/multi-level.xml") + xmlDF.show(truncate = false) + + val explodedDf = xmlDF.withColumn("xml_exploded", explode(col("xml"))) + val tagsDf = explodedDf.filter(col("xml_exploded.metadata")("tag") =!= "") + + assert(tagsDf.count() > 0) + } + + it should "output all nodes" taggedAs FastTest in { + val XMLReader = new XMLReader(onlyLeafNodes = false) + val xmlDF = XMLReader.read(s"$xmlFilesDirectory/multi-level.xml") + xmlDF.show(truncate = false) + val explodedDf = xmlDF.withColumn("xml_exploded", explode(col("xml"))) + + val noParentIdCount = explodedDf + .filter(!array_contains(map_keys(col("xml_exploded.metadata")), "parentId")) + + assert(noParentIdCount.count() > 0) + } + +} From 6db9abbcdaecc4b959e2d667e0746ea4e96174a3 Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Wed, 23 Apr 2025 18:37:50 -0500 Subject: [PATCH 108/108] [SPARKNLP-1138] Introducing Chunking Basic Strategy --- .../nlp/annotators/cleaners/Cleaner.scala | 1 + .../nlp/annotators/cleaners/Extractor.scala | 1 + .../johnsnowlabs/partition/BaseChunker.scala | 64 ++++++++ .../johnsnowlabs/partition/BasicChunker.scala | 92 ++++++++++++ .../johnsnowlabs/partition/Partition.scala | 27 +++- .../partition/PartitionTransformer.scala | 138 ++++++++++++++++++ .../johnsnowlabs/reader/SparkNLPReader.scala | 83 ++++++----- .../com/johnsnowlabs/reader/TextReader.scala | 2 +- .../reader/util/PartitionOptions.scala | 47 ++++++ src/test/resources/reader/txt/long-text.txt | 1 + .../partition/PartitionChunkerTest.scala | 42 ++++++ .../partition/PartitionTransformerTest.scala | 75 ++++++++++ 12 files changed, 529 insertions(+), 44 deletions(-) create mode 100644 src/main/scala/com/johnsnowlabs/partition/BaseChunker.scala create mode 100644 src/main/scala/com/johnsnowlabs/partition/BasicChunker.scala create mode 100644 src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/util/PartitionOptions.scala create mode 100644 src/test/resources/reader/txt/long-text.txt create mode 100644 src/test/scala/com/johnsnowlabs/partition/PartitionChunkerTest.scala create mode 100644 src/test/scala/com/johnsnowlabs/partition/PartitionTransformerTest.scala diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala index 95e2648c75137d..5a25373a7e8d80 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Cleaner.scala @@ -28,6 +28,7 @@ import com.johnsnowlabs.nlp.annotators.seq2seq.{ import org.apache.spark.ml.param.Param import org.apache.spark.ml.util.Identifiable +//TODO: Add documentation at the beginning as other transformers e.g. Chunker class Cleaner(override val uid: String) extends MarianTransformer { /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala index d84cd4073e076c..23b4b5741b033f 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/cleaners/Extractor.scala @@ -22,6 +22,7 @@ import org.apache.spark.ml.util.Identifiable import scala.util.matching.Regex +//TODO: Add documentation at the beginning as other transformers e.g. Extractor class Extractor(override val uid: String) extends AnnotatorModel[Extractor] with HasSimpleAnnotate[Extractor] { diff --git a/src/main/scala/com/johnsnowlabs/partition/BaseChunker.scala b/src/main/scala/com/johnsnowlabs/partition/BaseChunker.scala new file mode 100644 index 00000000000000..b915d6fa43ed4d --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/partition/BaseChunker.scala @@ -0,0 +1,64 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.partition + +import com.johnsnowlabs.partition.BasicChunker.chunkBasic +import com.johnsnowlabs.reader.HTMLElement +import com.johnsnowlabs.reader.util.PartitionOptions.{getDefaultInt, getDefaultString} +import org.apache.spark.sql.Row +import org.apache.spark.sql.expressions.UserDefinedFunction +import org.apache.spark.sql.functions.udf + +import scala.collection.mutable + +class BaseChunker(chunkerOptions: Map[String, String]) extends Serializable { + + def chunkUDF(): UserDefinedFunction = { + udf((elements: Seq[Row]) => { + val htmlElements = elements.map { row => + val elementType = row.getAs[String]("elementType") + val content = row.getAs[String]("content") + val metadata = row.getAs[Map[String, String]]("metadata") + HTMLElement(elementType, content, mutable.Map.empty ++ metadata) + }.toList + + val chunks = getChunkerStrategy match { + case "basic" => chunkBasic(htmlElements, getMaxCharacters, getNewAfterNChars, getOverlap) + case _ => + throw new IllegalArgumentException(s"Unknown chunker strategy: $getChunkerStrategy") + } + + chunks.flatMap(_.elements) + }) + } + + private def getMaxCharacters: Int = { + getDefaultInt(chunkerOptions, Seq("maxCharacters", "max_characters"), default = 500) + } + + private def getNewAfterNChars: Int = { + getDefaultInt(chunkerOptions, Seq("newAfterNChars", "new_after_n_chars"), default = -1) + } + + private def getOverlap: Int = { + getDefaultInt(chunkerOptions, Seq("overlap", "overlap"), default = 0) + } + + private def getChunkerStrategy: String = { + getDefaultString(chunkerOptions, Seq("chunkingStrategy", "chunking_strategy"), default = "none") + } + +} \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/partition/BasicChunker.scala b/src/main/scala/com/johnsnowlabs/partition/BasicChunker.scala new file mode 100644 index 00000000000000..e69a1c86b779b0 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/partition/BasicChunker.scala @@ -0,0 +1,92 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.partition + +import com.johnsnowlabs.reader.HTMLElement + +import scala.collection.mutable + +case class Chunk(elements: List[HTMLElement]) { + def length: Int = elements.map(_.content.length).sum +} + +object BasicChunker { + + def chunkBasic( + elements: List[HTMLElement], + maxCharacters: Int, + newAfterNChars: Int = -1, + overlap: Int = 0 + ): List[Chunk] = { + val softLimit = if (newAfterNChars > 0) newAfterNChars else maxCharacters + var currentChunk = List.empty[HTMLElement] + var currentLength = 0 + val chunks = mutable.ListBuffer.empty[Chunk] + + def finalizeChunk(): Unit = { + if (currentChunk.nonEmpty) { + chunks += Chunk(currentChunk) + currentChunk = List.empty[HTMLElement] + currentLength = 0 + } + } + + for (element <- elements) { + val elLength = element.content.length + + if (elLength > maxCharacters) { + val splitElements = splitHTMLElement(element, maxCharacters, overlap) + for (splitEl <- splitElements) { + if (currentLength + splitEl.content.length > maxCharacters || currentLength >= softLimit) finalizeChunk() + currentChunk :+= splitEl + currentLength += splitEl.content.length + } + } else if (currentLength + elLength > maxCharacters || currentLength >= softLimit) { + finalizeChunk() + currentChunk :+= element + currentLength += elLength + } else { + currentChunk :+= element + currentLength += elLength + } + } + + finalizeChunk() + chunks.toList + } + + private def splitHTMLElement(element: HTMLElement, maxLen: Int, overlap: Int): List[HTMLElement] = { + val words = element.content.split(" ") + val buffer = mutable.ListBuffer.empty[HTMLElement] + var chunk = new StringBuilder + + for (word <- words) { + if (chunk.length + word.length + 1 > maxLen) { + val text = chunk.toString().trim + buffer += element.copy(content = text) + chunk = new StringBuilder + if (overlap > 0 && text.length >= overlap) + chunk.append(text.takeRight(overlap)).append(" ") + } + chunk.append(word).append(" ") + } + + if (chunk.nonEmpty) + buffer += element.copy(content = chunk.toString().trim) + + buffer.toList + } +} diff --git a/src/main/scala/com/johnsnowlabs/partition/Partition.scala b/src/main/scala/com/johnsnowlabs/partition/Partition.scala index 179187da668586..1bb61dd0abca17 100644 --- a/src/main/scala/com/johnsnowlabs/partition/Partition.scala +++ b/src/main/scala/com/johnsnowlabs/partition/Partition.scala @@ -20,7 +20,9 @@ import org.apache.spark.sql.DataFrame import java.net.URL import scala.collection.JavaConverters._ +import scala.util.Try +//TODO: Add notebook examples for this pipeline uses cases class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) { def partition( @@ -31,14 +33,17 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) return sparkNLPReader.html(path) } - val contentTypeOpt = Option(params.get("content_type")) - - val reader = contentTypeOpt match { + val reader = getContentType match { case Some(contentType) => getReaderByContentType(contentType, sparkNLPReader) case None => getReaderByExtension(path, sparkNLPReader) } - reader(path) + val partitionResult = reader(path) + if (hasChunkerStrategy) { + val chunker = new BaseChunker(params.asScala.toMap) + //TODO: Send column name to partitionResult dynamically + partitionResult.withColumn("chunks", chunker.chunkUDF()(partitionResult("txt"))) + } else partitionResult } private def getReaderByContentType( @@ -110,6 +115,20 @@ class Partition(params: java.util.Map[String, String] = new java.util.HashMap()) } } + private def getContentType: Option[String] = { + Seq("content_type", "ContentType") + .flatMap(key => Option(params.get(key))) + .flatMap(value => Try(value).toOption) + .headOption + } + + import scala.jdk.CollectionConverters._ + + private def hasChunkerStrategy: Boolean = { + Seq("chunking_strategy", "chunkingStrategy") + .exists(params.asScala.contains) + } + } object Partition { diff --git a/src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala b/src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala new file mode 100644 index 00000000000000..1ace6a686f4e3c --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala @@ -0,0 +1,138 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.partition + +import com.johnsnowlabs.nlp.AnnotatorType.{CHUNK, DOCUMENT} +import com.johnsnowlabs.nlp.{Annotation, AnnotatorModel, HasSimpleAnnotate} +import com.johnsnowlabs.reader.{HTMLElement, TextReader} +import org.apache.spark.ml.PipelineModel +import org.apache.spark.ml.param.Param +import org.apache.spark.ml.util.Identifiable +import org.apache.spark.sql.functions.{col, udf} +import org.apache.spark.sql.types.{ArrayType, StructType} +import org.apache.spark.sql.{DataFrame, Dataset, Encoders, Row} +import org.apache.spark.sql.functions.explode +import org.slf4j.{Logger, LoggerFactory} + +class PartitionTransformer(override val uid: String) + extends AnnotatorModel[PartitionTransformer] + with HasSimpleAnnotate[PartitionTransformer] { + + def this() = this(Identifiable.randomUID("PartitionTransformer")) + protected val logger: Logger = LoggerFactory.getLogger(getClass.getName) + /** Annotator reference id. Used to identify elements in metadata or to refer to this annotator + * type + */ + override val inputAnnotatorTypes: Array[AnnotatorType] = Array(DOCUMENT) + override val outputAnnotatorType: AnnotatorType = DOCUMENT + + override def setInputCols(value: Array[String]): this.type = { + val validAnnotatorTypes = Array(DOCUMENT, CHUNK) + require( + value.length == inputAnnotatorTypes.length, + s"setInputCols in ${this.uid} expecting ${inputAnnotatorTypes.length} columns. " + + s"Provided column amount: ${value.length}. " + + s"Which should be columns from one of the following annotators: ${validAnnotatorTypes.mkString(", ")} ") + set(inputCols, value) + } + +// override def getInputCols: Array[String] = $(inputCols) + + val contentPath = new Param[String](this, "contentPath", "Path to the content source") + + def setContentPath(value: String): this.type = set(contentPath, value) + + setDefault(contentPath, "") + + /** takes a document and annotations and produces new annotations of this annotator's annotation + * type + * + * @param annotations + * Annotations that correspond to inputAnnotationCols generated by previous annotators if any + * @return + * any number of annotations processed for every input annotation. Not necessary one to one + * relationship + */ + override def annotate(annotations: Seq[Annotation]): Seq[Annotation] = { + annotations + } + + override def _transform(dataset: Dataset[_], recursivePipeline: Option[PipelineModel]): DataFrame = { + val partitionDf = if ($(contentPath).isEmpty) { + val textColum = $(inputCols).head + val flattenDf = dataset.withColumn("flatten_result", explode(col(s"$textColum.result"))) + val textReader = new TextReader() + val parseTxtUDF = udf((text: String) => textReader.parseTxt(text)) + flattenDf.withColumn( + "txt", + parseTxtUDF(col("flatten_result")) + ).drop("flatten_result") + } else { + Partition().partition($(contentPath)) + } + val colName = findHTMLElementColumn(partitionDf).getOrElse { + val schemaString = partitionDf.schema.treeString + throw new Exception( + s"""โŒ No column of type Array[HTMLElement] was found in the DataFrame. + | + |๐Ÿ’ก Expected a column with schema matching: Array[HTMLElement] + | + |๐Ÿงช DataFrame Schema: + |$schemaString + | + |๐Ÿ‘‰ Make sure at least one column is an Array of structs with fields: + | - elementType: String + | - content: String + | - metadata: Map[String, String] + """.stripMargin) + } + partitionDf.withColumn( + getOutputCol, + wrapColumnMetadata(convertToAnnotations(col(colName))) + ) + } + + private def convertToAnnotations = udf { elements: Seq[Row] => + elements.map { row => + val content = row.getAs[String]("content") + val metadata = row.getAs[Map[String, String]]("metadata") + + val begin = 0 + val end = if (content != null) content.length - 1 else 0 + + Annotation( + annotatorType = DOCUMENT, + begin = begin, + end = end, + result = content, + metadata = metadata, + embeddings = Array.emptyFloatArray + ) + } + } + + private def findHTMLElementColumn(df: org.apache.spark.sql.DataFrame): Option[String] = { + val htmlElementSchema = Encoders.product[HTMLElement].schema + df.schema.fields.find { field => + field.dataType match { + case ArrayType(structType: StructType, _) => + structType == htmlElementSchema + case _ => false + } + }.map(_.name) + } + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala index 002d33666ae1af..0f2f53181d94cb 100644 --- a/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/SparkNLPReader.scala @@ -17,6 +17,11 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.annotators.cleaners.util.CleanerHelper.DOUBLE_PARAGRAPH_PATTERN import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.reader.util.PartitionOptions.{ + getDefaultBoolean, + getDefaultInt, + getDefaultString +} import org.apache.spark.ml.Pipeline import org.apache.spark.sql.DataFrame @@ -98,15 +103,15 @@ class SparkNLPReader( else headers.asScala.toMap.map { case (k, v) => k -> v } private def getTitleFontSize: Int = { - getDefaultInt(Seq("titleFontSize", "title_font_size"), default = 16) + getDefaultInt(params.asScala.toMap, Seq("titleFontSize", "title_font_size"), default = 16) } private def getStoreContent: Boolean = { - getDefaultBoolean(Seq("storeContent", "store_content"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("storeContent", "store_content"), default = false) } private def getTimeout: Int = { - getDefaultInt(Seq("timeout"), default = 30) + getDefaultInt(params.asScala.toMap, Seq("timeout"), default = 30) } /** Instantiates class to read email files. @@ -158,7 +163,7 @@ class SparkNLPReader( } private def getAddAttachmentContent: Boolean = { - getDefaultBoolean(Seq("addAttachmentContent", "add_attachment_content"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("addAttachmentContent", "add_attachment_content"), default = false) } /** Instantiates class to read Word files. @@ -266,7 +271,7 @@ class SparkNLPReader( } private def getStoreSplittedPdf: Boolean = { - getDefaultBoolean(Seq("storeSplittedPdf", "store_splitted_pdf"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("storeSplittedPdf", "store_splitted_pdf"), default = false) } /** Instantiates class to read Excel files. @@ -329,11 +334,11 @@ class SparkNLPReader( } private def getInferTableStructure: Boolean = { - getDefaultBoolean(Seq("inferTableStructure", "infer_table_structure"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("inferTableStructure", "infer_table_structure"), default = false) } private def getAppendCells: Boolean = { - getDefaultBoolean(Seq("appendCells", "append_cells"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("appendCells", "append_cells"), default = false) } /** Instantiates class to read PowerPoint files. @@ -451,27 +456,27 @@ class SparkNLPReader( } private def getTitleLengthSize: Int = { - getDefaultInt(Seq("titleLengthSize", "title_length_size"), default = 50) + getDefaultInt(params.asScala.toMap, Seq("titleLengthSize", "title_length_size"), default = 50) } private def getIncludePageBreaks: Boolean = { - getDefaultBoolean(Seq("includePageBreaks", "include_page_breaks"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("includePageBreaks", "include_page_breaks"), default = false) } private def getGroupBrokenParagraphs: Boolean = { - getDefaultBoolean(Seq("groupBrokenParagraphs", "group_broken_paragraphs"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("groupBrokenParagraphs", "group_broken_paragraphs"), default = false) } private def getParagraphSplit: String = { - getDefaultString(Seq("paragraphSplit", "paragraph_split"), default = DOUBLE_PARAGRAPH_PATTERN) + getDefaultString(params.asScala.toMap, Seq("paragraphSplit", "paragraph_split"), default = DOUBLE_PARAGRAPH_PATTERN) } private def getShortLineWordThreshold: Int = { - getDefaultInt(Seq("shortLineWordThreshold", "short_line_word_threshold"), default = 5) + getDefaultInt(params.asScala.toMap, Seq("shortLineWordThreshold", "short_line_word_threshold"), default = 5) } private def getMaxLineCount: Int = { - getDefaultInt(Seq("maxLineCount", "max_line_count"), default = 2000) + getDefaultInt(params.asScala.toMap, Seq("maxLineCount", "max_line_count"), default = 2000) } private def getThreshold: Double = { @@ -491,36 +496,36 @@ class SparkNLPReader( } private def getXmlKeepTags: Boolean = { - getDefaultBoolean(Seq("xmlKeepTags", "xml_keep_tags"), default = false) + getDefaultBoolean(params.asScala.toMap, Seq("xmlKeepTags", "xml_keep_tags"), default = false) } private def getOnlyLeafNodes: Boolean = { - getDefaultBoolean(Seq("onlyLeafNodes", "only_leaf_nodes"), default = true) - } - - private def getDefaultBoolean(options: Seq[String], default: Boolean): Boolean = { - options - .flatMap(key => Option(params.get(key))) - .map(_.trim.toLowerCase) - .flatMap(value => scala.util.Try(value.toBoolean).toOption) - .headOption - .getOrElse(default) + getDefaultBoolean(params.asScala.toMap, Seq("onlyLeafNodes", "only_leaf_nodes"), default = true) } - private def getDefaultInt(options: Seq[String], default: Int): Int = { - options - .flatMap(key => Option(params.get(key))) - .flatMap(value => scala.util.Try(value.toInt).toOption) - .headOption - .getOrElse(default) - } - - private def getDefaultString(options: Seq[String], default: String): String = { - options - .flatMap(key => Option(params.get(key))) - .flatMap(value => scala.util.Try(value).toOption) - .headOption - .getOrElse(default) - } +// private def getDefaultBoolean(options: Seq[String], default: Boolean): Boolean = { +// options +// .flatMap(key => Option(params.get(key))) +// .map(_.trim.toLowerCase) +// .flatMap(value => Try(value.toBoolean).toOption) +// .headOption +// .getOrElse(default) +// } + +// private def getDefaultInt(options: Seq[String], default: Int): Int = { +// options +// .flatMap(key => Option(params.get(key))) +// .flatMap(value => Try(value.toInt).toOption) +// .headOption +// .getOrElse(default) +// } + +// private def getDefaultString(options: Seq[String], default: String): String = { +// options +// .flatMap(key => Option(params.get(key))) +// .flatMap(value => Try(value).toOption) +// .headOption +// .getOrElse(default) +// } } diff --git a/src/main/scala/com/johnsnowlabs/reader/TextReader.scala b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala index fbf3290c45c666..dec5dcee5d938b 100644 --- a/src/main/scala/com/johnsnowlabs/reader/TextReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/TextReader.scala @@ -74,7 +74,7 @@ class TextReader( * - Otherwise, treat blocks as narrative text. * - Omit any element with empty content. */ - private def parseTxt(text: String): Seq[HTMLElement] = { + def parseTxt(text: String): Seq[HTMLElement] = { val processedText = if (groupBrokenParagraphs) { TextParser.autoParagraphGrouper( text, diff --git a/src/main/scala/com/johnsnowlabs/reader/util/PartitionOptions.scala b/src/main/scala/com/johnsnowlabs/reader/util/PartitionOptions.scala new file mode 100644 index 00000000000000..e6986801e964fb --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/util/PartitionOptions.scala @@ -0,0 +1,47 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader.util + +import scala.util.Try + +object PartitionOptions { + + def getDefaultBoolean(params: Map[String, String], options: Seq[String], default: Boolean): Boolean = { + options + .flatMap(params.get) + .map(_.trim.toLowerCase) + .flatMap(value => Try(value.toBoolean).toOption) + .headOption + .getOrElse(default) + } + + def getDefaultInt(params: Map[String, String], options: Seq[String], default: Int): Int = { + options + .flatMap(params.get) + .flatMap(value => Try(value.toInt).toOption) + .headOption + .getOrElse(default) + } + + def getDefaultString(params: Map[String, String], options: Seq[String], default: String): String = { + options + .flatMap(params.get) + .flatMap(value => Try(value).toOption) + .headOption + .getOrElse(default) + } + +} diff --git a/src/test/resources/reader/txt/long-text.txt b/src/test/resources/reader/txt/long-text.txt new file mode 100644 index 00000000000000..cadaab9be2048e --- /dev/null +++ b/src/test/resources/reader/txt/long-text.txt @@ -0,0 +1 @@ +Ukrainian forces reportedly advanced in the western Donetsk-eastern Zaporizhia Oblast border area and in western Zaporizhia Oblast amid Ukrainian counteroffensive operations in southern and eastern Ukraine. Tavriisk Group of Forces Spokesperson Oleksandr Shtupun reported that Ukrainian forces are advancing in the directions of Novoprokopivka (13km south of Orikhiv), Mala Tokmachka (9km southeast of Orikhiv), and Ocheretuvate (25km southeast of Orikhiv) in western Zaporizhia Oblast.[1] Shtupun also stated that Ukrainian forces advanced near Urozhaine (9km south of Velyka Novosilka) and Robotyne (10km south of Orikhiv) and achieved unspecified successes near Staromayorske (9km south of Velyka Novosilka) in the Berdyansk direction (western Donetsk-eastern Zaporizhia Oblast border area) and in an unspecified location in the Melitopol direction (western Zaporizhia Oblast).[2] Ukrainian Eastern Group of Forces Spokesperson Ilya Yevlash stated that Ukrainian forces continued offensive operations in the Bakhmut direction.[3] \ No newline at end of file diff --git a/src/test/scala/com/johnsnowlabs/partition/PartitionChunkerTest.scala b/src/test/scala/com/johnsnowlabs/partition/PartitionChunkerTest.scala new file mode 100644 index 00000000000000..5a9cf8d4ed80dd --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/partition/PartitionChunkerTest.scala @@ -0,0 +1,42 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.partition + +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.tags.FastTest +import org.apache.spark.sql.functions.explode +import org.scalatest.flatspec.AnyFlatSpec + +class PartitionChunkerTest extends AnyFlatSpec { + + import ResourceHelper.spark.implicits._ + val txtDirectory = "src/test/resources/reader/txt" + + "Partition" should "perform basic chunk text" taggedAs FastTest in { + val partitionOptions = Map("contentType" -> "text/plain", "chunkingStrategy" -> "basic") + val textDf = Partition(partitionOptions).partition(s"$txtDirectory/long-text.txt") + textDf.show(truncate = false) + textDf.printSchema() + + val partitionDf = textDf.select(explode($"txt.content")) + partitionDf.show(truncate = false) + + val chunkDf = textDf.select(explode($"chunks.content")) + chunkDf.show(truncate = false) +// assert(!textDf.select(col("txt").getItem(0)).isEmpty) + } + +} diff --git a/src/test/scala/com/johnsnowlabs/partition/PartitionTransformerTest.scala b/src/test/scala/com/johnsnowlabs/partition/PartitionTransformerTest.scala new file mode 100644 index 00000000000000..39ac1d06b3662d --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/partition/PartitionTransformerTest.scala @@ -0,0 +1,75 @@ +package com.johnsnowlabs.partition + +import com.johnsnowlabs.nlp.annotator.MarianTransformer +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.nlp.annotators.cleaners.Cleaner +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec + +class PartitionTransformerTest extends AnyFlatSpec with SparkSessionTest { + + val wordDirectory = "src/test/resources/reader/doc" + + "PartitionTransformer" should "work in a RAG pipeline" in { + val partition = new PartitionTransformer() + .setContentPath(s"$wordDirectory/fake_table.docx") +// .setInputCols("doc") + .setOutputCol("partition") + //TODO: Should we allow the user to set the input column name? + + val marian = MarianTransformer.pretrained() + .setInputCols("partition") + .setOutputCol("translation") + .setMaxInputLength(30) + + val pipeline = new Pipeline() + .setStages(Array(partition, marian)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(emptyDataSet) + resultDf.select("doc", "partition", "translation").show(truncate = false) + } + + it should "work with a Document input" in { + import spark.implicits._ + val testDataSet = Seq("An example with DocumentAssembler annotator").toDS.toDF("text") + + val partition = new PartitionTransformer() + .setInputCols("document") + .setOutputCol("partition") + + val pipeline = new Pipeline() + .setStages(Array(documentAssembler, partition)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(testDataSet) + resultDf.show(truncate = false) + } + + it should "work with a Cleaner input" in { + import spark.implicits._ + val testDf = Seq("\\x88This text contains ยฎnon-ascii characters!โ—").toDS.toDF("text") + testDf.show(truncate = false) + + val cleaner = new Cleaner() + .setInputCols("document") + .setOutputCol("cleaned") + .setCleanerMode("clean_non_ascii_chars") + + val partition = new PartitionTransformer() + .setInputCols("cleaned") + .setOutputCol("partition") + + val pipeline = new Pipeline() + .setStages(Array(documentAssembler, cleaner, partition)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(testDf) + resultDf.show(truncate = false) + } + + // Pipeline4: Partition("contentType" -> "application/msword", "chunkerStrategy" -> "basic") --> ChatGPTAPI or other LLM from HuggingFace + + //TODO: Unit tests exceptions + +}

    L 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


Copy a token from your Hugging Face\ntokens page
and paste it below.
Immediately click login after copying\nyour token or it might be stored in plain text in this notebook file.