diff --git a/benchmarks/online-inference-pipeline/1_fraud_online_feature_pipeline.ipynb b/benchmarks/online-inference-pipeline/1_fraud_online_feature_pipeline.ipynb index 935877c9..9059afe6 100644 --- a/benchmarks/online-inference-pipeline/1_fraud_online_feature_pipeline.ipynb +++ b/benchmarks/online-inference-pipeline/1_fraud_online_feature_pipeline.ipynb @@ -101,7 +101,7 @@ "source": [ "# Read the profiles data from a CSV file\n", "profiles_df = pd.read_csv(\n", - " \"https://repo.hops.works/branch-4.3/hopsworks-tutorials/data/card_fraud_online/profiles.csv\", \n", + " \"https://repo.hops.works/master/hopsworks-tutorials/data/card_fraud_online/profiles.csv\", \n", " parse_dates=[\"birthdate\"],\n", ")\n", "\n", @@ -223,7 +223,7 @@ "source": [ "# Read the transactions data from a CSV file\n", "trans_df = pd.read_csv(\n", - " \"https://repo.hops.works/branch-4.3/hopsworks-tutorials/data/card_fraud_online/transactions.csv\", \n", + " \"https://repo.hops.works/master/hopsworks-tutorials/data/card_fraud_online/transactions.csv\", \n", " parse_dates=[\"datetime\"],\n", ")\n", "\n", @@ -418,11 +418,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "2025-06-26 10:51:40,162 INFO: Initializing external client\n", - "2025-06-26 10:51:40,162 INFO: Base URL: https://10.87.42.15:28181\n", - "2025-06-26 10:51:40,956 INFO: Python Engine initialized.\n", + "2025-06-26 17:24:25,984 INFO: Initializing external client\n", + "2025-06-26 17:24:25,984 INFO: Base URL: https://10.87.43.79:28181\n", + "2025-06-26 17:24:26,739 INFO: Python Engine initialized.\n", "\n", - "Logged in to project, explore it here https://10.87.42.15:28181/p/119\n" + "Logged in to project, explore it here https://10.87.43.79:28181/p/119\n" ] } ], @@ -463,14 +463,14 @@ "output_type": "stream", "text": [ "Feature Group created successfully, explore it at \n", - "https://10.87.42.15:28181/p/119/fs/67/fg/1037\n" + "https://10.87.43.79:28181/p/119/fs/67/fg/13\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "Uploading Dataframe: 100.00% |█████████████████████████████████████████████████████████████████████████████████████████████████| Rows 365112/365112 | Elapsed Time: 02:18 | Remaining Time: 00:00\n" + "Uploading Dataframe: 100.00% |█████████████████████████████████████████████████████████████████████████████████████████████████| Rows 365112/365112 | Elapsed Time: 02:06 | Remaining Time: 00:00\n" ] }, { @@ -479,7 +479,7 @@ "text": [ "Launching job: transactions_fraud_online_fg_1_offline_fg_materialization\n", "Job started successfully, you can follow the progress at \n", - "https://10.87.42.15:28181/p/119/jobs/named/transactions_fraud_online_fg_1_offline_fg_materialization/executions\n", + "https://10.87.43.79:28181/p/119/jobs/named/transactions_fraud_online_fg_1_offline_fg_materialization/executions\n", "✅ Done!\n" ] } @@ -532,7 +532,7 @@ "output_type": "stream", "text": [ "Feature Group created successfully, explore it at \n", - "https://10.87.42.15:28181/p/119/fs/67/fg/1038\n" + "https://10.87.43.79:28181/p/119/fs/67/fg/14\n" ] }, { @@ -548,7 +548,7 @@ "text": [ "Launching job: profile_fraud_online_fg_1_offline_fg_materialization\n", "Job started successfully, you can follow the progress at \n", - "https://10.87.42.15:28181/p/119/jobs/named/profile_fraud_online_fg_1_offline_fg_materialization/executions\n", + "https://10.87.43.79:28181/p/119/jobs/named/profile_fraud_online_fg_1_offline_fg_materialization/executions\n", "✅ Done!\n" ] } @@ -584,28 +584,6 @@ " profile_fg.update_feature_description(desc[\"name\"], desc[\"description\"])" ] }, - { - "cell_type": "code", - "execution_count": 13, - "id": "25199ca6-a3ad-4780-8cc9-580549bc204a", - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "'Project' object has no attribute '_api_key_value'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mproject\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_api_key_value\u001b[49m\n", - "\u001b[0;31mAttributeError\u001b[0m: 'Project' object has no attribute '_api_key_value'" - ] - } - ], - "source": [ - "project._api_key_value" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/benchmarks/online-inference-pipeline/2_fraud_online_training_pipeline.ipynb b/benchmarks/online-inference-pipeline/2_fraud_online_training_pipeline.ipynb index 17fd4fc4..d616ded4 100644 --- a/benchmarks/online-inference-pipeline/2_fraud_online_training_pipeline.ipynb +++ b/benchmarks/online-inference-pipeline/2_fraud_online_training_pipeline.ipynb @@ -47,11 +47,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "2025-06-26 11:38:12,135 INFO: Initializing external client\n", - "2025-06-26 11:38:12,135 INFO: Base URL: https://10.87.42.15:28181\n", - "2025-06-26 11:38:12,830 INFO: Python Engine initialized.\n", + "2025-06-27 09:26:32,476 INFO: Initializing external client\n", + "2025-06-27 09:26:32,477 INFO: Base URL: https://10.87.43.79:28181\n", + "2025-06-27 09:26:33,997 INFO: Python Engine initialized.\n", "\n", - "Logged in to project, explore it here https://10.87.42.15:28181/p/119\n" + "Logged in to project, explore it here https://10.87.43.79:28181/p/119\n" ] } ], @@ -86,7 +86,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "2025-06-26 11:38:14,120 INFO: Using ['gender'] from feature group `profile_fraud_online_fg` as features for the query. To include primary key and event time use `select_all`.\n" + "2025-06-27 09:26:35,795 INFO: Using ['gender'] from feature group `profile_fraud_online_fg` as features for the query. To include primary key and event time use `select_all`.\n" ] } ], @@ -185,7 +185,7 @@ "output_type": "stream", "text": [ "Feature view created successfully, explore it at \n", - "https://10.87.42.15:28181/p/119/fs/67/fv/transactions_fraud_online_fv/version/1\n" + "https://10.87.43.79:28181/p/119/fs/67/fv/transactions_fraud_online_fv/version/1\n" ] } ], @@ -217,15 +217,15 @@ "output_type": "stream", "text": [ "Training dataset job started successfully, you can follow the progress at \n", - "https://10.87.42.15:28181/p/119/jobs/named/transactions_fraud_online_fv_1_create_fv_td_26062025093821/executions\n", - "2025-06-26 11:38:30,120 INFO: Waiting for execution to finish. Current state: SUBMITTED. Final status: UNDEFINED\n", - "2025-06-26 11:38:33,220 INFO: Waiting for execution to finish. Current state: RUNNING. Final status: UNDEFINED\n", - "2025-06-26 11:40:15,912 INFO: Waiting for execution to finish. Current state: AGGREGATING_LOGS. Final status: SUCCEEDED\n", - "2025-06-26 11:40:16,044 INFO: Waiting for log aggregation to finish.\n", - "2025-06-26 11:40:24,520 INFO: Execution finished successfully.\n", - "2025-06-26 11:40:24,633 WARNING: VersionWarning: Incremented version to `1`.\n", + "https://10.87.43.79:28181/p/119/jobs/named/transactions_fraud_online_fv_1_create_fv_td_27062025072643/executions\n", + "2025-06-27 09:26:53,202 INFO: Waiting for execution to finish. Current state: SUBMITTED. Final status: UNDEFINED\n", + "2025-06-27 09:26:56,386 INFO: Waiting for execution to finish. Current state: RUNNING. Final status: UNDEFINED\n", + "2025-06-27 09:28:42,060 INFO: Waiting for execution to finish. Current state: AGGREGATING_LOGS. Final status: SUCCEEDED\n", + "2025-06-27 09:28:42,261 INFO: Waiting for log aggregation to finish.\n", + "2025-06-27 09:28:51,758 INFO: Execution finished successfully.\n", + "2025-06-27 09:28:52,028 WARNING: VersionWarning: Incremented version to `1`.\n", "\n", - "2025-06-26 11:40:28,389 INFO: Provenance cached data - overwriting last accessed/created training dataset from 1 to 1.\n" + "2025-06-27 09:29:28,068 INFO: Provenance cached data - overwriting last accessed/created training dataset from 1 to 1.\n" ] } ], @@ -1418,7 +1418,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6a3fda6f40124ee7808acf80f06cba22", + "model_id": "a7a5165fcb074b3eba1eb589caef877f", "version_major": 2, "version_minor": 0 }, @@ -1432,7 +1432,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "205d25ef805843be8dd93b45dcda5dc1", + "model_id": "0b392a07ea9d48febd55c8cb9954f4ca", "version_major": 2, "version_minor": 0 }, @@ -1446,7 +1446,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0b0c903138c14804b93e23ccaa414d93", + "model_id": "2b711d71cfd845f388f539f0f07b9851", "version_major": 2, "version_minor": 0 }, @@ -1460,7 +1460,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "0b1cbd7b72f940d9abee0cef764a9ab3", + "model_id": "bfa410a911e345fcbb868ed87b0c3ebd", "version_major": 2, "version_minor": 0 }, @@ -1474,7 +1474,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "608dd2c457184847a913220a01ed1373", + "model_id": "8727293c84464254abd31b2188e2fcc4", "version_major": 2, "version_minor": 0 }, @@ -1489,7 +1489,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Model created, explore it at https://10.87.42.15:28181/p/119/models/xgboost_fraud_online_model/1\n" + "Model created, explore it at https://10.87.43.79:28181/p/119/models/xgboost_fraud_online_model/1\n" ] }, { @@ -1543,9 +1543,9 @@ "source": [ "### 📎 Predictor script for Python models\n", "\n", - "The predictor script to use the [online feature store REST APIs](https://docs.hopsworks.ai/latest/user_guides/fs/feature_view/feature-server/) and an aysnc `predict` function to allow higher IO-Bound parallelism.\n", + "The predictor script to use the [online feature store REST APIs](https://docs.hopsworks.ai/latest/user_guides/fs/feature_view/feature-server/).\n", "\n", - "The `predict` function uses [aiohttp](https://docs.aiohttp.org/en/stable/) to asynchronously retrive the feature vectors from the online feature store rest APIs and it then uses the feature view to transform the retrived feature vectors using model dependent tranformations attached to the feature view. The transformed feature vector is then passed to the trained XgBoost Model. \n", + "The `predict` function uses `httpx` to retrive the feature vectors from the online feature store rest APIs and it then uses the feature view to transform the retrived feature vectors using model dependent tranformations attached to the feature view. The transformed feature vector is then passed to the trained XgBoost Model. \n", "\n", "The concurrency used by the XgBoost model is limited since it is a similar model and we do not want it to created contention between threads and cause a bottleneck.\n", "\n", @@ -1557,7 +1557,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -1578,7 +1578,7 @@ "import numpy as np\n", "import hopsworks\n", "import joblib\n", - "import aiohttp\n", + "import httpx\n", "import hopsworks\n", "from hopsworks.hsfs.core import variable_api\n", "\n", @@ -1604,10 +1604,10 @@ " # Lloading the mpdel\n", " self.model = joblib.load(os.environ[\"MODEL_FILES_PATH\"] + \"/xgboost_fraud_online_model.pkl\")\n", "\n", - " # The aiohttp session is lazy initialized since an asyncio event loop is not created when the model is initalized.\n", - " self._session = None\n", + " # The httpx session\n", + " self.client = httpx.Client(verify=False)\n", "\n", - " async def predict(self, inputs):\n", + " def predict(self, inputs):\n", " \"\"\" Serves a prediction request usign a trained model\"\"\"\n", "\n", " data = {\n", @@ -1620,11 +1620,9 @@ " }\n", " }\n", "\n", - " async with self.session.post(f\"https://{self.online_store_rest_endpoint}:4406/0.1.0/batch_feature_store\", \n", + " rsp_data = self.client.post(f\"https://{self.online_store_rest_endpoint}:4406/0.1.0/batch_feature_store\", \n", " headers=self.headers, \n", - " json=data, \n", - " ssl=False) as rsp:\n", - " rsp_data = await rsp.json()\n", + " json=data).json()\n", "\n", " feature_vector = rsp_data[\"features\"]\n", "\n", @@ -1632,29 +1630,23 @@ "\n", " prediction = self.model.predict(transfomed_feature_vectors).tolist() # Numpy Arrays are not JSON serializable\n", "\n", - " return prediction\n", - "\n", - " @property\n", - " def session(self):\n", - " if not self._session:\n", - " self._session = aiohttp.ClientSession()\n", - " return self._session" + " return prediction" ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "bc3bef9f55c14686afda354583935b5a", + "model_id": "66a77106c577486ebd9a56817ceab8ca", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Uploading /Users/manu/Desktop/HopsWorks/bechmarking_hopsworks/predict_rdrs_async.py: 0.000%| | 0/2733…" + "Uploading /Users/manu/Desktop/HopsWorks/bechmarking_hopsworks/predict_rdrs_async.py: 0.000%| | 0/2410…" ] }, "metadata": {}, @@ -1675,75 +1667,25 @@ "predictor_script_path_async_rdrs = os.path.join(\"/Projects\", project.name, uploaded_file_path_async_rdrs)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create the environment required for the deployment" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0407889af2f44f5dae6679673ac266e4", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Uploading /Users/manu/Desktop/HopsWorks/bechmarking_hopsworks/requirements-deployment.txt: 0.000%| | …" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Upload the requirements to hopsworks file system\n", - "ds_api = project.get_dataset_api()\n", - "requirements_path = ds_api.upload(\"requirements-deployment.txt\", \"Resources\", overwrite=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [], - "source": [ - "# Create a new environment\n", - "env_api = project.get_environment_api()\n", - "\n", - "deployment_env = env_api.get_environment(\"pandas-inference-pipeline-aio-hhtp\")\n", - "\n", - "if not deployment_env:\n", - " deployment_env = env_api.create_environment(\"pandas-inference-pipeline-aio-hhtp\", base_environment_name=\"pandas-inference-pipeline\")\n", - " # Insall the environment\n", - " deployment_env.install_requirements(requirements_path)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create the deployment\n", "\n", - "The deployment uses 30 instances with 1 CPU each. This is the minimal requirement to hit a benchmark of 5000 RPS with batchs sizes of 25 for this deployment. We scale horizontaly instead of vertically for the deployment to avoid issues with the [Python Global Interpreter Lock](https://wiki.python.org/moin/GlobalInterpreterLock)" + "The deployment uses 48 instances with 1 CPU each. This is the minimal requirement to hit a benchmark of 5000 RPS with batchs sizes of 25 for this deployment. We scale horizontaly instead of vertically for the deployment to avoid issues with the [Python Global Interpreter Lock](https://wiki.python.org/moin/GlobalInterpreterLock)" ] }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Deployment created, explore it at https://10.87.42.15:28181/p/119/deployments/2054\n", + "Deployment created, explore it at https://10.87.43.79:28181/p/119/deployments/5\n", "Before making predictions, start the deployment by using `.start()`\n" ] } @@ -1754,19 +1696,18 @@ " name=\"deploymentasyncrdrs\", # Specify a name for the deployment\n", " script_file=predictor_script_path_async_rdrs, # Provide the path to the Python script for prediction\n", " resources={'num_instances': 1, 'requests': {'cores': 1}, 'limits': {'cores': 1}},\n", - " environment=deployment_env.name\n", ")" ] }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "99d6b66d77fe49f9b8d276f35d2679cb", + "model_id": "cdb6ae2b2ac04f2b9b843ce3c6215e87", "version_major": 2, "version_minor": 0 }, diff --git a/benchmarks/online-inference-pipeline/3_fraud_online_inference_pipeline.ipynb b/benchmarks/online-inference-pipeline/3_fraud_online_inference_pipeline.ipynb index f7498a1e..d7b24aa9 100644 --- a/benchmarks/online-inference-pipeline/3_fraud_online_inference_pipeline.ipynb +++ b/benchmarks/online-inference-pipeline/3_fraud_online_inference_pipeline.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "id": "ed952ece", "metadata": {}, "outputs": [ @@ -18,13 +18,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "2025-06-26 12:02:10,824 INFO: Closing external client and cleaning up certificates.\n", - "Connection closed.\n", - "2025-06-26 12:02:10,835 INFO: Initializing external client\n", - "2025-06-26 12:02:10,836 INFO: Base URL: https://10.87.42.15:28181\n", - "2025-06-26 12:02:11,542 INFO: Python Engine initialized.\n", + "2025-06-26 17:37:02,224 INFO: Initializing external client\n", + "2025-06-26 17:37:02,224 INFO: Base URL: https://10.87.43.79:28181\n", + "2025-06-26 17:37:02,933 INFO: Python Engine initialized.\n", "\n", - "Logged in to project, explore it here https://10.87.42.15:28181/p/119\n" + "Logged in to project, explore it here https://10.87.43.79:28181/p/119\n" ] } ], @@ -46,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "id": "d2a8475b", "metadata": {}, "outputs": [ @@ -54,24 +52,24 @@ "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using Hopsworks Feature Query Service (1.02s) \n" + "Finished: Reading data from Hopsworks, using Hopsworks Feature Query Service (2.41s) \n" ] }, { "data": { "text/plain": [ - "array([4307206161394478, 4991539658091830, 4556426990917111,\n", - " 4897277640695450, 4123638178254919, 4215909337633098,\n", - " 4883806594247243, 4565376751743421, 4134800299253298,\n", - " 4598649623090127, 4454908897243389, 4628483972728572,\n", - " 4837617840384848, 4359225696258815, 4758035858626403,\n", - " 4689840185625851, 4893428073388709, 4899899195688156,\n", - " 4564193664676304, 4834372953306161, 4277322646120192,\n", - " 4536307339137659, 4322617096913250, 4382251375646022,\n", - " 4167653876012714])" + "array([4529989164575225, 4797751386008277, 4938575311751440,\n", + " 4073886918689387, 4884550899940453, 4807667713290974,\n", + " 4645995009385547, 4308477157795460, 4831276264575419,\n", + " 4287279140563913, 4114577275897108, 4843428721510291,\n", + " 4984384623009925, 4956471536597403, 4455861877211109,\n", + " 4506785011061246, 4600599737277837, 4355584636414605,\n", + " 4575723451072478, 4265592758407850, 4815333028167429,\n", + " 4871812759365964, 4635552426140120, 4043005422002519,\n", + " 4638658919738846])" ] }, - "execution_count": 7, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -100,7 +98,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 3, "id": "4303ac82", "metadata": {}, "outputs": [], @@ -122,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 4, "id": "42196023", "metadata": {}, "outputs": [], @@ -133,7 +131,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 5, "id": "596f3241", "metadata": {}, "outputs": [ @@ -143,7 +141,7 @@ "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" ] }, - "execution_count": 25, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } diff --git a/benchmarks/online-inference-pipeline/README.md b/benchmarks/online-inference-pipeline/README.md index 45d25b81..ef5497e9 100644 --- a/benchmarks/online-inference-pipeline/README.md +++ b/benchmarks/online-inference-pipeline/README.md @@ -19,7 +19,9 @@ This repository benchmarks a deployment running inside **Hopsworks** using [Locu - Generate the API key by following [this guide](https://docs.hopsworks.ai/latest/user_guides/projects/api_key/create_api_key/). 4. **Create the 'HOPSWORKS_API_KEY' secret** + - Create a secret with the name `HOPSWORKS_API_KEY` which contains the API key by following [this guide](https://docs.hopsworks.ai/latest/user_guides/projects/secrets/create_secret/). + 5. **Build the Locust Docker Image** - Use the provided [Dockerfile](https://github.com/logicalclocks/hopsworks-tutorials/blob/branch-4.3/benchmarks/online-inference-pipeline/locust/Dockerfile) to build a Locust image. diff --git a/benchmarks/online-inference-pipeline/locust_reports/locust_report_5k_rps_25_batch_size.pdf b/benchmarks/online-inference-pipeline/locust_reports/locust_report_5k_rps_25_batch_size.pdf index d693a4c1..9ab59855 100644 Binary files a/benchmarks/online-inference-pipeline/locust_reports/locust_report_5k_rps_25_batch_size.pdf and b/benchmarks/online-inference-pipeline/locust_reports/locust_report_5k_rps_25_batch_size.pdf differ diff --git a/benchmarks/online-inference-pipeline/requirements-deployment.txt b/benchmarks/online-inference-pipeline/requirements-deployment.txt deleted file mode 100644 index 807d1284..00000000 --- a/benchmarks/online-inference-pipeline/requirements-deployment.txt +++ /dev/null @@ -1 +0,0 @@ -aiohttp==3.11.10 \ No newline at end of file