From 247340b89d8fc008c362a450a141a6a3b5bcc585 Mon Sep 17 00:00:00 2001 From: vinhn Date: Fri, 24 Oct 2025 05:44:14 +0000 Subject: [PATCH 1/6] add LLM TCO calculator --- notebooks/LLM_TCO_Calculator.xlsx | Bin 0 -> 213009 bytes notebooks/README.md | 5 + notebooks/TCO_calculator.ipynb | 607 ++++++++++++++++++++++++++++++ 3 files changed, 612 insertions(+) create mode 100644 notebooks/LLM_TCO_Calculator.xlsx create mode 100644 notebooks/README.md create mode 100644 notebooks/TCO_calculator.ipynb diff --git a/notebooks/LLM_TCO_Calculator.xlsx b/notebooks/LLM_TCO_Calculator.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1c8eb31fa243356d1399e7c95419509a56f4f443 GIT binary patch literal 213009 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 literal 0 HcmV?d00001 diff --git a/notebooks/README.md b/notebooks/README.md new file mode 100644 index 000000000..61198791e --- /dev/null +++ b/notebooks/README.md @@ -0,0 +1,5 @@ +# AIPerf Utility Notebooks + +This folder contains the various utility notebooks for AIPerf. + +1. [TCO_calculator.ipynb](TCO_calculator.ipynb): This notebook allows user to benchmark a NIM LLM deployment, then export the data to the NIM total cost of ownership (TCO) calculator. \ No newline at end of file diff --git a/notebooks/TCO_calculator.ipynb b/notebooks/TCO_calculator.ipynb new file mode 100644 index 000000000..af534f980 --- /dev/null +++ b/notebooks/TCO_calculator.ipynb @@ -0,0 +1,607 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "f3e4c571-624d-4db6-b4d9-ae912879967b", + "metadata": {}, + "source": [ + "# AIPerf -> LLM TCO Calculator Data Connector\n", + "\n", + "This notebook shows you how to do LLM performance benchmarking with the NVIDIA AIPerf tool and then export the data to an Excel spreadsheet, which can be used to transfer the data to the NIM [spreadsheet TCO calculator tool].\n", + "\n", + "\n", + "To execute this notebook, you can use the NVIDIA Pytorch container:\n", + "```\n", + "docker run --gpus=all --ipc=host --net=host --rm -it -v $PWD:/myworkspace nvcr.io/nvidia/pytorch:25.03-py3 bash \n", + "```\n", + "\n", + "Then from within the docker interactive session:\n", + "```\n", + "pip install jupyterlab\n", + "jupyter lab --ip 0.0.0.0 --port=8888 --allow-root --notebook-dir=/myworkspace\n", + "```\n", + "\n", + "First, we define some metadata fields describing the deployment environment.\n", + "\n", + "**Notes:**\n", + "- NIM engine ID provides both the backend type (e.g. TensorRT-LLM, vLLM or SGlang) and precision. You can find this information when the NIM container starts.\n", + "\n", + "- This notebook collects data corresponding to a single deployment environment described by the metadata field. In this tutorial, we will make use of the `Meta-Llama-3-8B-Instruct` model. Note that NVIDIA NGC and HuggingFace model hub use slightly different identifier for this model." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "93c18473-09ea-4a6f-87fa-d67fa3f7daa5", + "metadata": {}, + "outputs": [], + "source": [ + "meta_field = {\n", + " 'Model': \"meta-llama/Meta-Llama-3-8B-Instruct\",\n", + " 'GPU Type': \"H100_80GB\",\n", + " 'number_of_gpus': 1,\n", + " 'Precision': \"BF16\",\n", + " 'Execution Mode': \"NIM-TRTLLM\",\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "70b3df53-c103-4de2-81f5-419aa4d65f83", + "metadata": {}, + "source": [ + "## Pre-requisite\n", + "\n", + "First, we install the AIPerf tool in the Pytorch container. \n", + "As a client-side LLM-focused benchmarking tool, NVIDIA AIPerf provides key metrics such as time to first token (TTFT), inter-token latency (ITL), tokens per second (TPS), requests per second (RPS) and more. AIPerf also supports any LLM inference service conforming to the OpenAI API specification, a widely accepted de facto standard in the industry. For this benchmarking guide, we’ll use NVIDIA NIM, a collection of inference microservices that offer high-throughput and low-latency inference for both base and fine-tuned LLMs. NIM features ease-of-use and enterprise-grade security and manageability. \n", + "\n", + "### Install AIPerf tool" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad5de6fe-8547-4259-956a-980aa8b71dce", + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + "pip install aiperf" + ] + }, + { + "cell_type": "markdown", + "id": "9e6351a6-a5a3-4067-831e-abe26ae53969", + "metadata": {}, + "source": [ + "### Setting up a NIM LLM server (optional)\n", + "\n", + "If you don't already have a target for benchmarking, like an OpenAI compatible LLM service, let's setup one. \n", + "\n", + "NVIDIA NIM provides the easiest and quickest way to put LLMs and other AI foundation models into production. Read [A Simple Guide to Deploying Generative AI with NVIDIA NIM](https://developer.nvidia.com/blog/a-simple-guide-to-deploying-generative-ai-with-nvidia-nim/) or consult the latest [NIM LLM documentation](https://docs.nvidia.com/nim/large-language-models/latest/introduction.html) to get started, which will walk you through hardware requirements and prerequisites, including NVIDIA NGC API keys.\n", + "\n", + "For convenience, the following commands have been provided for deploying NIM and executing inference from the [Getting Started Guide](https://docs.nvidia.com/nim/large-language-models/latest/getting-started.html): \n", + "\n", + " \n", + "```\n", + "export NGC_API_KEY= \n", + "export LOCAL_NIM_CACHE=~/.cache/nim\n", + "\n", + "mkdir -p \"$LOCAL_NIM_CACHE\"\n", + "\n", + "docker run -it --rm \\\n", + " --gpus all \\\n", + " --shm-size=16GB \\\n", + " -e NGC_API_KEY \\\n", + " -v \"$LOCAL_NIM_CACHE:/opt/nim/.cache\" \\\n", + " -u $(id -u) \\\n", + " -p 8000:8000 \\\n", + " nvcr.io/nim/meta/llama3-8b-instruct:latest\n", + "```\n", + "\n", + "\n", + "## Performance benchmarking script\n", + "\n", + "The next step is to define the use cases (i.e. input/output sequence length scenarios) and carry out the benchmarking." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e8395733-ce18-4447-845c-b3579acc2067", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting benchmark.sh\n" + ] + } + ], + "source": [ + "%%writefile benchmark.sh\n", + "#!/usr/bin/env bash\n", + "\n", + "declare -A useCases\n", + "\n", + "# Populate the array with use case descriptions and their specified input/output lengths\n", + "useCases[\"Translation\"]=\"200/200\"\n", + "useCases[\"Text classification\"]=\"200/5\"\n", + "useCases[\"Text summary\"]=\"1000/200\"\n", + "\n", + "# Function to execute AIPerf with the input/output lengths as arguments\n", + "runBenchmark() {\n", + " local description=\"$1\"\n", + " local lengths=\"${useCases[$description]}\"\n", + " IFS='/' read -r inputLength outputLength <<< \"$lengths\"\n", + "\n", + " echo \"Running AIPerf for $description with input length $inputLength and output length $outputLength\"\n", + " #Runs\n", + " for concurrency in 1 2 5 10 50 100 250; do\n", + "\n", + " local INPUT_SEQUENCE_LENGTH=$inputLength\n", + " local INPUT_SEQUENCE_STD=0\n", + " local OUTPUT_SEQUENCE_LENGTH=$outputLength\n", + " local CONCURRENCY=$concurrency\n", + " local REQUEST_COUNT=$(($CONCURRENCY * 3))\n", + " local MODEL=meta/llama3-8b-instruct\n", + "\n", + " aiperf profile \\\n", + " -m $MODEL \\\n", + " --endpoint-type chat \\\n", + " --streaming \\\n", + " -u localhost:8000 \\\n", + " --synthetic-input-tokens-mean $INPUT_SEQUENCE_LENGTH \\\n", + " --synthetic-input-tokens-stddev $INPUT_SEQUENCE_STD \\\n", + " --concurrency $CONCURRENCY \\\n", + " --request-count $REQUEST_COUNT \\\n", + " --output-tokens-mean $OUTPUT_SEQUENCE_LENGTH \\\n", + " --extra-inputs min_tokens:$OUTPUT_SEQUENCE_LENGTH \\\n", + " --extra-inputs ignore_eos:true \\\n", + " --tokenizer meta-llama/Meta-Llama-3-8B-Instruct \\\n", + " --artifact-dir artifact/ISL${INPUT_SEQUENCE_LENGTH}_OSL${OUTPUT_SEQUENCE_LENGTH}/CON${CONCURRENCY}\n", + "\n", + " done\n", + "}\n", + "\n", + "# Iterate over all defined use cases and run the benchmark script for each\n", + "for description in \"${!useCases[@]}\"; do\n", + " runBenchmark \"$description\"\n", + "done\n" + ] + }, + { + "cell_type": "markdown", + "id": "603f1941-5206-4bca-a547-028e0ea50f21", + "metadata": {}, + "source": [ + "This test will use the llama-3 tokenizer from HuggingFace, which is a guarded repository [https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct](https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct). You will need to apply for access, then login with your HF credential.\n", + "\n", + "Open a terminal in you Jupyter lab interface, then login to HF:\n", + "```\n", + " pip install huggingface_hub\n", + " huggingface-cli login\n", + "```\n", + "\n", + "Next, we execute the bash script, which will carry out the defined benchmarking scenarios and gather the data in a default directory named `artifacts` under the current working directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cbfacd3-5755-4c0b-ae23-3abffceebbdb", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%%bash\n", + "bash benchmark.sh" + ] + }, + { + "cell_type": "markdown", + "id": "c480b28d-6816-4c84-9124-bdc56fc81f41", + "metadata": {}, + "source": [ + "## Reading AIPerf data\n", + "\n", + "Once performance benchmarking is done, we read and collect the results in a single data frame." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ff69c986-0c9b-46a9-8f28-800cd61ab24d", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "import pandas as pd\n", + "\n", + "ISL_OSL_LIST = [\"200_5\", \"200_200\", \"1000_200\"]\n", + "CONCURRENCIES = [1, 2, 5, 10, 50, 100, 250]\n", + "df = pd.DataFrame()\n", + "\n", + "for concurrency in CONCURRENCIES :\n", + " for isl_osl in ISL_OSL_LIST:\n", + " ISL=isl_osl.split(\"_\")[0]\n", + " OSL=isl_osl.split(\"_\")[1]\n", + " \n", + " with open(f'./artifact/ISL{ISL}_OSL{OSL}/CON{concurrency}/profile_export_aiperf.json', 'r') as f:\n", + " data = json.load(f)\n", + " \n", + " row = {\n", + " 'Inter Token 90th Percentile Latency (ms)': data[\"records\"][\"inter_token_latency\"][\"p90\"],\n", + " 'Inter Token 99th Percentile Latency (ms)': data[\"records\"][\"inter_token_latency\"][\"p99\"],\n", + " 'Inter Token Average Latency (ms)': data[\"records\"][\"inter_token_latency\"][\"avg\"],\n", + " 'Time to First Token 90th Percentile Latency (ms)': data[\"records\"][\"ttft\"][\"p90\"],\n", + " 'Time to First Token 99th Percentile Latency (ms)': data[\"records\"][\"ttft\"][\"p99\"],\n", + " 'Time to First Token Average Latency (ms)': data[\"records\"][\"ttft\"][\"avg\"],\n", + " 'Request 90th Percentile Latency (ms)': data[\"records\"][\"request_latency\"][\"p90\"],\n", + " 'Request 99th Percentile Latency (ms)': data[\"records\"][\"request_latency\"][\"p99\"],\n", + " 'Request Latency (ms)': data[\"records\"][\"request_latency\"][\"avg\"],\n", + " 'Requests per Second': data[\"records\"][\"request_throughput\"][\"avg\"],\n", + " 'Tokens per Second': data[\"records\"][\"output_token_throughput\"][\"avg\"],\n", + " 'Seq Length (ISL/OSL)': isl_osl,\n", + " 'Concurrency': concurrency\n", + " } \n", + " \n", + " row = meta_field | row\n", + " \n", + " df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)" + ] + }, + { + "cell_type": "markdown", + "id": "3a997b59-3d4e-4877-953c-088563aa8998", + "metadata": {}, + "source": [ + "## Exporting data to excel format\n", + "\n", + "We next export the benchmarking data to a NIM TCO Calculator compatible format, which comprises both metadata fields as well as performance metric fields." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "f39710a9-882c-44aa-b428-d7ed2976eb23", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ModelGPU Typenumber_of_gpusPrecisionExecution ModeInter Token 90th Percentile Latency (ms)Inter Token 99th Percentile Latency (ms)Inter Token Average Latency (ms)Time to First Token 90th Percentile Latency (ms)Time to First Token 99th Percentile Latency (ms)Time to First Token Average Latency (ms)Request 90th Percentile Latency (ms)Request 99th Percentile Latency (ms)Request Latency (ms)Requests per SecondTokens per SecondSeq Length (ISL/OSL)Concurrency
0meta-llama/Meta-Llama-3-8B-InstructH100_80GB1BF16NIM-TRTLLM4.9493454.9670924.86787120.53295620.98871818.88936439.75327340.06144838.36085025.419948127.099742200_51
1meta-llama/Meta-Llama-3-8B-InstructH100_80GB1BF16NIM-TRTLLM4.8732484.8743384.86967920.86083821.59170518.887994990.637183991.584959987.9540301.010877202.175430200_2001
2meta-llama/Meta-Llama-3-8B-InstructH100_80GB1BF16NIM-TRTLLM4.7084174.7115564.70026346.37349846.79023245.210619981.959344982.167204980.5629751.018414203.6828551000_2001
3meta-llama/Meta-Llama-3-8B-InstructH100_80GB1BF16NIM-TRTLLM6.2573296.3406665.83873329.91147833.78747822.65207853.72580957.13734546.00701141.722412208.612062200_52
4meta-llama/Meta-Llama-3-8B-InstructH100_80GB1BF16NIM-TRTLLM4.9204284.9216034.91261429.58008133.86831422.3793201007.4998421011.103446999.9895051.995674399.134707200_2002
\n", + "
" + ], + "text/plain": [ + " Model GPU Type number_of_gpus Precision \\\n", + "0 meta-llama/Meta-Llama-3-8B-Instruct H100_80GB 1 BF16 \n", + "1 meta-llama/Meta-Llama-3-8B-Instruct H100_80GB 1 BF16 \n", + "2 meta-llama/Meta-Llama-3-8B-Instruct H100_80GB 1 BF16 \n", + "3 meta-llama/Meta-Llama-3-8B-Instruct H100_80GB 1 BF16 \n", + "4 meta-llama/Meta-Llama-3-8B-Instruct H100_80GB 1 BF16 \n", + "\n", + " Execution Mode Inter Token 90th Percentile Latency (ms) \\\n", + "0 NIM-TRTLLM 4.949345 \n", + "1 NIM-TRTLLM 4.873248 \n", + "2 NIM-TRTLLM 4.708417 \n", + "3 NIM-TRTLLM 6.257329 \n", + "4 NIM-TRTLLM 4.920428 \n", + "\n", + " Inter Token 99th Percentile Latency (ms) Inter Token Average Latency (ms) \\\n", + "0 4.967092 4.867871 \n", + "1 4.874338 4.869679 \n", + "2 4.711556 4.700263 \n", + "3 6.340666 5.838733 \n", + "4 4.921603 4.912614 \n", + "\n", + " Time to First Token 90th Percentile Latency (ms) \\\n", + "0 20.532956 \n", + "1 20.860838 \n", + "2 46.373498 \n", + "3 29.911478 \n", + "4 29.580081 \n", + "\n", + " Time to First Token 99th Percentile Latency (ms) \\\n", + "0 20.988718 \n", + "1 21.591705 \n", + "2 46.790232 \n", + "3 33.787478 \n", + "4 33.868314 \n", + "\n", + " Time to First Token Average Latency (ms) \\\n", + "0 18.889364 \n", + "1 18.887994 \n", + "2 45.210619 \n", + "3 22.652078 \n", + "4 22.379320 \n", + "\n", + " Request 90th Percentile Latency (ms) Request 99th Percentile Latency (ms) \\\n", + "0 39.753273 40.061448 \n", + "1 990.637183 991.584959 \n", + "2 981.959344 982.167204 \n", + "3 53.725809 57.137345 \n", + "4 1007.499842 1011.103446 \n", + "\n", + " Request Latency (ms) Requests per Second Tokens per Second \\\n", + "0 38.360850 25.419948 127.099742 \n", + "1 987.954030 1.010877 202.175430 \n", + "2 980.562975 1.018414 203.682855 \n", + "3 46.007011 41.722412 208.612062 \n", + "4 999.989505 1.995674 399.134707 \n", + "\n", + " Seq Length (ISL/OSL) Concurrency \n", + "0 200_5 1 \n", + "1 200_200 1 \n", + "2 1000_200 1 \n", + "3 200_5 2 \n", + "4 200_200 2 " + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "5baf8e86-c8d1-42fc-94d3-15b592a5adc9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/dill-0.3.9-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/texttable-1.7.0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/opt_einsum-3.4.0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/igraph-0.11.8-py3.12-linux-x86_64.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/nvfuser-0.2.13a0+0d33366-py3.12-linux-x86_64.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/lightning_thunder-0.2.0.dev0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/lightning_utilities-0.11.9-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/looseversion-1.3.0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", + "\u001b[0mLooking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com\n", + "Collecting openpyxl\n", + " Downloading openpyxl-3.1.5-py2.py3-none-any.whl.metadata (2.5 kB)\n", + "Collecting et-xmlfile (from openpyxl)\n", + " Downloading et_xmlfile-2.0.0-py3-none-any.whl.metadata (2.7 kB)\n", + "Downloading openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)\n", + "Downloading et_xmlfile-2.0.0-py3-none-any.whl (18 kB)\n", + "Installing collected packages: et-xmlfile, openpyxl\n", + "Successfully installed et-xmlfile-2.0.0 openpyxl-3.1.5\n", + "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable.It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.\u001b[0m\u001b[33m\n", + "\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.2\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpython -m pip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "!pip install openpyxl" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "125f78e6-cc51-4091-bb16-9a1d8403d6cf", + "metadata": {}, + "outputs": [], + "source": [ + "columns = [\n", + " 'Model',\n", + " 'GPU Type',\n", + " 'Seq Length (ISL/OSL)',\n", + " 'number_of_gpus',\n", + " 'Concurrency',\n", + " 'Precision',\n", + " 'Execution Mode',\n", + " 'Inter Token 90th Percentile Latency (ms)',\n", + " 'Inter Token 99th Percentile Latency (ms)',\n", + " 'Inter Token Average Latency (ms)',\n", + " 'Time to First Token 90th Percentile Latency (ms)',\n", + " 'Time to First Token 99th Percentile Latency (ms)',\n", + " 'Time to First Token Average Latency (ms)',\n", + " 'Request 90th Percentile Latency (ms)',\n", + " 'Request 99th Percentile Latency (ms)',\n", + " 'Request Latency (ms)',\n", + " 'Requests per Second',\n", + " 'Tokens per Second'\n", + " ]\n", + "df[columns].to_excel('data.xlsx', index=False)\n" + ] + }, + { + "cell_type": "markdown", + "id": "becc138b-6d92-49aa-a9a6-3ad31ad75c87", + "metadata": {}, + "source": [ + "## Importing the data to the TCO calculator\n", + "\n", + "The [NIM TCO calculator tool](LLM_TCO_Calculator.xlsx) is implemented as an Excel spreadsheet. You can use MS Excel spreadsheet to open the excel file above, then simply copy the data rows into the \"data\" subsheet of the TCO calculator. That will complete the import phase and make the new data available in the TCO calculator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a411717e-00ee-4a10-a274-7aa4caa9580d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3de83cfde4913fdb58ebb3e169faf0ed1970bfd4 Mon Sep 17 00:00:00 2001 From: vinhn Date: Wed, 5 Nov 2025 04:20:35 +0000 Subject: [PATCH 2/6] fix minor text --- notebooks/TCO_calculator.ipynb | 212 ++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 6 deletions(-) diff --git a/notebooks/TCO_calculator.ipynb b/notebooks/TCO_calculator.ipynb index af534f980..003b19c05 100644 --- a/notebooks/TCO_calculator.ipynb +++ b/notebooks/TCO_calculator.ipynb @@ -8,12 +8,12 @@ "source": [ "# AIPerf -> LLM TCO Calculator Data Connector\n", "\n", - "This notebook shows you how to do LLM performance benchmarking with the NVIDIA AIPerf tool and then export the data to an Excel spreadsheet, which can be used to transfer the data to the NIM [spreadsheet TCO calculator tool].\n", + "This notebook shows you how to do LLM performance benchmarking with the NVIDIA AIPerf tool and then export the data to a TCO (Total Cost of Ownership) calculator in [Excel spreadsheet format](./LLM_TCO_Calculator.xlsx).\n", "\n", "\n", - "To execute this notebook, you can use the NVIDIA Pytorch container:\n", + "To execute this notebook, you can use the NVIDIA Triton server container:\n", "```\n", - "docker run --gpus=all --ipc=host --net=host --rm -it -v $PWD:/myworkspace nvcr.io/nvidia/pytorch:25.03-py3 bash \n", + "docker run --gpus=all --ipc=host --net=host --rm -it -v $PWD:/myworkspace nvcr.io/nvidia/tritonserver:25.05-py3-sdk bash \n", "```\n", "\n", "Then from within the docker interactive session:\n", @@ -61,10 +61,210 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "ad5de6fe-8547-4259-956a-980aa8b71dce", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting aiperf\n", + " Downloading aiperf-0.2.0-py3-none-any.whl.metadata (9.0 kB)\n", + "Collecting aiofiles~=24.1.0 (from aiperf)\n", + " Downloading aiofiles-24.1.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting aiohttp~=3.12.14 (from aiperf)\n", + " Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.7 kB)\n", + "Collecting cyclopts<4,>=3 (from aiperf)\n", + " Downloading cyclopts-3.24.0-py3-none-any.whl.metadata (11 kB)\n", + "Collecting ffmpeg-python~=0.2.0 (from aiperf)\n", + " Downloading ffmpeg_python-0.2.0-py3-none-any.whl.metadata (1.7 kB)\n", + "Requirement already satisfied: numpy~=1.26.4 in /usr/local/lib/python3.12/dist-packages (from aiperf) (1.26.4)\n", + "Collecting openai~=1.92.2 (from openai[aiohttp]~=1.92.2->aiperf)\n", + " Downloading openai-1.92.3-py3-none-any.whl.metadata (29 kB)\n", + "Requirement already satisfied: orjson~=3.10.18 in /usr/local/lib/python3.12/dist-packages (from aiperf) (3.10.18)\n", + "Collecting pillow~=11.1.0 (from aiperf)\n", + " Downloading pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (9.1 kB)\n", + "Requirement already satisfied: prometheus-client~=0.23.1 in /usr/local/lib/python3.12/dist-packages (from aiperf) (0.23.1)\n", + "Collecting psutil~=7.0.0 (from aiperf)\n", + " Downloading psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (22 kB)\n", + "Collecting pydantic-settings~=2.10.0 (from aiperf)\n", + " Downloading pydantic_settings-2.10.1-py3-none-any.whl.metadata (3.4 kB)\n", + "Collecting pydantic~=2.11.4 (from aiperf)\n", + " Downloading pydantic-2.11.10-py3-none-any.whl.metadata (68 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m68.6/68.6 kB\u001b[0m \u001b[31m4.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hCollecting pyzmq~=26.4.0 (from aiperf)\n", + " Downloading pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (6.0 kB)\n", + "Collecting rich~=14.1.0 (from aiperf)\n", + " Downloading rich-14.1.0-py3-none-any.whl.metadata (18 kB)\n", + "Collecting ruamel-yaml~=0.18.12 (from aiperf)\n", + " Downloading ruamel.yaml-0.18.16-py3-none-any.whl.metadata (25 kB)\n", + "Collecting setproctitle~=1.3.6 (from aiperf)\n", + " Downloading setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (10 kB)\n", + "Requirement already satisfied: soundfile~=0.13.1 in /usr/local/lib/python3.12/dist-packages (from aiperf) (0.13.1)\n", + "Collecting textual~=5.3.0 (from aiperf)\n", + " Downloading textual-5.3.0-py3-none-any.whl.metadata (9.1 kB)\n", + "Requirement already satisfied: tqdm>=4.67.1 in /usr/local/lib/python3.12/dist-packages (from aiperf) (4.67.1)\n", + "Requirement already satisfied: transformers>=4.52.0 in /usr/local/lib/python3.12/dist-packages (from aiperf) (4.52.2)\n", + "Collecting uvloop~=0.21.0 (from aiperf)\n", + " Downloading uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (2.6.1)\n", + "Collecting aiosignal>=1.4.0 (from aiohttp~=3.12.14->aiperf)\n", + " Downloading aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)\n", + "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (25.3.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (1.6.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (6.4.4)\n", + "Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (0.3.1)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (1.20.0)\n", + "Collecting docstring-parser>=0.15 (from cyclopts<4,>=3->aiperf)\n", + " Downloading docstring_parser-0.17.0-py3-none-any.whl.metadata (3.5 kB)\n", + "Collecting rich-rst<2.0.0,>=1.3.1 (from cyclopts<4,>=3->aiperf)\n", + " Downloading rich_rst-1.3.2-py3-none-any.whl.metadata (6.1 kB)\n", + "Collecting future (from ffmpeg-python~=0.2.0->aiperf)\n", + " Downloading future-1.0.0-py3-none-any.whl.metadata (4.0 kB)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (4.11.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (0.28.1)\n", + "Collecting jiter<1,>=0.4.0 (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf)\n", + " Downloading jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.2 kB)\n", + "Requirement already satisfied: sniffio in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (1.3.1)\n", + "Requirement already satisfied: typing-extensions<5,>=4.11 in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (4.13.2)\n", + "Collecting httpx-aiohttp>=0.1.6 (from openai[aiohttp]~=1.92.2->aiperf)\n", + " Downloading httpx_aiohttp-0.1.9-py3-none-any.whl.metadata (2.8 kB)\n", + "Collecting annotated-types>=0.6.0 (from pydantic~=2.11.4->aiperf)\n", + " Downloading annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)\n", + "Collecting pydantic-core==2.33.2 (from pydantic~=2.11.4->aiperf)\n", + " Downloading pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.8 kB)\n", + "Collecting typing-inspection>=0.4.0 (from pydantic~=2.11.4->aiperf)\n", + " Downloading typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)\n", + "Collecting python-dotenv>=0.21.0 (from pydantic-settings~=2.10.0->aiperf)\n", + " Downloading python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.12/dist-packages (from rich~=14.1.0->aiperf) (3.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.12/dist-packages (from rich~=14.1.0->aiperf) (2.19.1)\n", + "Collecting ruamel.yaml.clib>=0.2.7 (from ruamel-yaml~=0.18.12->aiperf)\n", + " Downloading ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.0 kB)\n", + "Requirement already satisfied: cffi>=1.0 in /usr/local/lib/python3.12/dist-packages (from soundfile~=0.13.1->aiperf) (1.17.1)\n", + "Requirement already satisfied: platformdirs<5,>=3.6.0 in /usr/local/lib/python3.12/dist-packages (from textual~=5.3.0->aiperf) (4.5.0)\n", + "Collecting pygments<3.0.0,>=2.13.0 (from rich~=14.1.0->aiperf)\n", + " Downloading pygments-2.19.2-py3-none-any.whl.metadata (2.5 kB)\n", + "Requirement already satisfied: filelock in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (3.18.0)\n", + "Requirement already satisfied: huggingface-hub<1.0,>=0.30.0 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (0.31.4)\n", + "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (25.0)\n", + "Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (6.0.2)\n", + "Requirement already satisfied: regex!=2019.12.17 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (2024.11.6)\n", + "Requirement already satisfied: requests in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (2.32.3)\n", + "Requirement already satisfied: tokenizers<0.22,>=0.21 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (0.21.1)\n", + "Requirement already satisfied: safetensors>=0.4.3 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (0.5.3)\n", + "Requirement already satisfied: idna>=2.8 in /usr/local/lib/python3.12/dist-packages (from anyio<5,>=3.5.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (3.10)\n", + "Requirement already satisfied: pycparser in /usr/local/lib/python3.12/dist-packages (from cffi>=1.0->soundfile~=0.13.1->aiperf) (2.22)\n", + "Requirement already satisfied: certifi in /usr/local/lib/python3.12/dist-packages (from httpx<1,>=0.23.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (2025.4.26)\n", + "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.12/dist-packages (from httpx<1,>=0.23.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (1.0.9)\n", + "Requirement already satisfied: h11>=0.16 in /usr/local/lib/python3.12/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (0.16.0)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<1.0,>=0.30.0->transformers>=4.52.0->aiperf) (2025.5.0)\n", + "Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.12/dist-packages (from markdown-it-py>=2.2.0->rich~=14.1.0->aiperf) (0.1.2)\n", + "Collecting linkify-it-py<3,>=1 (from markdown-it-py[linkify,plugins]>=2.1.0->textual~=5.3.0->aiperf)\n", + " Downloading linkify_it_py-2.0.3-py3-none-any.whl.metadata (8.5 kB)\n", + "Collecting mdit-py-plugins (from markdown-it-py[linkify,plugins]>=2.1.0->textual~=5.3.0->aiperf)\n", + " Downloading mdit_py_plugins-0.5.0-py3-none-any.whl.metadata (2.8 kB)\n", + "Collecting docutils (from rich-rst<2.0.0,>=1.3.1->cyclopts<4,>=3->aiperf)\n", + " Downloading docutils-0.22.2-py3-none-any.whl.metadata (15 kB)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests->transformers>=4.52.0->aiperf) (3.4.2)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests->transformers>=4.52.0->aiperf) (2.4.0)\n", + "Collecting uc-micro-py (from linkify-it-py<3,>=1->markdown-it-py[linkify,plugins]>=2.1.0->textual~=5.3.0->aiperf)\n", + " Downloading uc_micro_py-1.0.3-py3-none-any.whl.metadata (2.0 kB)\n", + "Downloading aiperf-0.2.0-py3-none-any.whl (2.9 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.9/2.9 MB\u001b[0m \u001b[31m10.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m \u001b[36m0:00:01\u001b[0mm\n", + "\u001b[?25hDownloading aiofiles-24.1.0-py3-none-any.whl (15 kB)\n", + "Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.7 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.7/1.7 MB\u001b[0m \u001b[31m37.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading cyclopts-3.24.0-py3-none-any.whl (86 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m86.2/86.2 kB\u001b[0m \u001b[31m49.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading ffmpeg_python-0.2.0-py3-none-any.whl (25 kB)\n", + "Downloading openai-1.92.3-py3-none-any.whl (753 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m753.4/753.4 kB\u001b[0m \u001b[31m37.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl (4.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.5/4.5 MB\u001b[0m \u001b[31m55.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (277 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m278.0/278.0 kB\u001b[0m \u001b[31m79.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading pydantic-2.11.10-py3-none-any.whl (444 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m444.8/444.8 kB\u001b[0m \u001b[31m84.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading pydantic_core-2.33.2-cp312-cp312-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[31m89.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading pydantic_settings-2.10.1-py3-none-any.whl (45 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m45.2/45.2 kB\u001b[0m \u001b[31m27.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl (855 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m855.9/855.9 kB\u001b[0m \u001b[31m86.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading rich-14.1.0-py3-none-any.whl (243 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m243.4/243.4 kB\u001b[0m \u001b[31m66.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading ruamel.yaml-0.18.16-py3-none-any.whl (119 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m119.9/119.9 kB\u001b[0m \u001b[31m68.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (32 kB)\n", + "Downloading textual-5.3.0-py3-none-any.whl (702 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m702.7/702.7 kB\u001b[0m \u001b[31m99.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.7 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.7/4.7 MB\u001b[0m \u001b[31m87.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading aiosignal-1.4.0-py3-none-any.whl (7.5 kB)\n", + "Downloading annotated_types-0.7.0-py3-none-any.whl (13 kB)\n", + "Downloading docstring_parser-0.17.0-py3-none-any.whl (36 kB)\n", + "Downloading httpx_aiohttp-0.1.9-py3-none-any.whl (6.2 kB)\n", + "Downloading jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (358 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m358.8/358.8 kB\u001b[0m \u001b[31m116.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading pygments-2.19.2-py3-none-any.whl (1.2 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.2/1.2 MB\u001b[0m \u001b[31m92.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading python_dotenv-1.2.1-py3-none-any.whl (21 kB)\n", + "Downloading rich_rst-1.3.2-py3-none-any.whl (12 kB)\n", + "Downloading ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (753 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m753.1/753.1 kB\u001b[0m \u001b[31m58.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading typing_inspection-0.4.2-py3-none-any.whl (14 kB)\n", + "Downloading future-1.0.0-py3-none-any.whl (491 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m491.3/491.3 kB\u001b[0m \u001b[31m76.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading linkify_it_py-2.0.3-py3-none-any.whl (19 kB)\n", + "Downloading docutils-0.22.2-py3-none-any.whl (632 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m632.7/632.7 kB\u001b[0m \u001b[31m124.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading mdit_py_plugins-0.5.0-py3-none-any.whl (57 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m57.2/57.2 kB\u001b[0m \u001b[31m34.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading uc_micro_py-1.0.3-py3-none-any.whl (6.2 kB)\n", + "Installing collected packages: uvloop, uc-micro-py, typing-inspection, setproctitle, ruamel.yaml.clib, pyzmq, python-dotenv, pygments, pydantic-core, psutil, pillow, jiter, future, docutils, docstring-parser, annotated-types, aiosignal, aiofiles, ruamel-yaml, rich, pydantic, mdit-py-plugins, linkify-it-py, ffmpeg-python, aiohttp, rich-rst, pydantic-settings, openai, httpx-aiohttp, textual, cyclopts, aiperf\n", + " Attempting uninstall: pyzmq\n", + " Found existing installation: pyzmq 27.1.0\n", + " Uninstalling pyzmq-27.1.0:\n", + " Successfully uninstalled pyzmq-27.1.0\n", + " Attempting uninstall: pygments\n", + " Found existing installation: Pygments 2.19.1\n", + " Uninstalling Pygments-2.19.1:\n", + " Successfully uninstalled Pygments-2.19.1\n", + " Attempting uninstall: psutil\n", + " Found existing installation: psutil 7.1.3\n", + " Uninstalling psutil-7.1.3:\n", + " Successfully uninstalled psutil-7.1.3\n", + " Attempting uninstall: pillow\n", + " Found existing installation: pillow 11.2.1\n", + " Uninstalling pillow-11.2.1:\n", + " Successfully uninstalled pillow-11.2.1\n", + " Attempting uninstall: aiosignal\n", + " Found existing installation: aiosignal 1.3.2\n", + " Uninstalling aiosignal-1.3.2:\n", + " Successfully uninstalled aiosignal-1.3.2\n", + " Attempting uninstall: rich\n", + " Found existing installation: rich 14.0.0\n", + " Uninstalling rich-14.0.0:\n", + " Successfully uninstalled rich-14.0.0\n", + " Attempting uninstall: aiohttp\n", + " Found existing installation: aiohttp 3.11.18\n", + " Uninstalling aiohttp-3.11.18:\n", + " Successfully uninstalled aiohttp-3.11.18\n", + "Successfully installed aiofiles-24.1.0 aiohttp-3.12.15 aiosignal-1.4.0 aiperf-0.2.0 annotated-types-0.7.0 cyclopts-3.24.0 docstring-parser-0.17.0 docutils-0.22.2 ffmpeg-python-0.2.0 future-1.0.0 httpx-aiohttp-0.1.9 jiter-0.11.1 linkify-it-py-2.0.3 mdit-py-plugins-0.5.0 openai-1.92.3 pillow-11.1.0 psutil-7.0.0 pydantic-2.11.10 pydantic-core-2.33.2 pydantic-settings-2.10.1 pygments-2.19.2 python-dotenv-1.2.1 pyzmq-26.4.0 rich-14.1.0 rich-rst-1.3.2 ruamel-yaml-0.18.16 ruamel.yaml.clib-0.2.14 setproctitle-1.3.7 textual-5.3.0 typing-inspection-0.4.2 uc-micro-py-1.0.3 uvloop-0.21.0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n", + "\u001b[0m" + ] + } + ], "source": [ "%%bash\n", "pip install aiperf" @@ -577,7 +777,7 @@ { "cell_type": "code", "execution_count": null, - "id": "a411717e-00ee-4a10-a274-7aa4caa9580d", + "id": "1b1645aa-28e9-45d1-9db9-b86af239e627", "metadata": {}, "outputs": [], "source": [] From 39eb0aaff166afb508e3fd1899fad261b4edd9f7 Mon Sep 17 00:00:00 2001 From: Vinh Nguyen Date: Fri, 14 Nov 2025 09:43:18 +1100 Subject: [PATCH 3/6] update triton container version Signed-off-by: Vinh Nguyen --- notebooks/TCO_calculator.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/TCO_calculator.ipynb b/notebooks/TCO_calculator.ipynb index 003b19c05..61df6b11f 100644 --- a/notebooks/TCO_calculator.ipynb +++ b/notebooks/TCO_calculator.ipynb @@ -13,7 +13,7 @@ "\n", "To execute this notebook, you can use the NVIDIA Triton server container:\n", "```\n", - "docker run --gpus=all --ipc=host --net=host --rm -it -v $PWD:/myworkspace nvcr.io/nvidia/tritonserver:25.05-py3-sdk bash \n", + "docker run --gpus=all --ipc=host --net=host --rm -it -v $PWD:/myworkspace nvcr.io/nvidia/tritonserver:25.09-py3-sdk bash \n", "```\n", "\n", "Then from within the docker interactive session:\n", From 20431b9191479ef4cdadee251de52711c37bbc8e Mon Sep 17 00:00:00 2001 From: Vinh Nguyen Date: Fri, 14 Nov 2025 09:48:35 +1100 Subject: [PATCH 4/6] Fix formatting in README.md Signed-off-by: Vinh Nguyen --- notebooks/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebooks/README.md b/notebooks/README.md index 61198791e..42bfca8f8 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -1,5 +1,5 @@ # AIPerf Utility Notebooks -This folder contains the various utility notebooks for AIPerf. +This folder contains the various utility notebooks for AIPerf. -1. [TCO_calculator.ipynb](TCO_calculator.ipynb): This notebook allows user to benchmark a NIM LLM deployment, then export the data to the NIM total cost of ownership (TCO) calculator. \ No newline at end of file +1. [TCO_calculator.ipynb](TCO_calculator.ipynb): This notebook allows user to benchmark a NIM LLM deployment, then export the data to the NIM total cost of ownership (TCO) calculator. From e6482993a79e0783e5262e0fccf24e2f17bd48b0 Mon Sep 17 00:00:00 2001 From: vinhn Date: Thu, 13 Nov 2025 23:18:38 +0000 Subject: [PATCH 5/6] clean up cell output --- notebooks/TCO_calculator.ipynb | 236 +-------------------------------- 1 file changed, 4 insertions(+), 232 deletions(-) diff --git a/notebooks/TCO_calculator.ipynb b/notebooks/TCO_calculator.ipynb index 61df6b11f..8d548d7ce 100644 --- a/notebooks/TCO_calculator.ipynb +++ b/notebooks/TCO_calculator.ipynb @@ -61,210 +61,10 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "ad5de6fe-8547-4259-956a-980aa8b71dce", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Collecting aiperf\n", - " Downloading aiperf-0.2.0-py3-none-any.whl.metadata (9.0 kB)\n", - "Collecting aiofiles~=24.1.0 (from aiperf)\n", - " Downloading aiofiles-24.1.0-py3-none-any.whl.metadata (10 kB)\n", - "Collecting aiohttp~=3.12.14 (from aiperf)\n", - " Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.7 kB)\n", - "Collecting cyclopts<4,>=3 (from aiperf)\n", - " Downloading cyclopts-3.24.0-py3-none-any.whl.metadata (11 kB)\n", - "Collecting ffmpeg-python~=0.2.0 (from aiperf)\n", - " Downloading ffmpeg_python-0.2.0-py3-none-any.whl.metadata (1.7 kB)\n", - "Requirement already satisfied: numpy~=1.26.4 in /usr/local/lib/python3.12/dist-packages (from aiperf) (1.26.4)\n", - "Collecting openai~=1.92.2 (from openai[aiohttp]~=1.92.2->aiperf)\n", - " Downloading openai-1.92.3-py3-none-any.whl.metadata (29 kB)\n", - "Requirement already satisfied: orjson~=3.10.18 in /usr/local/lib/python3.12/dist-packages (from aiperf) (3.10.18)\n", - "Collecting pillow~=11.1.0 (from aiperf)\n", - " Downloading pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (9.1 kB)\n", - "Requirement already satisfied: prometheus-client~=0.23.1 in /usr/local/lib/python3.12/dist-packages (from aiperf) (0.23.1)\n", - "Collecting psutil~=7.0.0 (from aiperf)\n", - " Downloading psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (22 kB)\n", - "Collecting pydantic-settings~=2.10.0 (from aiperf)\n", - " Downloading pydantic_settings-2.10.1-py3-none-any.whl.metadata (3.4 kB)\n", - "Collecting pydantic~=2.11.4 (from aiperf)\n", - " Downloading pydantic-2.11.10-py3-none-any.whl.metadata (68 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m68.6/68.6 kB\u001b[0m \u001b[31m4.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hCollecting pyzmq~=26.4.0 (from aiperf)\n", - " Downloading pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl.metadata (6.0 kB)\n", - "Collecting rich~=14.1.0 (from aiperf)\n", - " Downloading rich-14.1.0-py3-none-any.whl.metadata (18 kB)\n", - "Collecting ruamel-yaml~=0.18.12 (from aiperf)\n", - " Downloading ruamel.yaml-0.18.16-py3-none-any.whl.metadata (25 kB)\n", - "Collecting setproctitle~=1.3.6 (from aiperf)\n", - " Downloading setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (10 kB)\n", - "Requirement already satisfied: soundfile~=0.13.1 in /usr/local/lib/python3.12/dist-packages (from aiperf) (0.13.1)\n", - "Collecting textual~=5.3.0 (from aiperf)\n", - " Downloading textual-5.3.0-py3-none-any.whl.metadata (9.1 kB)\n", - "Requirement already satisfied: tqdm>=4.67.1 in /usr/local/lib/python3.12/dist-packages (from aiperf) (4.67.1)\n", - "Requirement already satisfied: transformers>=4.52.0 in /usr/local/lib/python3.12/dist-packages (from aiperf) (4.52.2)\n", - "Collecting uvloop~=0.21.0 (from aiperf)\n", - " Downloading uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)\n", - "Requirement already satisfied: aiohappyeyeballs>=2.5.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (2.6.1)\n", - "Collecting aiosignal>=1.4.0 (from aiohttp~=3.12.14->aiperf)\n", - " Downloading aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)\n", - "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (25.3.0)\n", - "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (1.6.0)\n", - "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (6.4.4)\n", - "Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (0.3.1)\n", - "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/lib/python3.12/dist-packages (from aiohttp~=3.12.14->aiperf) (1.20.0)\n", - "Collecting docstring-parser>=0.15 (from cyclopts<4,>=3->aiperf)\n", - " Downloading docstring_parser-0.17.0-py3-none-any.whl.metadata (3.5 kB)\n", - "Collecting rich-rst<2.0.0,>=1.3.1 (from cyclopts<4,>=3->aiperf)\n", - " Downloading rich_rst-1.3.2-py3-none-any.whl.metadata (6.1 kB)\n", - "Collecting future (from ffmpeg-python~=0.2.0->aiperf)\n", - " Downloading future-1.0.0-py3-none-any.whl.metadata (4.0 kB)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (4.11.0)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (1.9.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (0.28.1)\n", - "Collecting jiter<1,>=0.4.0 (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf)\n", - " Downloading jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.2 kB)\n", - "Requirement already satisfied: sniffio in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (1.3.1)\n", - "Requirement already satisfied: typing-extensions<5,>=4.11 in /usr/local/lib/python3.12/dist-packages (from openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (4.13.2)\n", - "Collecting httpx-aiohttp>=0.1.6 (from openai[aiohttp]~=1.92.2->aiperf)\n", - " Downloading httpx_aiohttp-0.1.9-py3-none-any.whl.metadata (2.8 kB)\n", - "Collecting annotated-types>=0.6.0 (from pydantic~=2.11.4->aiperf)\n", - " Downloading annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)\n", - "Collecting pydantic-core==2.33.2 (from pydantic~=2.11.4->aiperf)\n", - " Downloading pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.8 kB)\n", - "Collecting typing-inspection>=0.4.0 (from pydantic~=2.11.4->aiperf)\n", - " Downloading typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)\n", - "Collecting python-dotenv>=0.21.0 (from pydantic-settings~=2.10.0->aiperf)\n", - " Downloading python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB)\n", - "Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.12/dist-packages (from rich~=14.1.0->aiperf) (3.0.0)\n", - "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.12/dist-packages (from rich~=14.1.0->aiperf) (2.19.1)\n", - "Collecting ruamel.yaml.clib>=0.2.7 (from ruamel-yaml~=0.18.12->aiperf)\n", - " Downloading ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.0 kB)\n", - "Requirement already satisfied: cffi>=1.0 in /usr/local/lib/python3.12/dist-packages (from soundfile~=0.13.1->aiperf) (1.17.1)\n", - "Requirement already satisfied: platformdirs<5,>=3.6.0 in /usr/local/lib/python3.12/dist-packages (from textual~=5.3.0->aiperf) (4.5.0)\n", - "Collecting pygments<3.0.0,>=2.13.0 (from rich~=14.1.0->aiperf)\n", - " Downloading pygments-2.19.2-py3-none-any.whl.metadata (2.5 kB)\n", - "Requirement already satisfied: filelock in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (3.18.0)\n", - "Requirement already satisfied: huggingface-hub<1.0,>=0.30.0 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (0.31.4)\n", - "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (25.0)\n", - "Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (6.0.2)\n", - "Requirement already satisfied: regex!=2019.12.17 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (2024.11.6)\n", - "Requirement already satisfied: requests in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (2.32.3)\n", - "Requirement already satisfied: tokenizers<0.22,>=0.21 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (0.21.1)\n", - "Requirement already satisfied: safetensors>=0.4.3 in /usr/local/lib/python3.12/dist-packages (from transformers>=4.52.0->aiperf) (0.5.3)\n", - "Requirement already satisfied: idna>=2.8 in /usr/local/lib/python3.12/dist-packages (from anyio<5,>=3.5.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (3.10)\n", - "Requirement already satisfied: pycparser in /usr/local/lib/python3.12/dist-packages (from cffi>=1.0->soundfile~=0.13.1->aiperf) (2.22)\n", - "Requirement already satisfied: certifi in /usr/local/lib/python3.12/dist-packages (from httpx<1,>=0.23.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (2025.4.26)\n", - "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.12/dist-packages (from httpx<1,>=0.23.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (1.0.9)\n", - "Requirement already satisfied: h11>=0.16 in /usr/local/lib/python3.12/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai~=1.92.2->openai[aiohttp]~=1.92.2->aiperf) (0.16.0)\n", - "Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.12/dist-packages (from huggingface-hub<1.0,>=0.30.0->transformers>=4.52.0->aiperf) (2025.5.0)\n", - "Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.12/dist-packages (from markdown-it-py>=2.2.0->rich~=14.1.0->aiperf) (0.1.2)\n", - "Collecting linkify-it-py<3,>=1 (from markdown-it-py[linkify,plugins]>=2.1.0->textual~=5.3.0->aiperf)\n", - " Downloading linkify_it_py-2.0.3-py3-none-any.whl.metadata (8.5 kB)\n", - "Collecting mdit-py-plugins (from markdown-it-py[linkify,plugins]>=2.1.0->textual~=5.3.0->aiperf)\n", - " Downloading mdit_py_plugins-0.5.0-py3-none-any.whl.metadata (2.8 kB)\n", - "Collecting docutils (from rich-rst<2.0.0,>=1.3.1->cyclopts<4,>=3->aiperf)\n", - " Downloading docutils-0.22.2-py3-none-any.whl.metadata (15 kB)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests->transformers>=4.52.0->aiperf) (3.4.2)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests->transformers>=4.52.0->aiperf) (2.4.0)\n", - "Collecting uc-micro-py (from linkify-it-py<3,>=1->markdown-it-py[linkify,plugins]>=2.1.0->textual~=5.3.0->aiperf)\n", - " Downloading uc_micro_py-1.0.3-py3-none-any.whl.metadata (2.0 kB)\n", - "Downloading aiperf-0.2.0-py3-none-any.whl (2.9 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.9/2.9 MB\u001b[0m \u001b[31m10.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m \u001b[36m0:00:01\u001b[0mm\n", - "\u001b[?25hDownloading aiofiles-24.1.0-py3-none-any.whl (15 kB)\n", - "Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.7 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.7/1.7 MB\u001b[0m \u001b[31m37.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", - "\u001b[?25hDownloading cyclopts-3.24.0-py3-none-any.whl (86 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m86.2/86.2 kB\u001b[0m \u001b[31m49.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading ffmpeg_python-0.2.0-py3-none-any.whl (25 kB)\n", - "Downloading openai-1.92.3-py3-none-any.whl (753 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m753.4/753.4 kB\u001b[0m \u001b[31m37.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl (4.5 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.5/4.5 MB\u001b[0m \u001b[31m55.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", - "\u001b[?25hDownloading psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (277 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m278.0/278.0 kB\u001b[0m \u001b[31m79.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading pydantic-2.11.10-py3-none-any.whl (444 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m444.8/444.8 kB\u001b[0m \u001b[31m84.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading pydantic_core-2.33.2-cp312-cp312-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[31m89.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading pydantic_settings-2.10.1-py3-none-any.whl (45 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m45.2/45.2 kB\u001b[0m \u001b[31m27.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl (855 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m855.9/855.9 kB\u001b[0m \u001b[31m86.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading rich-14.1.0-py3-none-any.whl (243 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m243.4/243.4 kB\u001b[0m \u001b[31m66.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading ruamel.yaml-0.18.16-py3-none-any.whl (119 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m119.9/119.9 kB\u001b[0m \u001b[31m68.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (32 kB)\n", - "Downloading textual-5.3.0-py3-none-any.whl (702 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m702.7/702.7 kB\u001b[0m \u001b[31m99.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.7 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.7/4.7 MB\u001b[0m \u001b[31m87.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", - "\u001b[?25hDownloading aiosignal-1.4.0-py3-none-any.whl (7.5 kB)\n", - "Downloading annotated_types-0.7.0-py3-none-any.whl (13 kB)\n", - "Downloading docstring_parser-0.17.0-py3-none-any.whl (36 kB)\n", - "Downloading httpx_aiohttp-0.1.9-py3-none-any.whl (6.2 kB)\n", - "Downloading jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (358 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m358.8/358.8 kB\u001b[0m \u001b[31m116.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading pygments-2.19.2-py3-none-any.whl (1.2 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.2/1.2 MB\u001b[0m \u001b[31m92.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading python_dotenv-1.2.1-py3-none-any.whl (21 kB)\n", - "Downloading rich_rst-1.3.2-py3-none-any.whl (12 kB)\n", - "Downloading ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (753 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m753.1/753.1 kB\u001b[0m \u001b[31m58.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading typing_inspection-0.4.2-py3-none-any.whl (14 kB)\n", - "Downloading future-1.0.0-py3-none-any.whl (491 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m491.3/491.3 kB\u001b[0m \u001b[31m76.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading linkify_it_py-2.0.3-py3-none-any.whl (19 kB)\n", - "Downloading docutils-0.22.2-py3-none-any.whl (632 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m632.7/632.7 kB\u001b[0m \u001b[31m124.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading mdit_py_plugins-0.5.0-py3-none-any.whl (57 kB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m57.2/57.2 kB\u001b[0m \u001b[31m34.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hDownloading uc_micro_py-1.0.3-py3-none-any.whl (6.2 kB)\n", - "Installing collected packages: uvloop, uc-micro-py, typing-inspection, setproctitle, ruamel.yaml.clib, pyzmq, python-dotenv, pygments, pydantic-core, psutil, pillow, jiter, future, docutils, docstring-parser, annotated-types, aiosignal, aiofiles, ruamel-yaml, rich, pydantic, mdit-py-plugins, linkify-it-py, ffmpeg-python, aiohttp, rich-rst, pydantic-settings, openai, httpx-aiohttp, textual, cyclopts, aiperf\n", - " Attempting uninstall: pyzmq\n", - " Found existing installation: pyzmq 27.1.0\n", - " Uninstalling pyzmq-27.1.0:\n", - " Successfully uninstalled pyzmq-27.1.0\n", - " Attempting uninstall: pygments\n", - " Found existing installation: Pygments 2.19.1\n", - " Uninstalling Pygments-2.19.1:\n", - " Successfully uninstalled Pygments-2.19.1\n", - " Attempting uninstall: psutil\n", - " Found existing installation: psutil 7.1.3\n", - " Uninstalling psutil-7.1.3:\n", - " Successfully uninstalled psutil-7.1.3\n", - " Attempting uninstall: pillow\n", - " Found existing installation: pillow 11.2.1\n", - " Uninstalling pillow-11.2.1:\n", - " Successfully uninstalled pillow-11.2.1\n", - " Attempting uninstall: aiosignal\n", - " Found existing installation: aiosignal 1.3.2\n", - " Uninstalling aiosignal-1.3.2:\n", - " Successfully uninstalled aiosignal-1.3.2\n", - " Attempting uninstall: rich\n", - " Found existing installation: rich 14.0.0\n", - " Uninstalling rich-14.0.0:\n", - " Successfully uninstalled rich-14.0.0\n", - " Attempting uninstall: aiohttp\n", - " Found existing installation: aiohttp 3.11.18\n", - " Uninstalling aiohttp-3.11.18:\n", - " Successfully uninstalled aiohttp-3.11.18\n", - "Successfully installed aiofiles-24.1.0 aiohttp-3.12.15 aiosignal-1.4.0 aiperf-0.2.0 annotated-types-0.7.0 cyclopts-3.24.0 docstring-parser-0.17.0 docutils-0.22.2 ffmpeg-python-0.2.0 future-1.0.0 httpx-aiohttp-0.1.9 jiter-0.11.1 linkify-it-py-2.0.3 mdit-py-plugins-0.5.0 openai-1.92.3 pillow-11.1.0 psutil-7.0.0 pydantic-2.11.10 pydantic-core-2.33.2 pydantic-settings-2.10.1 pygments-2.19.2 python-dotenv-1.2.1 pyzmq-26.4.0 rich-14.1.0 rich-rst-1.3.2 ruamel-yaml-0.18.16 ruamel.yaml.clib-0.2.14 setproctitle-1.3.7 textual-5.3.0 typing-inspection-0.4.2 uc-micro-py-1.0.3 uvloop-0.21.0\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n", - "\u001b[0m" - ] - } - ], + "outputs": [], "source": [ "%%bash\n", "pip install aiperf" @@ -698,38 +498,10 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "5baf8e86-c8d1-42fc-94d3-15b592a5adc9", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/dill-0.3.9-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/texttable-1.7.0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/opt_einsum-3.4.0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/igraph-0.11.8-py3.12-linux-x86_64.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/nvfuser-0.2.13a0+0d33366-py3.12-linux-x86_64.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/lightning_thunder-0.2.0.dev0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/lightning_utilities-0.11.9-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0m\u001b[33mDEPRECATION: Loading egg at /usr/local/lib/python3.12/dist-packages/looseversion-1.3.0-py3.12.egg is deprecated. pip 25.1 will enforce this behaviour change. A possible replacement is to use pip for package installation. Discussion can be found at https://github.com/pypa/pip/issues/12330\u001b[0m\u001b[33m\n", - "\u001b[0mLooking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com\n", - "Collecting openpyxl\n", - " Downloading openpyxl-3.1.5-py2.py3-none-any.whl.metadata (2.5 kB)\n", - "Collecting et-xmlfile (from openpyxl)\n", - " Downloading et_xmlfile-2.0.0-py3-none-any.whl.metadata (2.7 kB)\n", - "Downloading openpyxl-3.1.5-py2.py3-none-any.whl (250 kB)\n", - "Downloading et_xmlfile-2.0.0-py3-none-any.whl (18 kB)\n", - "Installing collected packages: et-xmlfile, openpyxl\n", - "Successfully installed et-xmlfile-2.0.0 openpyxl-3.1.5\n", - "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable.It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.\u001b[0m\u001b[33m\n", - "\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.2\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpython -m pip install --upgrade pip\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ "!pip install openpyxl" ] From e5d248e5c5be2573d5374c985cee604aaa303bf9 Mon Sep 17 00:00:00 2001 From: vinhn Date: Thu, 13 Nov 2025 23:30:49 +0000 Subject: [PATCH 6/6] run precommit hook --- notebooks/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/notebooks/README.md b/notebooks/README.md index 42bfca8f8..61f8a3863 100644 --- a/notebooks/README.md +++ b/notebooks/README.md @@ -1,3 +1,7 @@ + # AIPerf Utility Notebooks This folder contains the various utility notebooks for AIPerf.