From 7cd8dbf8f4d4291c7f75634b4a49595d5d71e145 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Thu, 16 Mar 2023 13:47:03 -0400 Subject: [PATCH 001/149] added user guide --- examples/qa_server_config.yaml | 6 + user-guide/benchmarking.md | 191 ++++++++++++ user-guide/deepsparse-pipelines.md | 464 +++++++++++++++++++++++++++++ user-guide/deepsparse-server.md | 302 +++++++++++++++++++ user-guide/hardware-support.md | 21 ++ user-guide/scheduler.md | 63 ++++ 6 files changed, 1047 insertions(+) create mode 100644 examples/qa_server_config.yaml create mode 100644 user-guide/benchmarking.md create mode 100644 user-guide/deepsparse-pipelines.md create mode 100644 user-guide/deepsparse-server.md create mode 100644 user-guide/hardware-support.md create mode 100644 user-guide/scheduler.md diff --git a/examples/qa_server_config.yaml b/examples/qa_server_config.yaml new file mode 100644 index 0000000000..456f2fb196 --- /dev/null +++ b/examples/qa_server_config.yaml @@ -0,0 +1,6 @@ +loggers: + python: + +endpoints: + - task: question_answering + model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned95_obs_quant-none \ No newline at end of file diff --git a/user-guide/benchmarking.md b/user-guide/benchmarking.md new file mode 100644 index 0000000000..8ab43fa293 --- /dev/null +++ b/user-guide/benchmarking.md @@ -0,0 +1,191 @@ +--- +title: "Benchmarking" +metaTitle: "Benchmarking Performance with DeepSparse" +metaDescription: "Benchmarking Performance with DeepSparse" +index: 3000 +--- + +# DeepSparse Benchmark + +DeepSparse includes a Benchmarking utility that enables you to profile performance on your system. + + +`deepsparse.benchmark` is a command-line (CLI) tool for benchmarking DeepSparse with ONNX models. +The tool will parse the arguments, download/compile the network into the engine, generate input tensors, and +execute the model depending on the chosen scenario. By default, it will choose a multi-stream or asynchronous mode to optimize for throughput. + +## Installation Requirements + +Use of the DeepSparse Benchmarking utilities requires installation of the [DeepSparse Community](/get-started/install/deepsparse). + +## Quickstart + +To benchmark a dense BERT ONNX model fine-tuned on the SST2 dataset (which is identified by its SparseZoo stub), run: + +```bash +deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none +``` + +## Usage + +In most cases, good performance will be found in the default options so usage can be as simple as running the command with a SparseZoo model stub or your local ONNX model. +However, if you prefer to customize benchmarking for your personal use case, you can run `deepsparse.benchmark -h` or with `--help` to view your usage options: + +CLI Arguments: +```bash +$ deepsparse.benchmark --help + +> positional arguments: +> +> model_path Path to an ONNX model file or SparseZoo model stub. +> +> optional arguments: +> +> -h, --help show this help message and exit. +> +> -b BATCH_SIZE, --batch_size BATCH_SIZE +> The batch size to run the analysis for. Must be +> greater than 0. +> +> -shapes INPUT_SHAPES, --input_shapes INPUT_SHAPES +> Override the shapes of the inputs, i.e. -shapes +> "[1,2,3],[4,5,6],[7,8,9]" results in input0=[1,2,3] +> input1=[4,5,6] input2=[7,8,9]. +> +> -ncores NUM_CORES, --num_cores NUM_CORES +> The number of physical cores to run the analysis on, +> defaults to all physical cores available on the system. +> +> -s {async,sync,elastic}, --scenario {async,sync,elastic} +> Choose between using the async, sync and elastic +> scenarios. Sync and async are similar to the single- +> stream/multi-stream scenarios. Elastic is a newer +> scenario that behaves similarly to the async scenario +> but uses a different scheduling backend. Default value +> is async. +> +> -t TIME, --time TIME +> The number of seconds the benchmark will run. Default +> is 10 seconds. +> +> -w WARMUP_TIME, --warmup_time WARMUP_TIME +> The number of seconds the benchmark will warmup before +> running.Default is 2 seconds. +> +> -nstreams NUM_STREAMS, --num_streams NUM_STREAMS +> The number of streams that will submit inferences in +> parallel using async scenario. Default is +> automatically determined for given hardware and may be +> sub-optimal. +> +> -pin {none,core,numa}, --thread_pinning {none,core,numa} +> Enable binding threads to cores ('core' the default), +> threads to cores on sockets ('numa'), or disable +> ('none'). +> +> -e {deepsparse,onnxruntime}, --engine {deepsparse,onnxruntime} +> Inference engine backend to run eval on. Choices are +> 'deepsparse', 'onnxruntime'. Default is 'deepsparse'. +> +> -q, --quiet Lower logging verbosity. +> +> -x EXPORT_PATH, --export_path EXPORT_PATH +> Store results into a JSON file. +``` +**PRO TIP:** Save your benchmark results in a convenient JSON file. + +The following is an example CLI command for benchmarking an ONNX model from the SparseZoo and saving the results to a `benchmark.json` file: + +```bash +deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none -x benchmark.json +``` + +### Sample CLI Argument Configurations + +To run a sparse FP32 MobileNetV1 at batch size 16 for 10 seconds for throughput using 8 streams of requests, use: + +```bash +deepsparse.benchmark zoo:cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-moderate --batch_size 16 --time 10 --scenario async --num_streams 8 +``` + +To run a sparse quantized INT8 6-layer BERT at batch size 1 for latency, use: + +```bash +deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_quant_6layers-aggressive_96 --batch_size 1 --scenario sync +``` + +## Inference Scenarios + +### Synchronous (Single-stream) Scenario + +Set by the `--scenario sync` argument, the goal metric is latency per batch (ms/batch). This scenario submits a single inference request at a time to the engine, recording the time taken for a request to return an output. This mimics an edge deployment scenario. + +The latency value reported is the mean of all latencies recorded during the execution period for the given batch size. + +### Asynchronous (Multi-stream) Scenario + +Set by the `--scenario async` argument, the goal metric is throughput in items per second (i/s). This scenario submits `--num_streams` concurrent inference requests to the engine, recording the time taken for each request to return an output. This mimics a model server or bulk batch deployment scenario. + +The throughput value reported comes from measuring the number of finished inferences within the execution time and the batch size. + +### Example Benchmarking Output of Synchronous vs. Asynchronous + +**BERT 3-layer FP32 Sparse Throughput** + +There is no need to add a *scenario* argument since `async` is the default option: +```bash +$ deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 + +> [INFO benchmark_model.py:202 ] Thread pinning to cores enabled +> DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) +> [INFO benchmark_model.py:247 ] deepsparse.engine.Engine: +> onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx +> batch_size: 1 +> num_cores: 18 +> scheduler: Scheduler.multi_stream +> cpu_avx_type: avx512 +> cpu_vnni: False +> [INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] +> [INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] +> [INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] +> [INFO benchmark_model.py:264 ] num_streams default value chosen of 9. This requires tuning and may be sub-optimal +> [INFO benchmark_model.py:270 ] Starting 'async' performance measurements for 10 seconds +> Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 +> Batch Size: 1 +> Scenario: multistream +> Throughput (items/sec): 83.5037 +> Latency Mean (ms/batch): 107.3422 +> Latency Median (ms/batch): 107.0099 +> Latency Std (ms/batch): 12.4016 +> Iterations: 840 +``` + +**BERT 3-layer FP32 Sparse Latency** + +To select a *synchronous inference scenario*, add `-s sync`: + +```bash +$ deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -s sync + +> [INFO benchmark_model.py:202 ] Thread pinning to cores enabled +> DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) +> [INFO benchmark_model.py:247 ] deepsparse.engine.Engine: +> onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx +> batch_size: 1 +> num_cores: 18 +> scheduler: Scheduler.single_stream +> cpu_avx_type: avx512 +> cpu_vnni: False +> [INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] +> [INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] +> [INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] +> [INFO benchmark_model.py:270 ] Starting 'sync' performance measurements for 10 seconds +> Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 +> Batch Size: 1 +> Scenario: singlestream +> Throughput (items/sec): 62.1568 +> Latency Mean (ms/batch): 16.0732 +> Latency Median (ms/batch): 15.7850 +> Latency Std (ms/batch): 1.0427 +> Iterations: 622 +``` diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md new file mode 100644 index 0000000000..ea9604137f --- /dev/null +++ b/user-guide/deepsparse-pipelines.md @@ -0,0 +1,464 @@ +--- +title: "Pipelines" +metaTitle: "Deploying with DeepSparse Pipelines" +metaDescription: "Deploying with DeepSparse Pipelines" +index: 1000 +--- + +# DeepSparse Pipelines + +Pipelines are the default API for deploying a model with DeepSparse. + +Similar to Hugging Face Pipelines, DeepSparse Pipelines wrap inference with task-specific +pre- and post-processing, enabling you to pass raw data and recieve the predictions. + +## Quickstart + +Let's try a quick example of the Pipeline API. All we have to is pass a task and model to the +the `Pipeline.create` function, and then we can run inference on raw data using DeepSparse! + +This example creates a sentiment analysis Pipeline with a 90% pruned-quantized verion of BERT +from the SparseZoo. + +```python +from deepsparse import Pipeline + +# download and compile onnx, create pipeline +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +sentiment_analysis_pipeline = Pipeline.create( + task="sentiment-analysis", # name of the task + model_path=zoo_stub, # zoo stub or path to local onnx file +) + +# run inference +print(sentiment_analysis_pipeline("I love using DeepSparse Pipelines")) +# >>> labels=['positive'] scores=[0.9954759478569031] +``` + +In this case, we passed a SparseZoo stub as the model, which instructs the DeepSparse to download the +relevant ONNX file from the SparseZoo. To deploy your own model, pass a path to a `model.onnx` file or to a +folder containing `model.onnx` and supporting files (e.g., the Hugging Face `tokenizer.json` and `config.json`). + +## Supported Use Cases + +DeepSparse Pipelines support many common CV and NLP use cases out of the box. Follow the links below for +usage examples of each use case with Pipelines. + +**Computer Vision**: +- [Image Classification](/use-cases/image-classification/deploying): `task="image_classification"` +- [Object Detection](/use-cases/object-detection/deploying): `task="yolo"` +- [Instance Segmentation](/use-cases/instance-segmentation/deploying): `task="yolact"` + +**Natural Language Processing**: +- [Embedding Extraction](/use-cases/embedding-extraction): `task="transformers_embedding_extraction"` +- [Text Classification](/use-cases/use-cases/natural-language-processing/text-classification): `task="text-classification"` +- [Zero Shot Text Classification](/use-cases/use-cases/natural-language-processing/zero-shot-text-classification): `task="zero-shot-text-classification"` +- [Sentiment Analysis](/use-cases/use-cases/natural-language-processing/sentiment-analysis): `task="sentiment-analysis"` +- [Token Classification](/use-cases/use-cases/natural-language-processing/token-classification): `task="token-classification"` +- [Question Answering](/use-cases/use-cases/natural-language-processing/question-answering): `task="question-answering"` + +## Custom Use Case + +Beyond officially supported use cases, Pipelines can be extended to additional tasks via the +`CustomTaskPipeline`. + +`CustomTaskPipelines` are passed the following arguments: +- `model_path` - a SparseZoo stub or path to a local ONNX file +- `process_inputs_fn` - an optional function that handles pre-processing of input into a list +of numpy arrays that can be passed directly to the inference forward pass +- `process_outputs_fn` - an optional function that handles post-processing of the list of numpy arrays +that are the output of the engine forward pass + +Let's demonstrate an example of how we could replicate the functionality of the image classification +pipeline as a custom Pipeline. + +Download an image and ONNX file (a 95% pruned-quantized ResNet-50) for the demo: +``` +wget https://raw.githubusercontent.com/neuralmagic/docs/main/files-for-examples/use-cases/embedding-extraction/goldfish.jpg +sparsezoo.download zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none --save-dir ./resnet-50-pruned-quant +``` + +We can create a custom image classification Pipeline which returns the raw logits and class probabilities +for the 1000 ImageNet classes with the following: + +```python +from deepsparse.pipelines.custom_pipeline import CustomTaskPipeline +from torchvision import transforms +from PIL import Image +import numpy as np +import torch + +IMAGENET_RGB_MEANS = [0.485, 0.456, 0.406] +IMAGENET_RGB_STDS = [0.229, 0.224, 0.225] +preprocess_transforms = transforms.Compose([ + transforms.Resize(256), + transforms.CenterCrop(224), + transforms.ToTensor(), + transforms.Normalize(mean=IMAGENET_RGB_MEANS, std=IMAGENET_RGB_STDS), +]) + +def preprocess(img_file): + with open(img_file, "rb") as img_file: + img = Image.open(img_file) + img = img.convert("RGB") + img = preprocess_transforms(img) + batch = torch.stack([img]) + return [batch.numpy()] + +custom_pipeline = CustomTaskPipeline( + model_path="./resnet-50-pruned-quant/model.onnx", + process_inputs_fn=preprocess, +) + +scores, probs = custom_pipeline("goldfish.jpg") + +print(scores.shape) +print(probs.shape) +print(np.sum(probs)) +print(np.argmax(probs)) + +# >> (1,1000) +# >> (1,1000) +# >> ~1.00000 +# >> 1 << index of the goldfish class in ImageNet +``` + +## Pipeline Utilities + +Beyond supporting pre- and post-processing, Pipelines also offer additional utilities that simplify +the deployment process. + +### Batch Size + +The `batch_size` argument configures the batch size of the Pipeline, modifying the underlying ONNX graph for you. +The default is batch size 1, and but we can override to batch size 3 with the following: + +```python +from deepsparse import Pipeline + +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 3 +sentiment_analysis_pipeline = Pipeline.create( + task="sentiment-analysis", # name of the task + model_path=zoo_stub, # zoo stub or path to local onnx file + batch_size=batch_size # default is batch 1 +) + +sentences = [ + "I love DeepSparse Pipelines", + "I hated changing the batch size with my prior Deep Learning framework", + "DeepSparse makes it very easy to adjust the batch size" +] +output = sentiment_analysis_pipeline(sentences) +print(output) + +# >>> labels=['positive', 'negative', 'positive'] scores=[0.9969560503959656, 0.9964107871055603, 0.7127435207366943] +``` + +### Number of Cores + +The `num_cores` argument configures the number of physical cores used by DeepSparse. The default is None, which +instructs DeepSparse to use all physical cores available on the system. We can override to use only +one core with the following: + +```python +from deepsparse import Pipeline + +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +num_cores = 1 +sentiment_analysis_pipeline = Pipeline.create( + task="sentiment-analysis", # name of the task + model_path=zoo_stub, # zoo stub or path to local onnx file + num_cores=1 +) + +sentences = "I love how DeepSparse makes it easy to configure the number of cores used" + +output = sentiment_analysis_pipeline(sentences) +print(output) + +# >> labels=['positive'] scores=[0.9951152801513672] << but runs slower than if using all cores +``` + +### Multi-Stream Scheduling + +Schedulers handle the distribution of work across cores in parallel computation. +The goal of a good scheduler is to ensure that while work is available, cores aren’t sitting idle. +On the contrary, as long as parallel tasks are available, all cores should be kept busy. + +The default scheduler for DeepSparse is a "synchronous" scheduler, where DeepSparse allocates +as many resources as possible to each request. This format is optimizes per-request latency. + +However, DeepSparse also offers a "multi-stream" scheduler, which configures DeepSparse to +process multiple requests at the same time. In deployment scenarios with low batch sizes and +high core counts, using the "multi-stream" scheduler can increase throughput by allowing DeepSparse +to better saturate the cores. + +Let's walk through an example enabling the multi-stream capability with the Pipeline API. +This example was run on a 32 core AWS `c6i.16xlarge` instance. + +```python +from deepsparse import Pipeline +from deepsparse.cpu import cpu_details +import queue, threading, time, numpy + +num_cores = cpu_details()[0] +num_streams = num_cores // 4 + +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +input = "This multi stream idea is awesome for getting better throughput!" +batch_size = 1 + +single_stream_pipeline = Pipeline.create( + task="sentiment_analysis", + model_path=zoo_stub, + batch_size=batch_size, +) + +print(single_stream_pipeline(input)) +# >> labels=['positive'] scores=[0.9943772554397583] + +multi_stream_pipeline = Pipeline.create( + task="sentiment_analysis", + model_path=zoo_stub, + batch_size=batch_size, + scheduler="multi_stream", # use multi-stream scheduler + executor=num_streams # enable num_streams simultaneous streams +) + +print(multi_stream_pipeline(input)) +# >> labels=['positive'] scores=[0.9943772554397583] + +class ExecutorThread(threading.Thread): + def __init__(self, pipeline, input, time_queue, max_time): + super(ExecutorThread, self).__init__() + self._pipeline = pipeline + self._input = input + self._time_queue = time_queue + self._max_time = max_time + def iteration(self): + start = time.perf_counter() + output = self._pipeline(self._input) + end = time.perf_counter() + return start, end + def run(self): + while time.perf_counter() < self._max_time: + start, end = self.iteration() + self._time_queue.put([start, end]) + +def benchmark_performance(pipeline, num_threads): + seconds_to_run = 10.0 + time_queue = queue.Queue() # threadsafe + max_time = time.perf_counter() + seconds_to_run + + threads = [] + for _ in range(num_threads): + threads.append(ExecutorThread(multi_stream_pipeline, input, time_queue, max_time)) + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + batch_times = list(time_queue.queue) + first_start_time = min([b[0] for b in batch_times]) + last_end_time = max([b[1] for b in batch_times]) + total_time_executing = last_end_time - first_start_time + items_per_sec = (batch_size * len(batch_times)) / total_time_executing + batch_times_ms = [ + (batch_time[1] - batch_time[0]) * 1000 for batch_time in batch_times + ] + + print(f">> Throughput: {items_per_sec} items/sec") + print(f">> Median Latency: {numpy.median(batch_times_ms)} ms") + +print("\nMulti-Stream Performance:") +benchmark_performance(multi_stream_pipeline, num_streams) + +print("\nSingle-Stream Performance:") +benchmark_performance(single_stream_pipeline, 1) + +# Multi-Stream Performance: +# >> Throughput: 310.2743200261281 items/sec +# >> Median Latency: 25.716418500451255 ms + +# Single-Stream Performance: +# >> Throughput: 241.04375983869264 items/sec +# >> Median Latency: 4.130347000682377 ms +``` + +Note that the deepSparse.benchmark script reports much better numbers for multi-stream. +Perhaps something is going on with the pre-processing sharing resources? + +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 1 -s async -nstreams 8 + +Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +Batch Size: 1 +Scenario: async +Throughput (items/sec): 817.3063 +Latency Mean (ms/batch): 9.7701 +Latency Median (ms/batch): 9.7612 +Latency Std (ms/batch): 0.1710 +Iterations: 8176 + +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 1 -s async -nstreams 8 + +Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +Batch Size: 1 +Scenario: sync +Throughput (items/sec): 272.8186 +Latency Mean (ms/batch): 3.6599 +Latency Median (ms/batch): 3.5558 +Latency Std (ms/batch): 0.2366 +Iterations: 2729 + +### Dynamic Batch Size + +We can utilize an the multi-stream capabilites of DeepSparse to make requests with dynamic batch sizes. + +Let's create an example with a single sentiment analysis Pipeline with dynamic batch sizes by +setting the `batch_size` argument to None. Under the hood, the pipeline will split the batch into +multiple asynchronous requests using the multi-stream scheduler. + +```python +from deepsparse import Pipeline + +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +sentiment_analysis_pipeline = Pipeline.create( + task="sentiment_analysis", + model_path=zoo_stub, + batch_size=None, # setting to None enables dynamic batch +) + +b1_request = ["This multi model concept is great!"] +b4_request = b1_request * 4 + +output_b1 = sentiment_analysis_pipeline(b1_request) +output_b4 = sentiment_analysis_pipeline(b4_request) + +print(output_b1) +# >> labels=['positive'] scores=[0.9995297789573669] + +print(output_b4) +# >> labels=['positive', 'positive', 'positive', 'positive'] scores=[0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669] +``` + +### Deploy Multiple Models on the Same System + +Some deployment scenarios will require running multiple instances of DeepSparse on a single +machine. DeepSparse includes a concepts called Context. Contexts can be used to run multiple +models with the same scheduler, enabling DeepSparse to manage the resources of the system effectively, +keeping engines that are running different models from fighting over resources. + +Let's create an example with multiple sentiment analysis Pipelines, one with batch size 1 (for maximum latency) +and one with batch size 32 (for maximum throughput). + +```python +from concurrent.futures import ThreadPoolExecutor +from deepsparse.engine import Context +from deepsparse import Pipeline + +context = Context() +executor = ThreadPoolExecutor(max_workers=context.num_streams) + +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" + +sentiment_analysis_pipeline_b1 = Pipeline.create( + task="sentiment_analysis", + model_path=zoo_stub, + batch_size=1, + context=context, + executor=executor +) + +sentiment_analysis_pipeline_b32 = Pipeline.create( + task="sentiment_analysis", + model_path=zoo_stub, + batch_size=32, + context=context, + executor=executor +) + +b1_request = ["This multi model concept is great!"] +b32_request = b1_request * 32 + +output_b1 = sentiment_analysis_pipeline_b1(b1_request) +output_b32 = sentiment_analysis_pipeline_b32(b32_request) + +print(output_b1) +print(output_b32) + +# >> labels=['positive'] scores=[0.9995297789573669] +# >> labels=['positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive', 'positive'] scores=[0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669, 0.9995297789573669] +``` + +If you are deploying multiple models on a same system, you may want to answer multiple +requests concurrently. We can enable this but setting the `num_streams` argument in the Context argument. + +```python +from concurrent.futures import ThreadPoolExecutor +from deepsparse.engine import Context +from deepsparse.pipeline import Pipeline +import threading + +class ExecutorThread(threading.Thread): + def __init__(self, pipeline, input, iters=1): + super(ExecutorThread, self).__init__() + self.pipeline = pipeline + self.input = input + self.iters = iters + + def run(self): + for _ in range(self.iters): + output = self.pipeline(self.input) + print(output) + +num_concurrent_requests = 2 + +context = Context(num_streams=num_concurrent_requests) +executor = ThreadPoolExecutor(max_workers=context.num_streams) + +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" + +sentiment_analysis_pipeline_b1 = Pipeline.create( + task="sentiment_analysis", + model_path=zoo_stub, + batch_size=1, + context=context, + executor=executor +) + +sentiment_analysis_pipeline_b32 = Pipeline.create( + task="sentiment_analysis", + model_path=zoo_stub, + batch_size=32, + context=context, + executor=executor +) + +b1_request = ["This multi model concept is great!"] +b64_request = b1_request * 32 + +threads = [ + ExecutorThread(sentiment_analysis_pipeline_b1, input=b1_request, iters=64), + ExecutorThread(sentiment_analysis_pipeline_b32, input=b64_request, iters=1), +] + +for thread in threads: + thread.start() + +for thread in threads: + thread.join() + +# mutiple b=1 queries print results before the b=32 query returns +``` + +Note that requests will be execute in a FIFO manner, with a maximum of `num_concurrent_requests` running at once. +As a result, high traffic on one of your Pipelines can impact performance on the other Pipeline. If you prefer to +isolate your Pipelines, we recommend using an orchestration framework such as Docker and Kubernetes with +one DeepSparse Pipeline running in each container for proper process isolation. + +### Logging + +Stay tuned for documentation on enabling logging with DeepSparse. \ No newline at end of file diff --git a/user-guide/deepsparse-server.md b/user-guide/deepsparse-server.md new file mode 100644 index 0000000000..533c656c8d --- /dev/null +++ b/user-guide/deepsparse-server.md @@ -0,0 +1,302 @@ +--- +title: "Server" +metaTitle: "Deploying with DeepSparse Server" +metaDescription: "Deploying with DeepSparse Server" +index: 2000 +--- + +# DeepSparse Server + +DeepSparse Server wraps Pipelines with a REST API, making it easy to stand up a inference +serving endpoint running DeepSparse. + +## Quickstart + +DeepSparse Server is launched from the CLI. Just like DeepSparse Pipelines, all we +have to do is pass a task and a model. + +Spin up sentiment analysis endpoint with a 90% pruned-quantized BERT model: +```bash +deepsparse.server \ + --task sentiment-analysis \ + --model_path zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +``` + +In this case, we used a SparseZoo stub, which instructs the Server to download the relevant +ONNX file from the SparseZoo. To deploy your own model, pass a path to a `model.onnx` file or a +folder containing the `model.onnx` and supporting files (e.g., the Hugging Face `tokenizer.json` and `config.json`). + +Let's make a request over HTTP. Since the Server is a wrapper around Pipelines, +we can send raw data to the endpoint and recieve the post-processed predictions: + +```python +import requests +url = "http://localhost:5543/predict" +obj = {"sequences": "I love querying DeepSparse over HTTP!"} +print(requests.post(url, json=obj).text) + +# >>> {"labels":["positive"],"scores":[0.9909943342208862]} +``` + +For full usage, run: +```bash +deepsparse.server --help +``` + +## Supported Use Cases + +DeepSparse Server supports all tasks available in DeepSparse Pipelines. Follow the links below +for usage examples of each task. + +**Computer Vision**: +- [Image Classification](/use-cases/image-classification/deploying): `task="image_classification"` +- [Object Detection](/use-cases/object-detection/deploying): `task="yolo"` +- [Instance Segmentation](/use-cases/instance-segmentation/deploying): `task="yolact"` + +**Natural Language Processing**: +- [Embedding Extraction](/use-cases/embedding-extraction): `task="transformers_embedding_extraction"` +- [Text Classification](/use-cases/use-cases/natural-language-processing/text-classification): `task="text-classification"` +- [Zero Shot Text Classification](/use-cases/use-cases/natural-language-processing/zero-shot-text-classification): `task="zero-shot-text-classification"` +- [Sentiment Analysis](/use-cases/use-cases/natural-language-processing/sentiment-analysis): `task="sentiment-analysis"` +- [Token Classification](/use-cases/use-cases/natural-language-processing/token-classification): `task="token-classification"` +- [Question Answering](/use-cases/use-cases/natural-language-processing/question-answering): `task="question-answering"` + +## Swagger UI + +FastAPI's Swagger UI enables you to view your Server's routes and to make sample requests. Navigate to the `/docs` +route (e.g., `http://localhost:5543/docs`) to try it out. + +

+ +

+ +## Server Configuration + +You can configure DeepSparse Server via YAML files. + +### Basic Example + +Let's walk through a basic example deploying via a configuration file. + +The following creates an endpoint running a 90% pruned-quantized version of +BERT trained on the SST2 dataset for the sentiment analysis task. + +```yaml +# config.yaml +endpoints: + - task: sentiment-analysis + model: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +``` + +We can then spin up with the `--config-file` argument: + +```bash +deepsparse.server \ + --config-file config.yaml +``` + +Sending a request: +``` +import requests +url = "http://localhost:5543/predict" +obj = {"sequences": "I love querying DeepSparse launched from a config file!"} +print(requests.post(url, json=obj).text) + +# >>> {"labels":["positive"],"scores":[0.9136188626289368]} +``` + +### Server Level Options + +At the server level, there are a few arguments that can be toggled. + +#### Physical Resources +`num_cores` specifies the number of cores that DeepSparse runs on. By default, +DeepSparse runs on all available cores. + +#### Scheduler +`num_workers` configures DeepSparse's scheduler. + +If `num_workers = 1` (the default), DeepSparse uses its "synchronous" scheduler, which allocates as many resources as possible +to each request. This format is optimizes per-request latency. By setting `num_workers > 1`, DeepSparse +utilizes its multi-stream scheduler, which processes multiple requests at the same time. +In deployment scenarios with low batch sizes and high core counts, using the "multi-stream" scheduler +can increase throughput by allowing DeepSparse to better saturate the cores. + +#### Thread Pinning +engine_thread_pinning + +TBU + +#### PyTorch Threads +pytorch_num_threads + +TBU + +The following configuration creates a Server with DeepSparse running on 2 cores, with 2 input streams, +DeepSparse threads pinned to cores, and PyTorch provided with 2 threads. + +```yaml +# server-level-options-config.yaml +num_cores: 2 +num_workers: 2 +engine_thread_pinning: core +pytorch_num_threads: 2 + +endpoints: + - task: sentiment-analysis + model: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +``` + +We can also adjust the port by providing the `--port` argument. + +Spinning up: +```bash +deepsparse.server \ + --config-file server-level-options-config.yaml \ + --port 5555 +``` + +We can then query the Server with the same pattern, querying on port 5555: +``` +import requests +url = "http://localhost:5555/predict" +obj = {"sequences": "I love querying DeepSparse launched from a config file!"} +print(requests.post(url, json=obj).text) + +# >>> {"labels":["positive"],"scores":[0.9136188626289368]} +``` + +### Multiple Endpoints + +To serve multiple models from the same context, we can add an additional endpoint +to the server configuration file. + +Here is an example which stands up two sentiment analysis endpoints, one using a +dense unoptimized BERT and one using a 90% pruned-quantized BERT. + +```yaml +# multiple-endpoint-config.yaml +endpoints: + - task: sentiment-analysis + model: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none + route: /sparse/predict + name: sparse-sentiment-analysis + + - task: sentiment-analysis + model: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none + route: /dense/predict + name: dense-sentiment-analysis +``` + +Spinning up: +```bash +deepsparse.server \ + --config-file multiple-endpoint-config.yaml +``` + +Making a request: +```python +import requests + +obj = {"sequences": "I love querying the multi-model server!"} + +sparse_url = "http://localhost:5543/sparse/predict" +print(f"From the sparse model: {requests.post(sparse_url, json=obj).text}") + +dense_url = "http://localhost:5543/dense/predict" +print(f"From the dense model: {requests.post(dense_url, json=obj).text}") + +# >>> From the sparse model: {"labels":["positive"],"scores":[0.9942120313644409]} +# >>> From the dense model: {"labels":["positive"],"scores":[0.998753547668457]} +``` + +### Endpoint Level Configuration + +We can also configure properties of each endpoint, including task specific +arguments from within the YAML file. + +For instance, the following configuration file creates two endpoints. + +The first is a text classification endpoint, using a 90% pruned-quantized BERT model trained on +IMDB for document classification (which means the model is tuned to classify long +sequence lengths). We configure this endpoint with batch size 1 and sequence length +of 512. Since sequence length is a task-specific argument used only in Transformers Pipelines, +we will pass this in `kwargs` in the YAML file. + +The second is a sentiment analysis endpoint. We will use the default +sequence length (128) with batch size 3. + +```yaml +# advanced-endpoint-config.yaml + +endpoints: + - task: text-classification + model: zoo:nlp/document_classification/obert-base/pytorch/huggingface/imdb/pruned90_quant-none + route: /text-classification/predict + name: text-classification + batch_size: 1 + kwargs: + sequence_length: 512 # uses 512 sequence len (transformers pipeline specific) + top_k: 2 # returns top 2 scores (text-classification pipeline specific arg) + + - task: sentiment-analysis + model: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none + route: /sentiment-analysis/predict + name: sentiment-analysis + batch_size: 3 +``` + +Spinning up: +```bash +deepsparse.server \ + --config-file advanced-endpoint-config.yaml +``` + +Making requests: +```python +import requests + +# batch 1 +document_obj = {"sequences": "I love sci-fi and am willing to put up with a lot. Sci-fi movies/TV are usually underfunded, under-appreciated and misunderstood. \ + I tried to like this, I really did, but it is to good TV sci-fi as Babylon 5 is to Star Trek (the original). Silly prosthetics, cheap cardboard sets, \ + stilted dialogues, CG that doesn't match the background, and painfully one-dimensional characters cannot be overcome with a 'sci-fi' setting. (I'm sure \ + there are those of you out there who think Babylon 5 is good sci-fi TV. It's not. It's clichéd and uninspiring.) While US viewers might like emotion and \ + character development, sci-fi is a genre that does not take itself seriously (cf. Star Trek). It may treat important issues, yet not as a serious philosophy. \ + It's really difficult to care about the characters here as they are not simply foolish, just missing a spark of life. Their actions and reactions are wooden \ + and predictable, often painful to watch. The makers of Earth KNOW it's rubbish as they have to always say 'Gene Roddenberry's Earth...' otherwise people \ + would not continue watching. Roddenberry's ashes must be turning in their orbit as this dull, cheap, poorly edited (watching it without advert breaks \ + really brings this home) trudging Trabant of a show lumbers into space. Spoiler. So, kill off a main character. And then bring him back as another actor. \ + Jeeez! Dallas all over again."} + +# batch 3 +short_obj = {"sequences": [ + "I love how easy it is to configure DeepSparse Server!", + "It was very challenging to configure my old deep learning inference platform", + "YAML is the best format for configuring my infrastructure" +]} + +document_classification_url = "http://localhost:5543/text-classification/predict" +print(requests.post(document_classification_url, json=document_obj).text) + +sentiment_analysis_url = "http://localhost:5543/sentiment-analysis/predict" +print(requests.post(sentiment_analysis_url, json=short_obj).text) + +# >>> {"labels":[["0","1"]],"scores":[[0.9994900226593018,0.0005100301350466907]]} +# >>> {"labels":["positive","negative","positive"],"scores":[0.9665533900260925,0.9952980279922485,0.9939143061637878]} +``` + +Checkout the [use case pages](/use-cases) for detailed documentation on task-specific +arguments that can be applied to the Server via `kwargs`. + + +## Custom Use Cases + +Stay tuned for documentation on Using a custom DeepSparse Pipeline wihin the Server! + +## Logging + +Stay tuned for documentation on DeepSparse Logging! + +## Hot Reloading + +Stay tuned for documentation on Hot Reloading! \ No newline at end of file diff --git a/user-guide/hardware-support.md b/user-guide/hardware-support.md new file mode 100644 index 0000000000..cce339abca --- /dev/null +++ b/user-guide/hardware-support.md @@ -0,0 +1,21 @@ +--- +title: "Supported Hardware" +metaTitle: "Supported Hardware for DeepSparse" +metaDescription: "Supported Hardware for DeepSparse including CPU types and instruction sets" +index: 8000 +--- + +# Supported Hardware for DeepSparse + +With support for AVX2, AVX-512, and VNNI instruction sets, DeepSparse is validated to work on x86 Intel (Haswell generation and later) and AMD (Zen 2 and later) CPUs running Linux. +Mac and Windows require running Linux in a Docker or virtual machine. + +Here is a table detailing specific support for some algorithms over different microarchitectures: + +| x86 Extension | Microarchitectures | Kernel Sparsity | Sparse Quantization | +|:----------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------:|:-------------------:| +| [AMD AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX2) | [Zen 2,](https://en.wikipedia.org/wiki/Zen_2) [Zen 3](https://en.wikipedia.org/wiki/Zen_3) | optimized | emulated | +| [AMD AVX-512](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX-512) VNNI | [Zen 4](https://en.wikipedia.org/wiki/Zen_4) | optimized | optimized | +| [Intel AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX2) | [Haswell,](https://en.wikipedia.org/wiki/Haswell_%28microarchitecture%29) [Broadwell,](https://en.wikipedia.org/wiki/Broadwell_%28microarchitecture%29) and newer | optimized | emulated | +| [Intel AVX-512](https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512) | [Skylake](https://en.wikipedia.org/wiki/Skylake_%28microarchitecture%29), [Cannon Lake](https://en.wikipedia.org/wiki/Cannon_Lake_%28microarchitecture%29), and newer | optimized | emulated | +| [Intel AVX-512](https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512) VNNI (DL Boost) | [Cascade Lake](https://en.wikipedia.org/wiki/Cascade_Lake_%28microarchitecture%29), [Ice Lake](https://en.wikipedia.org/wiki/Ice_Lake_%28microprocessor%29), [Cooper Lake](https://en.wikipedia.org/wiki/Cooper_Lake_%28microarchitecture%29), [Tiger Lake](https://en.wikipedia.org/wiki/Tiger_Lake_%28microprocessor%29) | optimized | optimized | diff --git a/user-guide/scheduler.md b/user-guide/scheduler.md new file mode 100644 index 0000000000..ff2345dc3c --- /dev/null +++ b/user-guide/scheduler.md @@ -0,0 +1,63 @@ +--- +title: "Inference Types" +metaTitle: "Inference Types with DeepSparse Scheduler" +metaDescription: "Inference Types with DeepSparse Scheduler" +index: 9000 +--- + +# Inference Types with DeepSparse Scheduler + +This page explains the various settings for DeepSparse, which enable you to tune the performance to your workload. + +Schedulers are special system software, which handle the distribution of work across cores in parallel computation. +The goal of a good scheduler is to ensure that, while work is available, cores are not sitting idle. +On the contrary, as long as parallel tasks are available, all cores should be kept busy. + +## Single Stream (Default) +In most use cases, the default scheduler is the preferred choice when running inferences with DeepSparse. +The default scheduler is highly optimized for minimum per-request latency, using all of the system's resources provided to it on every request it gets. +Often, particularly when working with large batch sizes, the scheduler is able to distribute the workload of a single request across as many cores as it's provided. + +*Single-stream scheduling; requests execute serially by default:* +single stream diagram + +## Multi-Stream + +There are circumstances in which more cores does not imply better performance. If the computation can't be divided up to produce enough parallelism (while maximizing use of the CPU cache), then adding more cores simply adds more compute power with little to apply it to. + +An alternative, multi-stream scheduler is provided with the software. In cases where parallelism is low, sending multiple requests simultaneously can more adequately saturate the available cores. In other words, if speedup can't be achieved by adding more cores, then perhaps speedup can be achieved by adding more work. + +If increasing core count does not decrease latency, that's a strong indicator that parallelism is low in your particular model/batch-size combination. It may be that total throughput can be increased by making more requests simultaneously. Using the [deepsparse.engine.Scheduler API,](https://docs.neuralmagic.com/deepsparse/api/deepsparse.html) the multi-stream scheduler can be selected, and requests made by multiple Python threads will be handled concurrently. + +*Multi-stream scheduling; requests execute in parallel and may better utilize hardware resources:* +multi stream diagram + + + +Whereas the default scheduler will queue up requests made simultaneously and handle them serially, the multi-stream scheduler allows multiple requests to be run in parallel. The `num_streams` argument to the Engine/Context classes controls how the multi-streams scheduler partitions up the machine. Each stream maps to a contiguous set of hardware threads. By default, only one hyperthread per core is used. There is no sharing amongst the partitions and it is generally good practice to make sure the `num_streams` value evenly divides into your number of cores. By default `num_streams` is set to multiplex requests across L3 caches. + +Here's an example. Consider a machine with 2 sockets, each with 8 cores. In this case, the multi-stream scheduler will create two streams, one per socket by default. The first stream will contain cores 0-7 and the second stream will contain cores 8-15. + +Manually increasing `num_streams` to 3 will result in the following stream breakdown: threads 0-5 in the first stream, 6-10 in the second, and 11-15 in the last. This is problematic for our 2-socket system. The second stream (threads 6-10) is straddling both sockets, meaning that each request being serviced by that stream is going to incur a performance penalty each time one of its threads makes a remote memory access. The impact of this penalty will depend on the workload, but it will likely be significant. + +Manually increasing `num_streams` to 4 is interesting. Here's the stream breakdown: threads 0-3 in the first stream, 4-7 in the second, 8-11 in the third, and 12-15 in the fourth. Each stream is only making memory accesses that are local to its socket, which is good. However, the first two and last two streams are sharing the same L3 cache, which can result in worse performance due to cache thrashing. Depending on the workload, though, the performance gain from the increased parallelism may negate this penalty. + +The most common use cases for the multi-stream scheduler are where parallelism is low with respect to core count, and where requests need to be made asynchronously without time to batch them. Implementing a model server may fit such a scenario and be ideal for using multi-stream scheduling. + +## Enabling a Scheduler + +Depending on your engine execution strategy, enable one of these options by running: + +```python +engine = compile_model(model_path, scheduler="single_stream") +``` + +or: + +```python +engine = compile_model(model_path, scheduler="multi_stream", num_streams=None) # None is the default +``` + +or pass in the enum value directly, since` "multi_stream" == Scheduler.multi_stream`. + +By default, the scheduler will map to a single stream. From a818bce7b04fd7d158fa53fb17d594ad3d9f118e Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 13:48:34 -0400 Subject: [PATCH 002/149] Delete qa_server_config.yaml --- examples/qa_server_config.yaml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 examples/qa_server_config.yaml diff --git a/examples/qa_server_config.yaml b/examples/qa_server_config.yaml deleted file mode 100644 index 456f2fb196..0000000000 --- a/examples/qa_server_config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -loggers: - python: - -endpoints: - - task: question_answering - model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned95_obs_quant-none \ No newline at end of file From 0dfb6e1c8da0db492a7af487fd411ba6e8419128 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Thu, 16 Mar 2023 13:51:34 -0400 Subject: [PATCH 003/149] removed gatsby headers --- user-guide/benchmarking.md | 7 ------- user-guide/deepsparse-pipelines.md | 7 ------- user-guide/deepsparse-server.md | 7 ------- user-guide/hardware-support.md | 7 ------- user-guide/scheduler.md | 7 ------- 5 files changed, 35 deletions(-) diff --git a/user-guide/benchmarking.md b/user-guide/benchmarking.md index 8ab43fa293..a2e33622f0 100644 --- a/user-guide/benchmarking.md +++ b/user-guide/benchmarking.md @@ -1,10 +1,3 @@ ---- -title: "Benchmarking" -metaTitle: "Benchmarking Performance with DeepSparse" -metaDescription: "Benchmarking Performance with DeepSparse" -index: 3000 ---- - # DeepSparse Benchmark DeepSparse includes a Benchmarking utility that enables you to profile performance on your system. diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index ea9604137f..5818f2968f 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -1,10 +1,3 @@ ---- -title: "Pipelines" -metaTitle: "Deploying with DeepSparse Pipelines" -metaDescription: "Deploying with DeepSparse Pipelines" -index: 1000 ---- - # DeepSparse Pipelines Pipelines are the default API for deploying a model with DeepSparse. diff --git a/user-guide/deepsparse-server.md b/user-guide/deepsparse-server.md index 533c656c8d..2630ca60d9 100644 --- a/user-guide/deepsparse-server.md +++ b/user-guide/deepsparse-server.md @@ -1,10 +1,3 @@ ---- -title: "Server" -metaTitle: "Deploying with DeepSparse Server" -metaDescription: "Deploying with DeepSparse Server" -index: 2000 ---- - # DeepSparse Server DeepSparse Server wraps Pipelines with a REST API, making it easy to stand up a inference diff --git a/user-guide/hardware-support.md b/user-guide/hardware-support.md index cce339abca..398c17971a 100644 --- a/user-guide/hardware-support.md +++ b/user-guide/hardware-support.md @@ -1,10 +1,3 @@ ---- -title: "Supported Hardware" -metaTitle: "Supported Hardware for DeepSparse" -metaDescription: "Supported Hardware for DeepSparse including CPU types and instruction sets" -index: 8000 ---- - # Supported Hardware for DeepSparse With support for AVX2, AVX-512, and VNNI instruction sets, DeepSparse is validated to work on x86 Intel (Haswell generation and later) and AMD (Zen 2 and later) CPUs running Linux. diff --git a/user-guide/scheduler.md b/user-guide/scheduler.md index ff2345dc3c..836927b479 100644 --- a/user-guide/scheduler.md +++ b/user-guide/scheduler.md @@ -1,10 +1,3 @@ ---- -title: "Inference Types" -metaTitle: "Inference Types with DeepSparse Scheduler" -metaDescription: "Inference Types with DeepSparse Scheduler" -index: 9000 ---- - # Inference Types with DeepSparse Scheduler This page explains the various settings for DeepSparse, which enable you to tune the performance to your workload. From b0d3454cbbb4c14e1767cacfb871c7e95dd4dea7 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Thu, 16 Mar 2023 14:34:44 -0400 Subject: [PATCH 004/149] update benchmarking --- user-guide/benchmarking.md | 278 ++++++++++++++++++------------------- 1 file changed, 139 insertions(+), 139 deletions(-) diff --git a/user-guide/benchmarking.md b/user-guide/benchmarking.md index a2e33622f0..4698a1f90c 100644 --- a/user-guide/benchmarking.md +++ b/user-guide/benchmarking.md @@ -1,184 +1,184 @@ # DeepSparse Benchmark -DeepSparse includes a Benchmarking utility that enables you to profile performance on your system. +DeepSparse contains a CLI utitlity for benchmarking DeepSparse's performance with ONNX models. - -`deepsparse.benchmark` is a command-line (CLI) tool for benchmarking DeepSparse with ONNX models. -The tool will parse the arguments, download/compile the network into the engine, generate input tensors, and -execute the model depending on the chosen scenario. By default, it will choose a multi-stream or asynchronous mode to optimize for throughput. +The tool will parse the arguments, download/compile the network into the engine, generate input tensors, and execute the model depending on the chosen scenario. ## Installation Requirements -Use of the DeepSparse Benchmarking utilities requires installation of the [DeepSparse Community](/get-started/install/deepsparse). +Install DeepSparse with `pip`: + +```bash +pip install deepsparse[onnxruntime] +``` + +The following benchmarking numbers were run on an AWS `c6i.16xlarge` (32 core) instance. -## Quickstart +## Quickstart - Comparing Dense and Sparse Performance -To benchmark a dense BERT ONNX model fine-tuned on the SST2 dataset (which is identified by its SparseZoo stub), run: +Run the following to benchmark DeepSparse with a dense, unoptimized BERT ONNX model (from SparseZoo): ```bash -deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none -``` +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none --batch_size 64 -## Usage +>> INFO:deepsparse.benchmark.benchmark_model:Starting 'singlestream' performance measurements for 10 seconds +>> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none +>> Batch Size: 64 +>> Scenario: sync +>> Throughput (items/sec): 102.1683 +``` -In most cases, good performance will be found in the default options so usage can be as simple as running the command with a SparseZoo model stub or your local ONNX model. -However, if you prefer to customize benchmarking for your personal use case, you can run `deepsparse.benchmark -h` or with `--help` to view your usage options: +Run the following to benchmark DeepSparse with a 90% pruned and quantized BERT ONNX model (from SparseZoo): -CLI Arguments: ```bash -$ deepsparse.benchmark --help - -> positional arguments: -> -> model_path Path to an ONNX model file or SparseZoo model stub. -> -> optional arguments: -> -> -h, --help show this help message and exit. -> -> -b BATCH_SIZE, --batch_size BATCH_SIZE -> The batch size to run the analysis for. Must be -> greater than 0. -> -> -shapes INPUT_SHAPES, --input_shapes INPUT_SHAPES -> Override the shapes of the inputs, i.e. -shapes -> "[1,2,3],[4,5,6],[7,8,9]" results in input0=[1,2,3] -> input1=[4,5,6] input2=[7,8,9]. -> -> -ncores NUM_CORES, --num_cores NUM_CORES -> The number of physical cores to run the analysis on, -> defaults to all physical cores available on the system. -> -> -s {async,sync,elastic}, --scenario {async,sync,elastic} -> Choose between using the async, sync and elastic -> scenarios. Sync and async are similar to the single- -> stream/multi-stream scenarios. Elastic is a newer -> scenario that behaves similarly to the async scenario -> but uses a different scheduling backend. Default value -> is async. -> -> -t TIME, --time TIME -> The number of seconds the benchmark will run. Default -> is 10 seconds. -> -> -w WARMUP_TIME, --warmup_time WARMUP_TIME -> The number of seconds the benchmark will warmup before -> running.Default is 2 seconds. -> -> -nstreams NUM_STREAMS, --num_streams NUM_STREAMS -> The number of streams that will submit inferences in -> parallel using async scenario. Default is -> automatically determined for given hardware and may be -> sub-optimal. -> -> -pin {none,core,numa}, --thread_pinning {none,core,numa} -> Enable binding threads to cores ('core' the default), -> threads to cores on sockets ('numa'), or disable -> ('none'). -> -> -e {deepsparse,onnxruntime}, --engine {deepsparse,onnxruntime} -> Inference engine backend to run eval on. Choices are -> 'deepsparse', 'onnxruntime'. Default is 'deepsparse'. -> -> -q, --quiet Lower logging verbosity. -> -> -x EXPORT_PATH, --export_path EXPORT_PATH -> Store results into a JSON file. +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none --batch_size 64 + +>> INFO:deepsparse.benchmark.benchmark_model:Starting 'singlestream' performance measurements for 10 seconds +>> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +>> Batch Size: 64 +>> Scenario: sync +>> Throughput (items/sec): 889.1262 ``` -**PRO TIP:** Save your benchmark results in a convenient JSON file. -The following is an example CLI command for benchmarking an ONNX model from the SparseZoo and saving the results to a `benchmark.json` file: +Running the sparse model, DeepSparse achieves 889 items/second vs 102 items/second with the dense model. **This is an 8.7x speedup!** + +### Comparing to ONNX Runtime +The benchmarking utility also allows you to use ONNX Runtime as the inference runtime in order to compare against DeepSparse. + +Run the following to benchmark ORT with the dense, unoptimized BERT ONNX model (from SparseZoo): ```bash -deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none -x benchmark.json +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none --batch_size 64 --engine onnxruntime + +>> INFO:deepsparse.benchmark.benchmark_model:Starting 'singlestream' performance measurements for 10 seconds +>> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none +>> Batch Size: 64 +>> Scenario: sync +>> Throughput (items/sec): 64.3392 ``` -### Sample CLI Argument Configurations +Run the following to benchmark ORT with the dense, unoptimized BERT ONNX model (from SparseZoo): +```bash +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none --batch_size 64 --engine onnxruntime + +>> INFO:deepsparse.benchmark.benchmark_model:Starting 'singlestream' performance measurements for 10 seconds +>> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none +>> Batch Size: 64 +>> Scenario: sync +>> Throughput (items/sec): 55.1905 +``` + +We can see that ORT does not gain additional performance from sparsity like DeepSparse. Additionally, DeepSparse runs the dense model +faster than ORT at high batch sizes. + +All in all, in this example **DeepSparse is 13.8x faster than ONNX Runtime**! -To run a sparse FP32 MobileNetV1 at batch size 16 for 10 seconds for throughput using 8 streams of requests, use: +## Usage + +Run `deepsparse.benchmark -h` to see full command line arguments. + +### Pass Your Local ONNX Model + +Beyond passing SparseZoo stubs, you can also pass a local path to an ONNX file to DeepSparse. + +As an example, let's download an ONNX file from SparseZoo using the CLI to a local directory called `./yolov5-download`. ```bash -deepsparse.benchmark zoo:cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-moderate --batch_size 16 --time 10 --scenario async --num_streams 8 +sparsezoo.download zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none --save-dir yolov5-download ``` -To run a sparse quantized INT8 6-layer BERT at batch size 1 for latency, use: +We can pass a local ONNX file as follows: ```bash -deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_quant_6layers-aggressive_96 --batch_size 1 --scenario sync +deepsparse.benchmark yolov5-download/model.onnx +>> Original Model Path: yolov5-download/model.onnx +>> Batch Size: 1 +>> Scenario: sync +>> Throughput (items/sec): 219.7396 ``` -## Inference Scenarios +### Batch Sizes -### Synchronous (Single-stream) Scenario +We can adjust the batch size of the inference with `-b` or `--batch_size`. -Set by the `--scenario sync` argument, the goal metric is latency per batch (ms/batch). This scenario submits a single inference request at a time to the engine, recording the time taken for a request to return an output. This mimics an edge deployment scenario. +The following runs a 95% pruned-quantized version of ResNet-50 at batch size 1: +```bash +deepsparse.benchmark zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none --batch_size 1 -The latency value reported is the mean of all latencies recorded during the execution period for the given batch size. +>> Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none +>> Batch Size: 1 +>> Scenario: sync +>> Throughput (items/sec): 852.7742 +``` + +The following runs a 95% pruned-quantized version of ResNet-50 at batch size 64: +```bash +deepsparse.benchmark zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none --batch_size 64 + +>> Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none +>> Batch Size: 64 +>> Scenario: sync +>> Throughput (items/sec): 2456.9958 +``` -### Asynchronous (Multi-stream) Scenario +In general, DeepSparse is able to achieve better performance at higher batch sizes, especially on many core machines as it is better able to utilitize the underlying hardware and saturate all of the cores at high batch sizes. -Set by the `--scenario async` argument, the goal metric is throughput in items per second (i/s). This scenario submits `--num_streams` concurrent inference requests to the engine, recording the time taken for each request to return an output. This mimics a model server or bulk batch deployment scenario. +### Custom Input Shape -The throughput value reported comes from measuring the number of finished inferences within the execution time and the batch size. +We can adjust the input share of the inference with `-i` or `--input_shape`. This is generally useful for changing the size of input images or sequence length for NLP. -### Example Benchmarking Output of Synchronous vs. Asynchronous +Here's an example doing a BERT inference with sequence length 384 (vs 128 as above): -**BERT 3-layer FP32 Sparse Throughput** +```bash +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none --input_shape [1,384] + +>> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +>> Batch Size: 1 +>> Scenario: sync +>> Throughput (items/sec): 121.7578 +``` + +Here's an example doing a YOLOv5s inference with a 320x320 image (rather than 640x640 as above) -There is no need to add a *scenario* argument since `async` is the default option: ```bash -$ deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 - -> [INFO benchmark_model.py:202 ] Thread pinning to cores enabled -> DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) -> [INFO benchmark_model.py:247 ] deepsparse.engine.Engine: -> onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx -> batch_size: 1 -> num_cores: 18 -> scheduler: Scheduler.multi_stream -> cpu_avx_type: avx512 -> cpu_vnni: False -> [INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] -> [INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] -> [INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] -> [INFO benchmark_model.py:264 ] num_streams default value chosen of 9. This requires tuning and may be sub-optimal -> [INFO benchmark_model.py:270 ] Starting 'async' performance measurements for 10 seconds -> Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -> Batch Size: 1 -> Scenario: multistream -> Throughput (items/sec): 83.5037 -> Latency Mean (ms/batch): 107.3422 -> Latency Median (ms/batch): 107.0099 -> Latency Std (ms/batch): 12.4016 -> Iterations: 840 +deepsparse.benchmark zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none -i [1,3,320,320] + +>> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none +>> Batch Size: 1 +>> Scenario: sync +>> Throughput (items/sec): 615.7185 ``` -**BERT 3-layer FP32 Sparse Latency** +### Inference Scenarios -To select a *synchronous inference scenario*, add `-s sync`: +The default scenrio is synchronous inference. Set by the `--scenario sync` argument, the goal metric is latency per batch (ms/batch). This scenario submits a single inference request at a time to the engine, recording the time taken for a request to return an output. This mimics an edge deployment scenario. + +Additionally, DeepSparse offers asynchronous inference, where DeepSparse will allocate resources to handle multiple inferences at once. Set by the `--scenario async` argument. This scenario submits `--num_streams` concurrent inference requests to the engine. This mimics a model server deployment scenario. + +Here's an example handling 8 concurrent batch 1 inferences: + +```bash +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none --scenario async --num_streams 8 + +>> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +>> Batch Size: 1 +>> Scenario: async +>> Throughput (items/sec): 807.3410 +>> Latency Mean (ms/batch): 9.8906 +``` + +Here's an example handling one batch 1 inference at a time with the same model: ```bash -$ deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -s sync - -> [INFO benchmark_model.py:202 ] Thread pinning to cores enabled -> DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) -> [INFO benchmark_model.py:247 ] deepsparse.engine.Engine: -> onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx -> batch_size: 1 -> num_cores: 18 -> scheduler: Scheduler.single_stream -> cpu_avx_type: avx512 -> cpu_vnni: False -> [INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] -> [INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] -> [INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] -> [INFO benchmark_model.py:270 ] Starting 'sync' performance measurements for 10 seconds -> Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -> Batch Size: 1 -> Scenario: singlestream -> Throughput (items/sec): 62.1568 -> Latency Mean (ms/batch): 16.0732 -> Latency Median (ms/batch): 15.7850 -> Latency Std (ms/batch): 1.0427 -> Iterations: 622 +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none --scenario sync + +>> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +>> Batch Size: 1 +>> Scenario: sync +>> Throughput (items/sec): 269.6001 +>> Latency Mean (ms/batch): 3.7041 ``` + +We can see that the async scenario achieves higher throughput, while the synchronous scenario achieves lower latency. + +Especially for very high core counts, using the asynchronous scheduler is a great way to improve performance if running at low batch sizes. \ No newline at end of file From 6124994c926c9dc2193d0690f667dc191ac0a3e1 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:41:01 -0400 Subject: [PATCH 005/149] Update benchmarking.md --- user-guide/benchmarking.md | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/user-guide/benchmarking.md b/user-guide/benchmarking.md index 4698a1f90c..33de9c29a4 100644 --- a/user-guide/benchmarking.md +++ b/user-guide/benchmarking.md @@ -1,8 +1,6 @@ # DeepSparse Benchmark -DeepSparse contains a CLI utitlity for benchmarking DeepSparse's performance with ONNX models. - -The tool will parse the arguments, download/compile the network into the engine, generate input tensors, and execute the model depending on the chosen scenario. +This page explains how to use DeepSparse's CLI utilties for benchmarking performance in a variety of scenarios. ## Installation Requirements @@ -12,11 +10,13 @@ Install DeepSparse with `pip`: pip install deepsparse[onnxruntime] ``` -The following benchmarking numbers were run on an AWS `c6i.16xlarge` (32 core) instance. +The benchmarking numbers were achieved on an AWS `c6i.16xlarge` (32 core) instance. + +## Quickstart -## Quickstart - Comparing Dense and Sparse Performance +Let's compare DeepSparse's performance with dense and sparse models. -Run the following to benchmark DeepSparse with a dense, unoptimized BERT ONNX model (from SparseZoo): +Run the following to benchmark DeepSparse with a [dense, unoptimized BERT ONNX model](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fbase-none): ```bash deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none --batch_size 64 @@ -28,7 +28,7 @@ deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/s >> Throughput (items/sec): 102.1683 ``` -Run the following to benchmark DeepSparse with a 90% pruned and quantized BERT ONNX model (from SparseZoo): +Run the following to benchmark DeepSparse with a [90% pruned and quantized BERT ONNX model](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none): ```bash deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none --batch_size 64 @@ -44,9 +44,9 @@ Running the sparse model, DeepSparse achieves 889 items/second vs 102 items/seco ### Comparing to ONNX Runtime -The benchmarking utility also allows you to use ONNX Runtime as the inference runtime in order to compare against DeepSparse. +The benchmarking utility also allows you to use ONNX Runtime as the inference runtime by passing `--engine onnxruntime`. -Run the following to benchmark ORT with the dense, unoptimized BERT ONNX model (from SparseZoo): +Run the following to benchmark ORT with the same dense, unoptimized BERT ONNX model as above: ```bash deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none --batch_size 64 --engine onnxruntime @@ -57,7 +57,7 @@ deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/s >> Throughput (items/sec): 64.3392 ``` -Run the following to benchmark ORT with the dense, unoptimized BERT ONNX model (from SparseZoo): +Run the following to benchmark ORT with the same 90% pruned and quantized BERT ONNX model as above: ```bash deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none --batch_size 64 --engine onnxruntime @@ -69,26 +69,23 @@ deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/s ``` We can see that ORT does not gain additional performance from sparsity like DeepSparse. Additionally, DeepSparse runs the dense model -faster than ORT at high batch sizes. - -All in all, in this example **DeepSparse is 13.8x faster than ONNX Runtime**! +faster than ORT at high batch sizes. All in all, in this example **DeepSparse is 13.8x faster than ONNX Runtime**! ## Usage Run `deepsparse.benchmark -h` to see full command line arguments. -### Pass Your Local ONNX Model +Let's walk through a few examples of common functionality. -Beyond passing SparseZoo stubs, you can also pass a local path to an ONNX file to DeepSparse. +### Pass Your Local ONNX Model -As an example, let's download an ONNX file from SparseZoo using the CLI to a local directory called `./yolov5-download`. +Beyond passing SparseZoo stubs, you can also pass a local path to an ONNX file to DeepSparse. As an example, let's download an ONNX file from SparseZoo using the CLI to a local directory called `./yolov5-download`. ```bash sparsezoo.download zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none --save-dir yolov5-download ``` We can pass a local ONNX file as follows: - ```bash deepsparse.benchmark yolov5-download/model.onnx >> Original Model Path: yolov5-download/model.onnx @@ -179,6 +176,4 @@ deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/s >> Latency Mean (ms/batch): 3.7041 ``` -We can see that the async scenario achieves higher throughput, while the synchronous scenario achieves lower latency. - -Especially for very high core counts, using the asynchronous scheduler is a great way to improve performance if running at low batch sizes. \ No newline at end of file +We can see that the async scenario achieves higher throughput, while the synchronous scenario achieves lower latency. Especially for very high core counts, using the asynchronous scheduler is a great way to improve performance if running at low batch sizes. From a18a79b252fc5d46443b9792d731ebdd863083ea Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:41:16 -0400 Subject: [PATCH 006/149] Update and rename benchmarking.md to deepsparse-benchmarking.md --- user-guide/{benchmarking.md => deepsparse-benchmarking.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename user-guide/{benchmarking.md => deepsparse-benchmarking.md} (99%) diff --git a/user-guide/benchmarking.md b/user-guide/deepsparse-benchmarking.md similarity index 99% rename from user-guide/benchmarking.md rename to user-guide/deepsparse-benchmarking.md index 33de9c29a4..b47ae4de16 100644 --- a/user-guide/benchmarking.md +++ b/user-guide/deepsparse-benchmarking.md @@ -1,4 +1,4 @@ -# DeepSparse Benchmark +# DeepSparse Benchmarking This page explains how to use DeepSparse's CLI utilties for benchmarking performance in a variety of scenarios. From 639b8c1d6ad794b2cda5b4a2ec94b05489ee11cb Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:49:30 -0400 Subject: [PATCH 007/149] Update deepsparse-pipelines.md --- user-guide/deepsparse-pipelines.md | 160 +++-------------------------- 1 file changed, 15 insertions(+), 145 deletions(-) diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index 5818f2968f..d5f5e8c3a7 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -34,21 +34,20 @@ folder containing `model.onnx` and supporting files (e.g., the Hugging Face `tok ## Supported Use Cases -DeepSparse Pipelines support many common CV and NLP use cases out of the box. Follow the links below for -usage examples of each use case with Pipelines. +Pipelines support many CV and NLP use cases out of the box. Check out the use case pages for more details on task-specific APIs. **Computer Vision**: -- [Image Classification](/use-cases/image-classification/deploying): `task="image_classification"` -- [Object Detection](/use-cases/object-detection/deploying): `task="yolo"` -- [Instance Segmentation](/use-cases/instance-segmentation/deploying): `task="yolact"` +- Image Classification: `task="image_classification"` +- Object Detection: `task="yolo"` +- Instance Segmentation: `task="yolact"` **Natural Language Processing**: -- [Embedding Extraction](/use-cases/embedding-extraction): `task="transformers_embedding_extraction"` -- [Text Classification](/use-cases/use-cases/natural-language-processing/text-classification): `task="text-classification"` -- [Zero Shot Text Classification](/use-cases/use-cases/natural-language-processing/zero-shot-text-classification): `task="zero-shot-text-classification"` -- [Sentiment Analysis](/use-cases/use-cases/natural-language-processing/sentiment-analysis): `task="sentiment-analysis"` -- [Token Classification](/use-cases/use-cases/natural-language-processing/token-classification): `task="token-classification"` -- [Question Answering](/use-cases/use-cases/natural-language-processing/question-answering): `task="question-answering"` +- Embedding Extraction:`task="transformers_embedding_extraction"` +- Text Classification: `task="text-classification"` +- Zero Shot Text Classification: `task="zero-shot-text-classification"` +- Sentiment Analysis: `task="sentiment-analysis"` +- Token Classification: `task="token-classification"` +- Question Answering: `task="question-answering"` ## Custom Use Case @@ -173,139 +172,6 @@ print(output) # >> labels=['positive'] scores=[0.9951152801513672] << but runs slower than if using all cores ``` -### Multi-Stream Scheduling - -Schedulers handle the distribution of work across cores in parallel computation. -The goal of a good scheduler is to ensure that while work is available, cores aren’t sitting idle. -On the contrary, as long as parallel tasks are available, all cores should be kept busy. - -The default scheduler for DeepSparse is a "synchronous" scheduler, where DeepSparse allocates -as many resources as possible to each request. This format is optimizes per-request latency. - -However, DeepSparse also offers a "multi-stream" scheduler, which configures DeepSparse to -process multiple requests at the same time. In deployment scenarios with low batch sizes and -high core counts, using the "multi-stream" scheduler can increase throughput by allowing DeepSparse -to better saturate the cores. - -Let's walk through an example enabling the multi-stream capability with the Pipeline API. -This example was run on a 32 core AWS `c6i.16xlarge` instance. - -```python -from deepsparse import Pipeline -from deepsparse.cpu import cpu_details -import queue, threading, time, numpy - -num_cores = cpu_details()[0] -num_streams = num_cores // 4 - -zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" -input = "This multi stream idea is awesome for getting better throughput!" -batch_size = 1 - -single_stream_pipeline = Pipeline.create( - task="sentiment_analysis", - model_path=zoo_stub, - batch_size=batch_size, -) - -print(single_stream_pipeline(input)) -# >> labels=['positive'] scores=[0.9943772554397583] - -multi_stream_pipeline = Pipeline.create( - task="sentiment_analysis", - model_path=zoo_stub, - batch_size=batch_size, - scheduler="multi_stream", # use multi-stream scheduler - executor=num_streams # enable num_streams simultaneous streams -) - -print(multi_stream_pipeline(input)) -# >> labels=['positive'] scores=[0.9943772554397583] - -class ExecutorThread(threading.Thread): - def __init__(self, pipeline, input, time_queue, max_time): - super(ExecutorThread, self).__init__() - self._pipeline = pipeline - self._input = input - self._time_queue = time_queue - self._max_time = max_time - def iteration(self): - start = time.perf_counter() - output = self._pipeline(self._input) - end = time.perf_counter() - return start, end - def run(self): - while time.perf_counter() < self._max_time: - start, end = self.iteration() - self._time_queue.put([start, end]) - -def benchmark_performance(pipeline, num_threads): - seconds_to_run = 10.0 - time_queue = queue.Queue() # threadsafe - max_time = time.perf_counter() + seconds_to_run - - threads = [] - for _ in range(num_threads): - threads.append(ExecutorThread(multi_stream_pipeline, input, time_queue, max_time)) - - for thread in threads: - thread.start() - - for thread in threads: - thread.join() - - batch_times = list(time_queue.queue) - first_start_time = min([b[0] for b in batch_times]) - last_end_time = max([b[1] for b in batch_times]) - total_time_executing = last_end_time - first_start_time - items_per_sec = (batch_size * len(batch_times)) / total_time_executing - batch_times_ms = [ - (batch_time[1] - batch_time[0]) * 1000 for batch_time in batch_times - ] - - print(f">> Throughput: {items_per_sec} items/sec") - print(f">> Median Latency: {numpy.median(batch_times_ms)} ms") - -print("\nMulti-Stream Performance:") -benchmark_performance(multi_stream_pipeline, num_streams) - -print("\nSingle-Stream Performance:") -benchmark_performance(single_stream_pipeline, 1) - -# Multi-Stream Performance: -# >> Throughput: 310.2743200261281 items/sec -# >> Median Latency: 25.716418500451255 ms - -# Single-Stream Performance: -# >> Throughput: 241.04375983869264 items/sec -# >> Median Latency: 4.130347000682377 ms -``` - -Note that the deepSparse.benchmark script reports much better numbers for multi-stream. -Perhaps something is going on with the pre-processing sharing resources? - -deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 1 -s async -nstreams 8 - -Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -Batch Size: 1 -Scenario: async -Throughput (items/sec): 817.3063 -Latency Mean (ms/batch): 9.7701 -Latency Median (ms/batch): 9.7612 -Latency Std (ms/batch): 0.1710 -Iterations: 8176 - -deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 1 -s async -nstreams 8 - -Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -Batch Size: 1 -Scenario: sync -Throughput (items/sec): 272.8186 -Latency Mean (ms/batch): 3.6599 -Latency Median (ms/batch): 3.5558 -Latency Std (ms/batch): 0.2366 -Iterations: 2729 - ### Dynamic Batch Size We can utilize an the multi-stream capabilites of DeepSparse to make requests with dynamic batch sizes. @@ -452,6 +318,10 @@ As a result, high traffic on one of your Pipelines can impact performance on the isolate your Pipelines, we recommend using an orchestration framework such as Docker and Kubernetes with one DeepSparse Pipeline running in each container for proper process isolation. +### Multi-Stream Scheduling + +Stay tuned for documentation on enabling multi-stream scheduling with DeepSparse Pipelines. + ### Logging -Stay tuned for documentation on enabling logging with DeepSparse. \ No newline at end of file +Stay tuned for documentation on enabling logging with DeepSparse Pipelines. From 5b5c23abdca109ade31527199ba412614a64c6a7 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:54:22 -0400 Subject: [PATCH 008/149] Update deepsparse-server.md --- user-guide/deepsparse-server.md | 45 +++++++++++---------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/user-guide/deepsparse-server.md b/user-guide/deepsparse-server.md index 2630ca60d9..40cb18462a 100644 --- a/user-guide/deepsparse-server.md +++ b/user-guide/deepsparse-server.md @@ -1,6 +1,6 @@ # DeepSparse Server -DeepSparse Server wraps Pipelines with a REST API, making it easy to stand up a inference +DeepSparse Server wraps [Pipelines](deepsparse-pipelines.md) with a REST API, making it easy to stand up a inference serving endpoint running DeepSparse. ## Quickstart @@ -38,21 +38,20 @@ deepsparse.server --help ## Supported Use Cases -DeepSparse Server supports all tasks available in DeepSparse Pipelines. Follow the links below -for usage examples of each task. +DeepSparse Server supports all tasks available in DeepSparse Pipelines. Check out the use case guides for more details on task-specific APIs. **Computer Vision**: -- [Image Classification](/use-cases/image-classification/deploying): `task="image_classification"` -- [Object Detection](/use-cases/object-detection/deploying): `task="yolo"` -- [Instance Segmentation](/use-cases/instance-segmentation/deploying): `task="yolact"` +- Image Classification: `task="image_classification"` +- Object Detection: `task="yolo"` +- Instance Segmentation: `task="yolact"` **Natural Language Processing**: -- [Embedding Extraction](/use-cases/embedding-extraction): `task="transformers_embedding_extraction"` -- [Text Classification](/use-cases/use-cases/natural-language-processing/text-classification): `task="text-classification"` -- [Zero Shot Text Classification](/use-cases/use-cases/natural-language-processing/zero-shot-text-classification): `task="zero-shot-text-classification"` -- [Sentiment Analysis](/use-cases/use-cases/natural-language-processing/sentiment-analysis): `task="sentiment-analysis"` -- [Token Classification](/use-cases/use-cases/natural-language-processing/token-classification): `task="token-classification"` -- [Question Answering](/use-cases/use-cases/natural-language-processing/question-answering): `task="question-answering"` +- Embedding Extraction: `task="transformers_embedding_extraction"` +- Text Classification: `task="text-classification"` +- Zero Shot Text Classification: `task="zero-shot-text-classification"` +- Sentiment Analysis: `task="sentiment-analysis"` +- Token Classification: `task="token-classification"` +- Question Answering: `task="question-answering"` ## Swagger UI @@ -89,7 +88,7 @@ deepsparse.server \ ``` Sending a request: -``` +```python import requests url = "http://localhost:5543/predict" obj = {"sequences": "I love querying DeepSparse launched from a config file!"} @@ -115,16 +114,6 @@ utilizes its multi-stream scheduler, which processes multiple requests at the sa In deployment scenarios with low batch sizes and high core counts, using the "multi-stream" scheduler can increase throughput by allowing DeepSparse to better saturate the cores. -#### Thread Pinning -engine_thread_pinning - -TBU - -#### PyTorch Threads -pytorch_num_threads - -TBU - The following configuration creates a Server with DeepSparse running on 2 cores, with 2 input streams, DeepSparse threads pinned to cores, and PyTorch provided with 2 threads. @@ -132,8 +121,6 @@ DeepSparse threads pinned to cores, and PyTorch provided with 2 threads. # server-level-options-config.yaml num_cores: 2 num_workers: 2 -engine_thread_pinning: core -pytorch_num_threads: 2 endpoints: - task: sentiment-analysis @@ -150,7 +137,7 @@ deepsparse.server \ ``` We can then query the Server with the same pattern, querying on port 5555: -``` +```python import requests url = "http://localhost:5555/predict" obj = {"sequences": "I love querying DeepSparse launched from a config file!"} @@ -278,9 +265,7 @@ print(requests.post(sentiment_analysis_url, json=short_obj).text) # >>> {"labels":["positive","negative","positive"],"scores":[0.9665533900260925,0.9952980279922485,0.9939143061637878]} ``` -Checkout the [use case pages](/use-cases) for detailed documentation on task-specific -arguments that can be applied to the Server via `kwargs`. - +Checkout the use case pages for detailed documentation on task-specific arguments that can be applied to the Server via `kwargs`. ## Custom Use Cases @@ -292,4 +277,4 @@ Stay tuned for documentation on DeepSparse Logging! ## Hot Reloading -Stay tuned for documentation on Hot Reloading! \ No newline at end of file +Stay tuned for documentation on Hot Reloading! From 15624f65f5e620c72dd3f7b30f891980157a8335 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 14:57:05 -0400 Subject: [PATCH 009/149] Update scheduler.md --- user-guide/scheduler.md | 1 + 1 file changed, 1 insertion(+) diff --git a/user-guide/scheduler.md b/user-guide/scheduler.md index 836927b479..df84a429fc 100644 --- a/user-guide/scheduler.md +++ b/user-guide/scheduler.md @@ -12,6 +12,7 @@ The default scheduler is highly optimized for minimum per-request latency, using Often, particularly when working with large batch sizes, the scheduler is able to distribute the workload of a single request across as many cores as it's provided. *Single-stream scheduling; requests execute serially by default:* + single stream diagram ## Multi-Stream From 17a2e6893f59979352a04015ac9c2aede65e1d9d Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:05:03 -0400 Subject: [PATCH 010/149] Update user-guide/scheduler.md Co-authored-by: Michael Goin --- user-guide/scheduler.md | 1 + 1 file changed, 1 insertion(+) diff --git a/user-guide/scheduler.md b/user-guide/scheduler.md index df84a429fc..ece86b8c79 100644 --- a/user-guide/scheduler.md +++ b/user-guide/scheduler.md @@ -13,6 +13,7 @@ Often, particularly when working with large batch sizes, the scheduler is able t *Single-stream scheduling; requests execute serially by default:* + single stream diagram ## Multi-Stream From 8053834698ec779bb34c2d9a560021d3a338a534 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:05:13 -0400 Subject: [PATCH 011/149] Update user-guide/scheduler.md Co-authored-by: Michael Goin --- user-guide/scheduler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user-guide/scheduler.md b/user-guide/scheduler.md index ece86b8c79..d7e4625898 100644 --- a/user-guide/scheduler.md +++ b/user-guide/scheduler.md @@ -18,7 +18,7 @@ Often, particularly when working with large batch sizes, the scheduler is able t ## Multi-Stream -There are circumstances in which more cores does not imply better performance. If the computation can't be divided up to produce enough parallelism (while maximizing use of the CPU cache), then adding more cores simply adds more compute power with little to apply it to. +There are circumstances in which more cores does not imply better performance. If the computation can't be divided up to produce enough parallelism (while maximizing use of the CPU cache), then adding more cores simply adds more compute power with little work to apply it to. An alternative, multi-stream scheduler is provided with the software. In cases where parallelism is low, sending multiple requests simultaneously can more adequately saturate the available cores. In other words, if speedup can't be achieved by adding more cores, then perhaps speedup can be achieved by adding more work. From eb57109e4a615c8a62cd90ca0f80bd240a1e6ad5 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:05:20 -0400 Subject: [PATCH 012/149] Update user-guide/scheduler.md Co-authored-by: Michael Goin --- user-guide/scheduler.md | 1 + 1 file changed, 1 insertion(+) diff --git a/user-guide/scheduler.md b/user-guide/scheduler.md index d7e4625898..b0669ca7ad 100644 --- a/user-guide/scheduler.md +++ b/user-guide/scheduler.md @@ -25,6 +25,7 @@ An alternative, multi-stream scheduler is provided with the software. In cases w If increasing core count does not decrease latency, that's a strong indicator that parallelism is low in your particular model/batch-size combination. It may be that total throughput can be increased by making more requests simultaneously. Using the [deepsparse.engine.Scheduler API,](https://docs.neuralmagic.com/deepsparse/api/deepsparse.html) the multi-stream scheduler can be selected, and requests made by multiple Python threads will be handled concurrently. *Multi-stream scheduling; requests execute in parallel and may better utilize hardware resources:* + multi stream diagram From 3946aca9b92bf1efca0791811e870a49c5b90cb6 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:36:44 -0400 Subject: [PATCH 013/149] Update user-guide/deepsparse-pipelines.md Co-authored-by: Michael Goin --- user-guide/deepsparse-pipelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index d5f5e8c3a7..1459467d46 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -7,7 +7,7 @@ pre- and post-processing, enabling you to pass raw data and recieve the predicti ## Quickstart -Let's try a quick example of the Pipeline API. All we have to is pass a task and model to the +Let's try a quick example of the Pipeline API. All we have to do is pass a task and model to the the `Pipeline.create` function, and then we can run inference on raw data using DeepSparse! This example creates a sentiment analysis Pipeline with a 90% pruned-quantized verion of BERT From d9803bb826b35feae9452cba1cb7a8ec0d7789d1 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:36:52 -0400 Subject: [PATCH 014/149] Update user-guide/deepsparse-pipelines.md Co-authored-by: Michael Goin --- user-guide/deepsparse-pipelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index 1459467d46..3b8d61385e 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -28,7 +28,7 @@ print(sentiment_analysis_pipeline("I love using DeepSparse Pipelines")) # >>> labels=['positive'] scores=[0.9954759478569031] ``` -In this case, we passed a SparseZoo stub as the model, which instructs the DeepSparse to download the +In this case we passed a SparseZoo stub as the model, which instructs DeepSparse to download the relevant ONNX file from the SparseZoo. To deploy your own model, pass a path to a `model.onnx` file or to a folder containing `model.onnx` and supporting files (e.g., the Hugging Face `tokenizer.json` and `config.json`). From 7e0a97b3e535f5601229d4ca18e1cd669618afc0 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:37:04 -0400 Subject: [PATCH 015/149] Update user-guide/deepsparse-pipelines.md Co-authored-by: Michael Goin --- user-guide/deepsparse-pipelines.md | 1 - 1 file changed, 1 deletion(-) diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index 3b8d61385e..1da5a2162c 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -157,7 +157,6 @@ one core with the following: from deepsparse import Pipeline zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" -num_cores = 1 sentiment_analysis_pipeline = Pipeline.create( task="sentiment-analysis", # name of the task model_path=zoo_stub, # zoo stub or path to local onnx file From ca0f27d976adfbf023c4d06a0812a78349604dc0 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Thu, 16 Mar 2023 15:42:33 -0400 Subject: [PATCH 016/149] added README --- user-guide/README.md | 347 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 user-guide/README.md diff --git a/user-guide/README.md b/user-guide/README.md new file mode 100644 index 0000000000..9efc7ca126 --- /dev/null +++ b/user-guide/README.md @@ -0,0 +1,347 @@ +--- +title: "DeepSparse Community" +metaTitle: "DeepSparse Community" +metaDescription: "Sparsity-aware neural network inference engine for GPU-class performance on CPUs" +index: 1000 +--- + +
+

+ tool icon +   DeepSparse Community +

+

A deep learning inference runtime with GPU-class performance on CPUs

+ +
+ +**DeepSparse is a CPU deep learning inference runtime with GPU-class performance.** + +Because DeepSparse reaches GPU-class latency with commodity CPUs, you no longer need to tie deployments to accelerators to reach the performance needed for production. +Free from specialized hardware, deployments can then take advantage of the flexibility and scalability of software-defined inference: +- Deploy the same model and runtime on any hardware from Intel to AMD to ARM (soon) and from cloud to data center to edge, including on pre-existing systems +- Scale vertically to 192 cores, horizontally with Kubernetes, or abstractly with serverless +- Integrate easily into "deploy with code" provisioning systems +- No wrestling with drivers, operator support, and compatibility issues + +Simply put, with DeepSparse on CPUs, you can both simplify your deep learning deployment process and save on infrastructure costs required to support enterprise-scale workloads. + +## Hardware Support and System Requirements + +Review [CPU Hardware Support for Various Architectures](https://docs.neuralmagic.com/deepsparse/source/hardware.html) to understand system requirements. + +DeepSparse runs natively on Linux. Mac and Windows require running Linux in a Docker or virtual machine. + +DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.12.0, ONNX opset version 11+, and manylinux compliant systems. Using a virtual environment is highly recommended. + +## Installation + +Install DeepSparse Community with `pip`: + +```bash +pip install deepsparse +``` + +See the [DeepSparse Installation page](https://docs.neuralmagic.com/get-started/install/deepsparse) for further installation options. + +## Performance Benchmarking + +DeepSparse's key feature is its performance on commodity CPUs. DeepSparse is competitive with other CPU runtimes +like ONNX Runtime for unoptimized dense models. However, when optimization techniques like pruning and quantization +are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. + +As an example, let's compare DeepSparse and ORT's performance on BERT. In SparseZoo, there is 90% pruned and quantized +BERT which retains >99% of the accuracy of the baseline dense model. +Running this model on a `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! + +![Performance Benchmarking Chart](https://raw.githubusercontent.com/neuralmagic/docs/rs/use-case-update/src/images/bert-performance-chart.png) + +To replicate the results, make sure you ONNX Runtime installed (`pip install onnxruntime`). + +ORT achieves 18.5 items/second running BERT: +```bash +deepsparse.benchmark zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/base-none -b 64 -s sync -nstreams 1 -i [64,384] -e onnxruntime + +>> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/base-none +>> Batch Size: 64 +>> Scenario: sync +>> Throughput (items/sec): 18.5742 +``` + +DeepSparse achieves 226 items/second running the pruned-quantized version of BERT: + +```bash +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 32 -s sync -nstreams 1 -e onnxruntime + +>> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none +>> Batch Size: 64 +>> Scenario: sync +>> Throughput (items/sec): 226.6340 +``` + +**Pro-Tip:** In place of a [SparseZoo](https://sparsezoo.neuralmagic.com/) stubs, you can pass a local ONNX file to test your model. + +## Deployment APIs + +Now that we have seen DeepSparse's performance gains, let's take a look at how we can add DeepSparse to an application. + +DeepSparse includes three deployment APIs: +- Engine is the lowest-level API. With Engine, you pass tensors and recieve the raw logits. +- Pipeline wraps the Engine with pre- and post-processing. With Pipeline, you pass raw data and +recieve the prediction. +- Server wraps Pipelines with a REST API using FastAPI. With Server, you send raw data to an endpoint over HTTP +and recieve the prediction. + +Let's walk through a simple example of each API to give a sense of usage. As an example, we will use +the sentiment analysis use-case with a 90% pruned-quantized version of BERT. + +Check out the [use case section](/use-cases) for details on the APIs of each supported use case. + +### Engine + +Engine is the lowest-level API, allowing you to run inference directly on input tensors. + +The example below downloads a 90% pruned-quantized BERT model for sentiment analysis +in ONNX format from SparseZoo, compiles the model, and runs inference on randomly generated input. + +```python +from deepsparse import compile_model +from deepsparse.utils import generate_random_inputs, model_to_path + +# download onnx, compile model +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 1 +bert_model = compile_model( + model=zoo_stub, # sparsezoo stub or path/to/local/model.onnx + batch_size=batch_size # default is batch 1 +) + +# run inference (input is raw numpy tensors, output is raw scores) +inputs = generate_random_inputs(model_to_path(zoo_stub), batch_size) +output = bert_model(inputs) +print(output) + +# > [array([[-0.3380675 , 0.09602544]], dtype=float32)] << raw scores +``` + +#### Model Format + +DeepSparse can accept ONNX models from two sources: + +- **SparseZoo Stubs**: SparseZoo is Neural Magic's open-source repository of sparse models. You can pass a SparseZoo stub, a unique identifier for +each model to DeepSparse, which downloads the necessary ONNX files from the remote repository. + +- **Custom ONNX**: DeepSparse allows you to use your own model in ONNX format. Checkout the SparseML user guide for more details on exporting +your sparse models to ONNX format. Here's a quick example using a custom ONNX file from the ONNX model zoo: + +```bash +wget https://github.com/onnx/models/raw/main/vision/classification/mobilenet/model/mobilenetv2-7.onnx +> Saving to: ‘mobilenetv2-7.onnx’ +``` + +```python +from deepsparse import compile_model +from deepsparse.utils import generate_random_inputs +onnx_filepath = "mobilenetv2-7.onnx" +batch_size = 16 + +# Generate random sample input +inputs = generate_random_inputs(onnx_filepath, batch_size) + +# Compile and run +engine = compile_model(onnx_filepath, batch_size) +outputs = engine.run(inputs) +``` + +### Pipeline + +Pipeline is the default API for interacting with DeepSparse. Similar to Hugging Face Pipelines, +DeepSparse Pipelines wrap Engine with pre- and post-processing (as well as other utilities), +enabling you to send raw data to DeepSparse and recieve the post-processed prediction. + +The example below downloads a 90% pruned-quantized BERT model for sentiment analysis +in ONNX format from SparseZoo, sets up a pipeline, and runs inference on sample data. + +```python +from deepsparse import Pipeline + +# download onnx, set up pipeline +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 1 +sentiment_analysis_pipeline = Pipeline.create( + task="sentiment-analysis", # name of the task + model_path=zoo_stub, # zoo stub or path to local onnx file + batch_size=batch_size # default is batch 1 +) + +# run inference (input is a sentence, output is the prediction) +prediction = sentiment_analysis_pipeline("I love using DeepSparse Pipelines") +print(prediction) +# > labels=['positive'] scores=[0.9954759478569031] +``` + + +### Server + +Server wraps Pipelines with REST APIs, that make it easy to stand up a model serving endpoint +running DeepSparse. This enables you to send raw data to DeepSparse over HTTP and recieve the post-processed +predictions. + +DeepSparse Server is launched from the command line, configured via arguments or a server configuration file. + +The following downloads a 90% pruned-quantized BERT model for sentiment analysis in ONNX format +from SparseZoo and launches a sentiment analysis endpoint: + +```bash +deepsparse.server \ + --task sentiment-analysis \ + --model_path zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +``` + +Alternatively, the following configuration file can launch the Server. + +```yaml +# config.yaml +endpoints: + - task: sentiment-analysis + route: /predict + model: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +``` + +Spinning up: +```bash +deepsparse.server \ + --config-file config.yaml +``` + +You should see Uvicorn report that it is running on port 5543. Navigating to the `/docs` endpoint will +show the exposed routes as well as sample requests. + +We can then send a request over HTTP. In this example, we will use the Python requests package +to format the HTTP. + +```python +import requests + +url = "http://localhost:5543/predict" # Server's port default to 5543 +obj = {"sequences": "Snorlax loves my Tesla!"} + +response = requests.post(url, json=obj) +print(response.text) +# {"labels":["positive"],"scores":[0.9965094327926636]} +``` + +## Supported Tasks + +DeepSparse supports many common CV and NLP use cases out of the box. Follow the links below for +usage examples of each use case. + +**Computer Vision**: +- [Image Classification](/use-cases/image-classification/deploying): `task="image_classification"` +- [Object Detection](/use-cases/object-detection/deploying): `task="yolo"` +- [Instance Segmentation](/use-cases/instance-segmentation/deploying): `task="yolact"` + +**Natural Language Processing**: +- [Embedding Extraction](/use-cases/embedding-extraction): `task="transformers_embedding_extraction"` +- [Text Classification](/use-cases/use-cases/natural-language-processing/text-classification): `task="text-classification"` +- [Zero Shot Text Classification](/use-cases/use-cases/natural-language-processing/zero-shot-text-classification): `task="zero-shot-text-classification"` +- [Sentiment Analysis](/use-cases/use-cases/natural-language-processing/sentiment-analysis): `task="sentiment-analysis"` +- [Token Classification](/use-cases/use-cases/natural-language-processing/token-classification): `task="token-classification"` +- [Question Answering](/use-cases/use-cases/natural-language-processing/question-answering): `task="question-answering"` + +## Advanced Functionality + +DeepSparse contains many additional utilties that simplify deployment. Check out the user guide for detailed exploration of the feature set: +- DeepSparse Pipelines +- DeepSparse Server +- DeepSparse Benchmark +- Logging +- Scheduler + +## Training a Sparse Model + +For details on training a sparse model, check out the SparseML user guide. + +## Community + +### Be Part of the Future ... And the Future is Sparse! + +Contribute with code, examples, integrations, and documentation as well as bug reports and feature requests! [Learn how here.](https://github.com/neuralmagic/deepsparse/blob/main/CONTRIBUTING.md) + +For user help or questions about DeepSparse, sign up or log into our [Neural Magic Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). We are growing the community member by member and happy to see you there. Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue.](https://github.com/neuralmagic/deepsparse/issues) You can get the latest news, webinar and event invites, research papers, and other ML performance tidbits by [subscribing](https://neuralmagic.com/subscribe/) to the Neural Magic community. + +For more general questions about Neural Magic, complete this [form.](http://neuralmagic.com/contact/) + +### License + +DeepSparse Community is licensed under the [Neural Magic DeepSparse Community License.](https://github.com/neuralmagic/deepsparse/blob/main/LICENSE-NEURALMAGIC) +Some source code, example files, and scripts included in the DeepSparse GitHub repository or directory are licensed under the [Apache License Version 2.0](https://github.com/neuralmagic/deepsparse/blob/main/LICENSE) as noted. + +[DeepSparse Enterprise](https://docs.neuralmagic.com/products/deepsparse-ent) requires a Trial License or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. + +### Cite + +Find this project useful in your research or other communications? Please consider citing: + +```bibtex +@InProceedings{ + pmlr-v119-kurtz20a, + title = {Inducing and Exploiting Activation Sparsity for Fast Inference on Deep Neural Networks}, + author = {Kurtz, Mark and Kopinsky, Justin and Gelashvili, Rati and Matveev, Alexander and Carr, John and Goin, Michael and Leiserson, William and Moore, Sage and Nell, Bill and Shavit, Nir and Alistarh, Dan}, + booktitle = {Proceedings of the 37th International Conference on Machine Learning}, + pages = {5533--5543}, + year = {2020}, + editor = {Hal Daumé III and Aarti Singh}, + volume = {119}, + series = {Proceedings of Machine Learning Research}, + address = {Virtual}, + month = {13--18 Jul}, + publisher = {PMLR}, + pdf = {http://proceedings.mlr.press/v119/kurtz20a/kurtz20a.pdf}, + url = {http://proceedings.mlr.press/v119/kurtz20a.html} +} + +@article{DBLP:journals/corr/abs-2111-13445, + author = {Eugenia Iofinova and + Alexandra Peste and + Mark Kurtz and + Dan Alistarh}, + title = {How Well Do Sparse Imagenet Models Transfer?}, + journal = {CoRR}, + volume = {abs/2111.13445}, + year = {2021}, + url = {https://arxiv.org/abs/2111.13445}, + eprinttype = {arXiv}, + eprint = {2111.13445}, + timestamp = {Wed, 01 Dec 2021 15:16:43 +0100}, + biburl = {https://dblp.org/rec/journals/corr/abs-2111-13445.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} +``` From 7a55eec9f4c53a41bd87d76859a93785d4a5abe6 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:47:01 -0400 Subject: [PATCH 017/149] Update README.md --- user-guide/README.md | 157 +++++-------------------------------------- 1 file changed, 16 insertions(+), 141 deletions(-) diff --git a/user-guide/README.md b/user-guide/README.md index 9efc7ca126..c292662fe6 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -1,77 +1,22 @@ ---- -title: "DeepSparse Community" -metaTitle: "DeepSparse Community" -metaDescription: "Sparsity-aware neural network inference engine for GPU-class performance on CPUs" -index: 1000 ---- - -
-

- tool icon -   DeepSparse Community -

-

A deep learning inference runtime with GPU-class performance on CPUs

- -
- -**DeepSparse is a CPU deep learning inference runtime with GPU-class performance.** - -Because DeepSparse reaches GPU-class latency with commodity CPUs, you no longer need to tie deployments to accelerators to reach the performance needed for production. -Free from specialized hardware, deployments can then take advantage of the flexibility and scalability of software-defined inference: -- Deploy the same model and runtime on any hardware from Intel to AMD to ARM (soon) and from cloud to data center to edge, including on pre-existing systems -- Scale vertically to 192 cores, horizontally with Kubernetes, or abstractly with serverless -- Integrate easily into "deploy with code" provisioning systems -- No wrestling with drivers, operator support, and compatibility issues - -Simply put, with DeepSparse on CPUs, you can both simplify your deep learning deployment process and save on infrastructure costs required to support enterprise-scale workloads. +# User Guide -## Hardware Support and System Requirements +This directory demonstrates usage of DeepSparse. -Review [CPU Hardware Support for Various Architectures](https://docs.neuralmagic.com/deepsparse/source/hardware.html) to understand system requirements. +## Hardware Support and System Requirements -DeepSparse runs natively on Linux. Mac and Windows require running Linux in a Docker or virtual machine. +Review [CPU Hardware Support for Various Architectures](https://docs.neuralmagic.com/deepsparse/source/hardware.html) to understand system requirements. DeepSparse runs natively on Linux. Mac and Windows require running Linux in a Docker or virtual machine. DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.12.0, ONNX opset version 11+, and manylinux compliant systems. Using a virtual environment is highly recommended. ## Installation -Install DeepSparse Community with `pip`: - ```bash pip install deepsparse ``` See the [DeepSparse Installation page](https://docs.neuralmagic.com/get-started/install/deepsparse) for further installation options. -## Performance Benchmarking +## [Performance Benchmarking](deepsparse-benchmarking.md) DeepSparse's key feature is its performance on commodity CPUs. DeepSparse is competitive with other CPU runtimes like ONNX Runtime for unoptimized dense models. However, when optimization techniques like pruning and quantization @@ -180,7 +125,7 @@ engine = compile_model(onnx_filepath, batch_size) outputs = engine.run(inputs) ``` -### Pipeline +### [Pipeline](deepsparse-pipeline.md) Pipeline is the default API for interacting with DeepSparse. Similar to Hugging Face Pipelines, DeepSparse Pipelines wrap Engine with pre- and post-processing (as well as other utilities), @@ -208,7 +153,7 @@ print(prediction) ``` -### Server +### [Server](deepsparse-server.md) Server wraps Pipelines with REST APIs, that make it easy to stand up a model serving endpoint running DeepSparse. This enables you to send raw data to DeepSparse over HTTP and recieve the post-processed @@ -264,84 +209,14 @@ DeepSparse supports many common CV and NLP use cases out of the box. Follow the usage examples of each use case. **Computer Vision**: -- [Image Classification](/use-cases/image-classification/deploying): `task="image_classification"` -- [Object Detection](/use-cases/object-detection/deploying): `task="yolo"` -- [Instance Segmentation](/use-cases/instance-segmentation/deploying): `task="yolact"` +- Image Classification: `task="image_classification"` +- Object Detection: `task="yolo"` +- Instance Segmentation: `task="yolact"` **Natural Language Processing**: -- [Embedding Extraction](/use-cases/embedding-extraction): `task="transformers_embedding_extraction"` -- [Text Classification](/use-cases/use-cases/natural-language-processing/text-classification): `task="text-classification"` -- [Zero Shot Text Classification](/use-cases/use-cases/natural-language-processing/zero-shot-text-classification): `task="zero-shot-text-classification"` -- [Sentiment Analysis](/use-cases/use-cases/natural-language-processing/sentiment-analysis): `task="sentiment-analysis"` -- [Token Classification](/use-cases/use-cases/natural-language-processing/token-classification): `task="token-classification"` -- [Question Answering](/use-cases/use-cases/natural-language-processing/question-answering): `task="question-answering"` - -## Advanced Functionality - -DeepSparse contains many additional utilties that simplify deployment. Check out the user guide for detailed exploration of the feature set: -- DeepSparse Pipelines -- DeepSparse Server -- DeepSparse Benchmark -- Logging -- Scheduler - -## Training a Sparse Model - -For details on training a sparse model, check out the SparseML user guide. - -## Community - -### Be Part of the Future ... And the Future is Sparse! - -Contribute with code, examples, integrations, and documentation as well as bug reports and feature requests! [Learn how here.](https://github.com/neuralmagic/deepsparse/blob/main/CONTRIBUTING.md) - -For user help or questions about DeepSparse, sign up or log into our [Neural Magic Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). We are growing the community member by member and happy to see you there. Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue.](https://github.com/neuralmagic/deepsparse/issues) You can get the latest news, webinar and event invites, research papers, and other ML performance tidbits by [subscribing](https://neuralmagic.com/subscribe/) to the Neural Magic community. - -For more general questions about Neural Magic, complete this [form.](http://neuralmagic.com/contact/) - -### License - -DeepSparse Community is licensed under the [Neural Magic DeepSparse Community License.](https://github.com/neuralmagic/deepsparse/blob/main/LICENSE-NEURALMAGIC) -Some source code, example files, and scripts included in the DeepSparse GitHub repository or directory are licensed under the [Apache License Version 2.0](https://github.com/neuralmagic/deepsparse/blob/main/LICENSE) as noted. - -[DeepSparse Enterprise](https://docs.neuralmagic.com/products/deepsparse-ent) requires a Trial License or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. - -### Cite - -Find this project useful in your research or other communications? Please consider citing: - -```bibtex -@InProceedings{ - pmlr-v119-kurtz20a, - title = {Inducing and Exploiting Activation Sparsity for Fast Inference on Deep Neural Networks}, - author = {Kurtz, Mark and Kopinsky, Justin and Gelashvili, Rati and Matveev, Alexander and Carr, John and Goin, Michael and Leiserson, William and Moore, Sage and Nell, Bill and Shavit, Nir and Alistarh, Dan}, - booktitle = {Proceedings of the 37th International Conference on Machine Learning}, - pages = {5533--5543}, - year = {2020}, - editor = {Hal Daumé III and Aarti Singh}, - volume = {119}, - series = {Proceedings of Machine Learning Research}, - address = {Virtual}, - month = {13--18 Jul}, - publisher = {PMLR}, - pdf = {http://proceedings.mlr.press/v119/kurtz20a/kurtz20a.pdf}, - url = {http://proceedings.mlr.press/v119/kurtz20a.html} -} - -@article{DBLP:journals/corr/abs-2111-13445, - author = {Eugenia Iofinova and - Alexandra Peste and - Mark Kurtz and - Dan Alistarh}, - title = {How Well Do Sparse Imagenet Models Transfer?}, - journal = {CoRR}, - volume = {abs/2111.13445}, - year = {2021}, - url = {https://arxiv.org/abs/2111.13445}, - eprinttype = {arXiv}, - eprint = {2111.13445}, - timestamp = {Wed, 01 Dec 2021 15:16:43 +0100}, - biburl = {https://dblp.org/rec/journals/corr/abs-2111-13445.bib}, - bibsource = {dblp computer science bibliography, https://dblp.org} -} -``` +- Embedding Extraction: `task="transformers_embedding_extraction"` +- Text Classification: `task="text-classification"` +- Zero Shot Text Classification: `task="zero-shot-text-classification"` +- Sentiment Analysis: `task="sentiment-analysis"` +- Token Classification: `task="token-classification"` +- Question Answering: `task="question-answering"` From f18acb36b70d75a041d47e499e9a3bf40edaf784 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:58:28 -0400 Subject: [PATCH 018/149] Update README.md --- user-guide/README.md | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/user-guide/README.md b/user-guide/README.md index c292662fe6..a7ab071f68 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -1,15 +1,15 @@ -# User Guide +# DeepSparse User Guide -This directory demonstrates usage of DeepSparse. - -## Hardware Support and System Requirements - -Review [CPU Hardware Support for Various Architectures](https://docs.neuralmagic.com/deepsparse/source/hardware.html) to understand system requirements. DeepSparse runs natively on Linux. Mac and Windows require running Linux in a Docker or virtual machine. - -DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.12.0, ONNX opset version 11+, and manylinux compliant systems. Using a virtual environment is highly recommended. +This directory demonstrates usage of DeepSparse's key API, including: +- [Performance Benchmarking](#performance-benchmarking) +- [Engine API](#engine) +- [Pipeline API](#pipeline) +- [Server API](#server) ## Installation +Install via `pip`. Using a virtual enviornment is highly recommended. + ```bash pip install deepsparse ``` @@ -21,16 +21,11 @@ See the [DeepSparse Installation page](https://docs.neuralmagic.com/get-started/ DeepSparse's key feature is its performance on commodity CPUs. DeepSparse is competitive with other CPU runtimes like ONNX Runtime for unoptimized dense models. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. + +As an example, let's compare DeepSparse and ORT's performance on BERT. In SparseZoo, there is [90% pruned-quantized +BERT]() which retains >99% of the accuracy of the baseline dense model. Running this model on a `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! -As an example, let's compare DeepSparse and ORT's performance on BERT. In SparseZoo, there is 90% pruned and quantized -BERT which retains >99% of the accuracy of the baseline dense model. -Running this model on a `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! - -![Performance Benchmarking Chart](https://raw.githubusercontent.com/neuralmagic/docs/rs/use-case-update/src/images/bert-performance-chart.png) - -To replicate the results, make sure you ONNX Runtime installed (`pip install onnxruntime`). - -ORT achieves 18.5 items/second running BERT: +ORT achieves 18.5 items/second running BERT (make sure you have ORT installed `pip install onnxruntime`): ```bash deepsparse.benchmark zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/base-none -b 64 -s sync -nstreams 1 -i [64,384] -e onnxruntime @@ -205,8 +200,7 @@ print(response.text) ## Supported Tasks -DeepSparse supports many common CV and NLP use cases out of the box. Follow the links below for -usage examples of each use case. +DeepSparse supports many common CV and NLP use cases out of the box. Check out the use case guide for more details on the task-specific APIs. **Computer Vision**: - Image Classification: `task="image_classification"` From 9e5f05fa1c63f4bdfe7aeee403f9781e10ef714d Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:59:40 -0400 Subject: [PATCH 019/149] Update README.md --- user-guide/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/user-guide/README.md b/user-guide/README.md index a7ab071f68..a638dde295 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -20,10 +20,9 @@ See the [DeepSparse Installation page](https://docs.neuralmagic.com/get-started/ DeepSparse's key feature is its performance on commodity CPUs. DeepSparse is competitive with other CPU runtimes like ONNX Runtime for unoptimized dense models. However, when optimization techniques like pruning and quantization -are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. - -As an example, let's compare DeepSparse and ORT's performance on BERT. In SparseZoo, there is [90% pruned-quantized -BERT]() which retains >99% of the accuracy of the baseline dense model. Running this model on a `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! +are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. As an example, let's compare DeepSparse and ORT's performance on BERT. In SparseZoo, there is [90% pruned-quantized BERT](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none). + +Running this model on a `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! ORT achieves 18.5 items/second running BERT (make sure you have ORT installed `pip install onnxruntime`): ```bash @@ -38,7 +37,7 @@ deepsparse.benchmark zoo:nlp/text_classification/obert-base/pytorch/huggingface/ DeepSparse achieves 226 items/second running the pruned-quantized version of BERT: ```bash -deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 32 -s sync -nstreams 1 -e onnxruntime +deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 64 -s sync -nstreams 1 -e onnxruntime >> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none >> Batch Size: 64 From 8a2c6661404bef8e927601f3b0ff2e07bba057d0 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:01:30 -0400 Subject: [PATCH 020/149] Update README.md --- user-guide/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/user-guide/README.md b/user-guide/README.md index a638dde295..ffc836a473 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -22,7 +22,7 @@ DeepSparse's key feature is its performance on commodity CPUs. DeepSparse is com like ONNX Runtime for unoptimized dense models. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. As an example, let's compare DeepSparse and ORT's performance on BERT. In SparseZoo, there is [90% pruned-quantized BERT](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none). -Running this model on a `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! +Running this model on an AWS `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! ORT achieves 18.5 items/second running BERT (make sure you have ORT installed `pip install onnxruntime`): ```bash @@ -61,8 +61,6 @@ and recieve the prediction. Let's walk through a simple example of each API to give a sense of usage. As an example, we will use the sentiment analysis use-case with a 90% pruned-quantized version of BERT. -Check out the [use case section](/use-cases) for details on the APIs of each supported use case. - ### Engine Engine is the lowest-level API, allowing you to run inference directly on input tensors. From 8ff12f5a8de3bf13c5df6e862bb8a7d9b65e8806 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:02:14 -0400 Subject: [PATCH 021/149] Update README.md --- user-guide/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user-guide/README.md b/user-guide/README.md index ffc836a473..bc7a42c21c 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -55,7 +55,7 @@ DeepSparse includes three deployment APIs: - Engine is the lowest-level API. With Engine, you pass tensors and recieve the raw logits. - Pipeline wraps the Engine with pre- and post-processing. With Pipeline, you pass raw data and recieve the prediction. -- Server wraps Pipelines with a REST API using FastAPI. With Server, you send raw data to an endpoint over HTTP +- Server wraps Pipelines with a REST API using FastAPI. With Server, you send raw data over HTTP and recieve the prediction. Let's walk through a simple example of each API to give a sense of usage. As an example, we will use From 19804f2f68abe35dc41a574b04cc5baef4d0319e Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:02:58 -0400 Subject: [PATCH 022/149] Update README.md --- user-guide/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user-guide/README.md b/user-guide/README.md index bc7a42c21c..1ec6a901a0 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -1,7 +1,7 @@ # DeepSparse User Guide This directory demonstrates usage of DeepSparse's key API, including: -- [Performance Benchmarking](#performance-benchmarking) +- [Benchmarking CLI](#performance-benchmarking) - [Engine API](#engine) - [Pipeline API](#pipeline) - [Server API](#server) From 53e9a939d389d6c8fc8cb7f326ca34f6dc922550 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:04:00 -0400 Subject: [PATCH 023/149] Update README.md --- user-guide/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/user-guide/README.md b/user-guide/README.md index 1ec6a901a0..fc48719c6c 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -211,3 +211,5 @@ DeepSparse supports many common CV and NLP use cases out of the box. Check out t - Sentiment Analysis: `task="sentiment-analysis"` - Token Classification: `task="token-classification"` - Question Answering: `task="question-answering"` + +Check out the [pipeline page](deepsparse-pipeline.md) for details on creating a custom pipeline. From d8972f2e986c0015a74761a309323b972e8dedae Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Thu, 16 Mar 2023 16:06:34 -0400 Subject: [PATCH 024/149] added sentiment-analysis --- use-cases/nlp/sentiment-analysis.md | 314 ++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 use-cases/nlp/sentiment-analysis.md diff --git a/use-cases/nlp/sentiment-analysis.md b/use-cases/nlp/sentiment-analysis.md new file mode 100644 index 0000000000..5623134e4f --- /dev/null +++ b/use-cases/nlp/sentiment-analysis.md @@ -0,0 +1,314 @@ +--- +title: "Deploy with DeepSparse" +metaTitle: "Deploy Sentiment Analysis Models on CPUs with DeepSparse" +metaDescription: "Deploy Sentiment Analysis Models on CPUs with DeepSparse" +index: 2000 +--- + +# Deploying Sentiment Analysis Models with DeepSparse + +This page explains how to benchmark and deploy a sentiment analysis model with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API. It enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similiar in concept to Hugging Face Pipelines, it wraps Engine with pre-preprocessing +and post-processing, allowing you to make requests on raw data and recieve post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on FastAPI and Uvicorn. It enables you to stand up a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 32 +core AWS c6i.8xlarge instance. + +### ONNX Runtime Baseline + +As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you have ORT installed (`pip install onnxruntime`). + +```bash +deepsparse.benchmark \ + zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e onnxruntime + +> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 18.6350 +``` + +ONNX Runtime achieves 19 items/second with batch 64 and sequence length 384. + +### DeepSparse Speedup + +Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 90% pruned and quantized, while +retaining >99% accuracy of the dense baseline on the SST2 dataset. + +```bash +deepsparse.benchmark \ + zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e deepsparse + +> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 217.6529 +``` + +DeepSparse achieves 218 items/second, ***an 11.5x speed-up over ONNX Runtime!*** + +## DeepSparse Engine + +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended you use the Pipeline +API but Engine is available as needed if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 90% pruned-quantized BERT trained on SST2 from SparseZoo: + +```python +from deepsparse import compile_model +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 1 +bert_engine = compile_model( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) + +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = bert_engine(inputs) +print(output) + +# >> [array([[-0.3380675 , 0.09602544]], dtype=float32)] +``` + +## DeepSparse Pipelines + +Pipeline is the default interface for interacting with DeepSparse. + +Just like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. +This creates a clean API that allows you to pass raw images to DeepSparse and recieve back the post-processed prediction, +making it easy to add DeepSparse to your application. + +We will use the `Pipeline.create()` constructor to create an instance of an image classifcation Pipeline +with a 90% pruned-quantized version of BERT trained on SST2. We can then pass the Pipeline raw text and recieve the predictions. +All of the pre-processing (such as tokenizing the input) is handled by the Pipeline. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 1 +sa_pipeline = Pipeline.create( + task="sentiment-analysis", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference on image file +prediction = sa_pipeline("The sentiment analysis pipeline is fast and easy to use") +print(prediction) + +# >>> labels=['positive'] scores=[0.9955807328224182] +``` + +### Use Case Specific Arguments + +The Sentiment Analysis Pipeline contains additional arguments for configuring a Pipeline. + +#### Sequence Length + +The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. Inside the DeepSparse pipelines, +the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. + +The example below compiles the model and runs inference with sequence length 64. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 1 +sequence_length = 64 +sa_pipeline = Pipeline.create( + task="sentiment-analysis", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 + sequence_length=64 # default sequence length is 128 +) + +# run inference on image file +prediction = sa_pipeline("The sentiment analysis pipeline is fast and easy to use") +print(prediction) + +# >>> labels=['positive'] scores=[0.9955807328224182] +``` + +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, +the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) +and direct inputs towards the smallest bucket into which it fits. + +The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 1 +buckets = [16, 128] +sa_pipeline = Pipeline.create( + task="sentiment-analysis", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + sequence_length=buckets # creates bucketed pipeline +) + +# run inference on short sequence +prediction = sa_pipeline("I love short sentences!") +print(prediction) + +# run inference on long sequence +prediction = sa_pipeline("Normal sized sequences take a lot longer to run but are I still like them a lot because of the speedup from DeepSparse") +print(prediction) + +# >>> labels=['positive'] scores=[0.9988369941711426] +# >>> labels=['positive'] scores=[0.9587154388427734] +``` + +#### Return All Scores + +The `return_all_scores` argument allows you to specify whether to return the prediction as the argmax of class predictions or +to return all scores as a list for each result in the batch. + +Here is an example with batch size 1 and batch size 2: + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +sa_pipeline_b1 = Pipeline.create( + task="sentiment-analysis", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + return_all_scores=True # default is false +) + +# download onnx from sparsezoo and compile with batch size 2 +batch_size = 2 +sa_pipeline_b2 = Pipeline.create( + task="sentiment-analysis", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size, # default batch size is 1 + return_all_scores=True # default is false +) + +# run inference with b1 +sequences_b1 = ["Returning all scores is a cool configuration option"] +prediction_b1 = sa_pipeline_b1(sequences_b1) +print(prediction_b1) + +# run inference with b2 +sequences_b2 = sequences_b1 * batch_size +prediction_b2 = sa_pipeline_b2(sequences_b2) +print(prediction_b2) + +# >>> labels=[['positive', 'negative']] scores=[[0.9845395088195801, 0.015460520051419735]] +# >>> labels=[['positive', 'negative'], ['positive', 'negative']] scores=[[0.9845395088195801, 0.015460520051419735], [0.9845395088195801, 0.015460520051419735]] +``` + +### Cross Use Case Functionality + +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. + +## DeepSparse Server + +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set-up a REST endpoint +for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it +inherits all of the utilities provided by Pipelines. + +The CLI command below launches an sentiment analysis pipeline with a 90% pruned-quantized +BERT model identifed by its SparseZoo stub: + +```bash +deepsparse.server \ + --task sentiment-analysis \ + --model_path "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" # or path/to/onnx +``` + +You should see Uvicorn report that it is running on `http://0.0.0.0:5543`. Once launched, a `/docs` path is +created with full endpoint descriptions and support for making sample requests. + +Here is an example client request, using the Python `requests` library for formatting the HTTP: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"sequences": "Sending requests to DeepSparse Server is fast and easy!"} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# >> {"labels":["positive"],"scores":[0.9330279231071472]} +``` + +### Use Case Specific Arguments + +To use the `sequence_length` and `return_all_scores` arguments, we can a Server configuration file, passing the arguments via `kwargs` + +This configuration file sets sequence length to 64 and returns all scores: + +```yaml +# sentiment-analysis-config.yaml +endpoints: + - task: text-classification + model: zoo:nlp/document_classification/obert-base/pytorch/huggingface/imdb/pruned90_quant-none + kwargs: + sequence_length: 64 # uses sequence length 64 + return_all_scores: True # returns all scores +``` + +Spinning up: +```bash +deepsparse.server \ + --config-file sentiment-analysis-config.yaml +``` + +Making a request: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"sequences": "Sending requests to DeepSparse Server is fast and easy!"} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# >> {"labels":[["1","0"]],"scores":[[0.9941965341567993,0.005803497973829508]]} +``` + +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file From e9e268596ce671853d7aa46dabee29f36ef2405a Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:07:31 -0400 Subject: [PATCH 025/149] Update sentiment-analysis.md --- use-cases/nlp/sentiment-analysis.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/use-cases/nlp/sentiment-analysis.md b/use-cases/nlp/sentiment-analysis.md index 5623134e4f..a2c924bd65 100644 --- a/use-cases/nlp/sentiment-analysis.md +++ b/use-cases/nlp/sentiment-analysis.md @@ -1,10 +1,3 @@ ---- -title: "Deploy with DeepSparse" -metaTitle: "Deploy Sentiment Analysis Models on CPUs with DeepSparse" -metaDescription: "Deploy Sentiment Analysis Models on CPUs with DeepSparse" -index: 2000 ---- - # Deploying Sentiment Analysis Models with DeepSparse This page explains how to benchmark and deploy a sentiment analysis model with DeepSparse. @@ -27,7 +20,7 @@ Confirm your machine is compatible with our [hardware requirements](/user-guide/ ## Benchmarking We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 32 -core AWS c6i.8xlarge instance. +core AWS c6i.16xlarge instance. ### ONNX Runtime Baseline @@ -311,4 +304,4 @@ print(resp.text) ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. From dd5bdfd5fc4ed9fee17dc129ecbad0a0c4275735 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Thu, 16 Mar 2023 16:31:05 -0400 Subject: [PATCH 026/149] added installation --- user-guide/installation.md | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 user-guide/installation.md diff --git a/user-guide/installation.md b/user-guide/installation.md new file mode 100644 index 0000000000..170e7adb98 --- /dev/null +++ b/user-guide/installation.md @@ -0,0 +1,46 @@ +--- +title: "DeepSparse Community" +metaTitle: "DeepSparse Community Installation" +metaDescription: "Installation instructions for DeepSparse enabling performant neural network deployments" +index: 1000 +--- + +# DeepSparse Community Installation + +[DeepSparse Community](/products/deepsparse) enables GPU-class performance on commodity CPUs. + +Currently, DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.10.1, ONNX opset version 11+ and is [manylinux compliant](https://peps.python.org/pep-0513/). + +We currently support x86 CPU architectures. + +DeepSparse is available in two versions: +1. [**DeepSparse Community**](/products/deepsparse) is free for evaluation, research, and non-production use with our [DeepSparse Community License](https://neuralmagic.com/legal/engine-license-agreement/). +2. [**DeepSparse Enterprise**](/products/deepsparse-ent) requires a Trial License or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. + +## General Install + +Use the following command to install DeepSparse Community with pip: + +```bash +pip install deepsparse +``` + +## Installing the Server + +[DeepSparse Server](/user-guide/deploying-deepsparse/deepsparse-server) allows you to serve models and pipelines through an HTTP interface using the deepsparse.server CLI. +To install, use the following extra option: + +```bash +pip install deepsparse[server] +``` + +## Installing YOLO + +The [Ultralytics YOLOv5](/use-cases/object-detection/deploying) models require extra dependencies for deployment. +To use YOLO models, install with the following extra option: + +```bash +pip install deepsparse[yolo] # just yolo requirements +pip install deepsparse[yolo,server] # both yolo + server requirements +``` + From b84e01ee9c13e22972e7451e8d5852d7589f6e88 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:33:40 -0400 Subject: [PATCH 027/149] Update installation.md --- user-guide/installation.md | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/user-guide/installation.md b/user-guide/installation.md index 170e7adb98..aa5e3ab1c0 100644 --- a/user-guide/installation.md +++ b/user-guide/installation.md @@ -1,25 +1,10 @@ ---- -title: "DeepSparse Community" -metaTitle: "DeepSparse Community Installation" -metaDescription: "Installation instructions for DeepSparse enabling performant neural network deployments" -index: 1000 ---- +# DeepSparse Installation -# DeepSparse Community Installation - -[DeepSparse Community](/products/deepsparse) enables GPU-class performance on commodity CPUs. - -Currently, DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.10.1, ONNX opset version 11+ and is [manylinux compliant](https://peps.python.org/pep-0513/). - -We currently support x86 CPU architectures. - -DeepSparse is available in two versions: -1. [**DeepSparse Community**](/products/deepsparse) is free for evaluation, research, and non-production use with our [DeepSparse Community License](https://neuralmagic.com/legal/engine-license-agreement/). -2. [**DeepSparse Enterprise**](/products/deepsparse-ent) requires a Trial License or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. +DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.10.1, ONNX opset version 11+ and is [manylinux compliant](https://peps.python.org/pep-0513/). It currently supports Intel and AMD AVX2, AVX512, and VNNI x86 instruction sets. ## General Install -Use the following command to install DeepSparse Community with pip: +Use the following command to install DeepSparse with pip: ```bash pip install deepsparse @@ -27,7 +12,7 @@ pip install deepsparse ## Installing the Server -[DeepSparse Server](/user-guide/deploying-deepsparse/deepsparse-server) allows you to serve models and pipelines through an HTTP interface using the deepsparse.server CLI. +DeepSparse Server allows you to serve models and pipelines through an HTTP interface using the `deepsparse.server` CLI. To install, use the following extra option: ```bash @@ -36,11 +21,9 @@ pip install deepsparse[server] ## Installing YOLO -The [Ultralytics YOLOv5](/use-cases/object-detection/deploying) models require extra dependencies for deployment. -To use YOLO models, install with the following extra option: +The Ultralytics YOLOv5 models require extra dependencies for deployment. To use YOLO models, install with the following extra option: ```bash pip install deepsparse[yolo] # just yolo requirements pip install deepsparse[yolo,server] # both yolo + server requirements ``` - From c145ac4169346fff6030460099f81bdd432bec9d Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:33:52 -0400 Subject: [PATCH 028/149] Update installation.md --- user-guide/installation.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/user-guide/installation.md b/user-guide/installation.md index aa5e3ab1c0..59bc31b072 100644 --- a/user-guide/installation.md +++ b/user-guide/installation.md @@ -1,6 +1,8 @@ # DeepSparse Installation -DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.10.1, ONNX opset version 11+ and is [manylinux compliant](https://peps.python.org/pep-0513/). It currently supports Intel and AMD AVX2, AVX512, and VNNI x86 instruction sets. +DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.10.1, ONNX opset version 11+ and is [manylinux compliant](https://peps.python.org/pep-0513/). + +It currently supports Intel and AMD AVX2, AVX512, and VNNI x86 instruction sets. ## General Install From f978ba4e6771647452334b1676a4ece9d53d9235 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 16:59:04 -0400 Subject: [PATCH 029/149] Update README.md --- user-guide/README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/user-guide/README.md b/user-guide/README.md index fc48719c6c..4eca2a82c7 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -11,18 +11,18 @@ This directory demonstrates usage of DeepSparse's key API, including: Install via `pip`. Using a virtual enviornment is highly recommended. ```bash -pip install deepsparse +pip install deepsparse[server] ``` -See the [DeepSparse Installation page](https://docs.neuralmagic.com/get-started/install/deepsparse) for further installation options. +See the [installation page](installation.md) for further installation options. ## [Performance Benchmarking](deepsparse-benchmarking.md) -DeepSparse's key feature is its performance on commodity CPUs. DeepSparse is competitive with other CPU runtimes -like ONNX Runtime for unoptimized dense models. However, when optimization techniques like pruning and quantization -are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. As an example, let's compare DeepSparse and ORT's performance on BERT. In SparseZoo, there is [90% pruned-quantized BERT](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none). +DeepSparse's key feature is its performance on commodity CPUs. -Running this model on an AWS `c6i.16xlarge` instance, DeepSparse achieves a ***12x speedup*** over ORT! +For dense unoptimized models, DeepSparse is competitive with other CPU runtimes like ONNX Runtime. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. + +As an example, let's compare DeepSparse and ORT's performance on BERT using a [90% pruned-quantized version](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none) in SparseZoo on an AWS `c6i.16xlarge` instance (32 cores). ORT achieves 18.5 items/second running BERT (make sure you have ORT installed `pip install onnxruntime`): ```bash @@ -37,7 +37,7 @@ deepsparse.benchmark zoo:nlp/text_classification/obert-base/pytorch/huggingface/ DeepSparse achieves 226 items/second running the pruned-quantized version of BERT: ```bash -deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -b 64 -s sync -nstreams 1 -e onnxruntime +deepsparse.benchmark zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none -b 64 -s sync -nstreams 1 -i [64,384] >> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none >> Batch Size: 64 @@ -45,6 +45,8 @@ deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/s >> Throughput (items/sec): 226.6340 ``` +DeepSparse achieves a ***12x speedup*** over ORT! + **Pro-Tip:** In place of a [SparseZoo](https://sparsezoo.neuralmagic.com/) stubs, you can pass a local ONNX file to test your model. ## Deployment APIs @@ -107,7 +109,7 @@ wget https://github.com/onnx/models/raw/main/vision/classification/mobilenet/mod from deepsparse import compile_model from deepsparse.utils import generate_random_inputs onnx_filepath = "mobilenetv2-7.onnx" -batch_size = 16 +batch_size = 1 # Generate random sample input inputs = generate_random_inputs(onnx_filepath, batch_size) From 380cf6e2725b36a42cc39435c8e8769c683df273 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 17:06:19 -0400 Subject: [PATCH 030/149] Update deepsparse-pipelines.md --- user-guide/deepsparse-pipelines.md | 1 + 1 file changed, 1 insertion(+) diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index 1da5a2162c..42fd5011fe 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -73,6 +73,7 @@ sparsezoo.download zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/ We can create a custom image classification Pipeline which returns the raw logits and class probabilities for the 1000 ImageNet classes with the following: +For the purposes of this demo, make sure you have `torch` and `PIL` installed. ```python from deepsparse.pipelines.custom_pipeline import CustomTaskPipeline from torchvision import transforms From 643af49c9465b106d473edb31b2984464c3ddcb2 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Thu, 16 Mar 2023 17:11:55 -0400 Subject: [PATCH 031/149] Update deepsparse-pipelines.md --- user-guide/deepsparse-pipelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index 42fd5011fe..2093ebcb20 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -73,7 +73,7 @@ sparsezoo.download zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/ We can create a custom image classification Pipeline which returns the raw logits and class probabilities for the 1000 ImageNet classes with the following: -For the purposes of this demo, make sure you have `torch` and `PIL` installed. +For the purposes of this quick example, make sure you have `torch` `torchvision` and `Pillow` installed. ```python from deepsparse.pipelines.custom_pipeline import CustomTaskPipeline from torchvision import transforms From 12092eec5b9531e8ea08bf9c4497b7aa03929831 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Mon, 20 Mar 2023 16:46:54 +0300 Subject: [PATCH 032/149] add text classification doc --- use-cases/nlp/text-classification.mdx | 401 ++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 use-cases/nlp/text-classification.mdx diff --git a/use-cases/nlp/text-classification.mdx b/use-cases/nlp/text-classification.mdx new file mode 100644 index 0000000000..7b101c7a38 --- /dev/null +++ b/use-cases/nlp/text-classification.mdx @@ -0,0 +1,401 @@ +# Deploying Text Classification Models with DeepSparse + +This page explains how to benchmark and deploy a text classification model with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API. It enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 23-core server. + +### ONNX Runtime Baseline + +As a baseline, let's check out ONNX Runtime's performance on oBERT. Make sure you have ORT installed (`pip install onnxruntime`). +```bash +deepsparse.benchmark \ + zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e onnxruntime + +> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 10.7702 +> Latency Mean (ms/batch): 5942.3135 +> Latency Median (ms/batch): 5942.3135 +> Latency Std (ms/batch): 309.5893 +> Iterations: 2 +``` +ONNX Runtime achieves 11 items/second with batch 64 and sequence length 384. + +### DeepSparse Speedup +Now, let's run DeepSparse on an inference-optimized sparse version of oBERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the MNLI dataset. +```bash +!deepsparse.benchmark \ + zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e deepsparse + +> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 84.7056 +> Latency Mean (ms/batch): 755.5426 +> Latency Median (ms/batch): 758.5148 +> Latency Std (ms/batch): 5.9118 +> Iterations: 14 +``` +DeepSparse achieves 85 items/second, an 7.7x speed-up over ONNX Runtime! + +## DeepSparse Engine +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended you use the Pipeline API but Engine is available as needed if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 90% pruned-quantized oBERT trained on MNLI from SparseZoo: +```python +from deepsparse import compile_model +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +batch_size = 1 +bert_engine = compile_model( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) + +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = bert_engine(inputs) +print(output) +# [array([[-0.9264987, -1.6990623, 2.3935342]], dtype=float32)] + +``` +## DeepSparse Pipelines +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +We will use the `Pipeline.create()` constructor to create an instance of a text classification Pipeline with a 90% pruned-quantized version of oBERT trained on MNLI. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +batch_size = 1 +pipeline = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference on image file +prediction = pipeline("The text classification pipeline is fast and easy to use") +print(prediction) +# labels=['entailment'] scores=[0.5807693004608154] + +``` +#### Zero Shot Classification +Given certain categories, zero shot classification aims at determining the class that best fits the given text. + +Here's an example of a zero shot text classification example with a DistilBERT that's 80% pruned and quantized on the MNLI dataset. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/mnli/pruned80_quant-none-vnni" +batch_size = 1 +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path="zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/mnli/pruned80_quant-none-vnni", + ) + +# run inference on image file +prediction = pipeline(sequences = "Today is election day in America",labels=['politics', 'public health', 'Europe'],) +print(prediction) +# sequences='Today is election day in America' labels=['politics', 'Europe', 'public health'] scores=[0.8922364115715027, 0.06215662881731987, 0.04560691863298416] +``` +### Example with QQP +[QQP( Quora Question Pairs2)](https://huggingface.co/datasets/glue) is a dataset that is part of the GLUE benchmark. The goal is to determine if a pair of questions are semantically equivalent. + +Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90%(90% of the weights have been removed without loss of accuracy) and quantized on the QQP dataset. +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/qqp/pruned90_quant-none" +batch_size = 1 +pipeline = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference on image file +prediction = pipeline([[ + "Should I have a hair transplant at age 24? How much would it cost?", + "How much cost does hair transplant require?", + ]]) +print(prediction) +# labels=['not_duplicate'] scores=[0.6760590076446533] + +``` +### Example with MNLI +[MNLI( Multi-Genre Natural Language Inference)](https://huggingface.co/datasets/glue) is a dataset with textual entailment annotations. The goal is to predict entailment, contradiction and neutrality given a premise and hypothesis. + +Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90%(90% of the weights have been removed without loss of accuracy) and quantized on the MNLI dataset. +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +batch_size = 1 +pipeline = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference on image file +prediction = pipeline([[ + "Timely access to information is in the best interests of both GAO and the agencies", + "It is in everyone's best interest to have access to information in a timely manner", + ]]) +print(prediction) +# labels=['entailment'] scores=[0.9688315987586975] + +``` + +### Example with Document Classification +Document classification involves classifying text in a long document. + +Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90% and quantized on the IMDB dataset. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/document_classification/obert-base/pytorch/huggingface/imdb/pruned90_quant-none" +pipeline = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference on image file +prediction = pipeline(["Phil the Alien is one of those quirky films where the humour is based around the oddness of everything rather than actual punchlines.

At first it was very odd and pretty funny but as the movie progressed I didn't find the jokes or oddness funny anymore.

Its a low budget film (thats never a problem in itself), there were some pretty interesting characters, but eventually I just lost interest.

I imagine this film would appeal to a stoner who is currently partaking.

For something similar but better try Brother from another planet"]) +print(prediction) +# labels=['0'] scores=[0.9986200332641602] + + +``` +### Example with GoEmotions +[The GoEmotions](https://huggingface.co/datasets/go_emotions) dataset contains Reddit comments labeled for 27 emotion categories or Neutral. The goal is to perform multi-class, multi-label emotion classification. + +Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90% and quantized on the GoEmotions dataset. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/multilabel_text_classification/obert-base/pytorch/huggingface/goemotions/pruned90-none" +pipeline = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference on image file +prediction = pipeline(["Thank you for asking questions and recognizing that there may be things that you don’t know or understand about police tactics. Seriously. Thank you."]) +print(prediction) +#labels=['gratitude'] scores=[0.9986923336982727] + +``` +### Use Case Specific Arguments +The Text Classification Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Sequence Length +The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. + +The example below compiles the model and runs inference with sequence length 64. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/mnli/pruned80_quant-none-vnni" +batch_size = 1 +sequence_length = 64 +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path=sparsezoo_stub, + sequence_length=sequence_length, + batch_size =batch_size, + ) + +# run inference on image file +prediction = pipeline(sequences = "Today is election day",labels=['politics', 'public health', 'Europe'],) +print(prediction) +# sequences='Today is election day' labels=['politics', 'Europe', 'public health'] scores=[0.9697986245155334, 0.01720993034541607, 0.012991504743695259] +``` +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. + +The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +batch_size = 1 +buckets = [16, 128] +pipeline = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, + sequence_length=buckets # creates bucketed pipeline +) +# run inference on image file +prediction = pipeline([[ + "Timely access to information is in the best interests of both GAO and the agencies", + "It is in everyone's best interest to have access to information in a timely manner", + ]]) +print(prediction) + +# run inference on image file +prediction = pipeline([[ + "Timely access to information is in the best interests of both GAO and the agencies. Let's make information more accessible", + "It is in everyone's best interest to have access to information in a timely manner. Information should be made more accessible.", + ]]) +print(prediction) +#labels=['entailment'] scores=[0.9688315987586975] +#labels=['entailment'] scores=[0.985545814037323] + +``` + +#### Return All Scores +The `return_all_scores` argument allows you to specify whether to return the prediction as the `argmax` of class predictions or to return all scores as a list for each result in the batch. + +Here is an example with batch size 1 and batch size 2: +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +pipeline_b1 = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + return_all_scores=True # default is false +) + +# download onnx from sparsezoo and compile with batch size 2 +pipeline_b2 = Pipeline.create( + task="text-classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=2, # default batch size is 1 + return_all_scores=True # default is false +) + +# run inference with b1 +sequences_b1 = [[ + "Timely access to information is in the best interests of both GAO and the agencies", + "It is in everyone's best interest to have access to information in a timely manner", + ]] +prediction_b1 = pipeline_b1(sequences_b1) +print(prediction_b1) + +# run inference with b2 +sequences_b2 = sequences_b1 * batch_size +prediction_b2 = pipeline_b2(sequences_b2) +print(prediction_b2) +# labels=[['entailment', 'neutral', 'contradiction']] scores=[[0.9688315987586975, 0.030656637623906136, 0.0005117706023156643]] +# labels=[['entailment', 'neutral', 'contradiction'], ['entailment', 'neutral', 'contradiction']] scores=[[0.9688315987586975, 0.030656637623906136, 0.0005117706023156643], [0.9688315987586975, 0.030656637623906136, 0.0005117706023156643]] +``` +### Cross Use Case Functionality +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +## DeepSparse Server +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. + +The CLI command below launches a text classification pipeline with a 90% pruned-quantized oBERT model: + +```bash +deepsparse.server \ + --task sentiment-analysis \ + --model_path "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" # or path/to/onnx + +``` +You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. + +Here is an example client request, using the Python requests library for formatting the HTTP: + +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"sequences": "Sending requests to DeepSparse Server is fast and easy!"} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# {"labels":["entailment"],"scores":[0.5475465655326843]} + +``` +### Use Case Specific Arguments +To use the `sequence_length` and `return_all_scores` arguments, create a Server configuration file for passing the arguments via kwargs. + +This configuration file sets sequence length to 64 and returns all scores: +```yaml + # text-classification-config.yaml +endpoints: + - task: text-classification + model: zoo:nlp/document_classification/obert-base/pytorch/huggingface/imdb/pruned90_quant-none + kwargs: + sequence_length: 64 # uses sequence length 64 + return_all_scores: True # returns all scores +``` +Spin up the server: + +```bash + +deepsparse.server \ + --config-filetext-classification-config.yaml +``` +Making a request: + +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"sequences": "Sending requests to DeepSparse Server is fast and easy!"} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# {"labels":[["1","0"]],"scores":[[0.9941965341567993,0.005803497973829508]]} + +``` +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. From 13ac283d81299be34be7b07a3c428c5ff6ebe9f3 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Tue, 21 Mar 2023 09:02:32 +0300 Subject: [PATCH 033/149] add text classification doc --- use-cases/nlp/{sentiment-analysis.md => sentiment-analysis.mdx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename use-cases/nlp/{sentiment-analysis.md => sentiment-analysis.mdx} (100%) diff --git a/use-cases/nlp/sentiment-analysis.md b/use-cases/nlp/sentiment-analysis.mdx similarity index 100% rename from use-cases/nlp/sentiment-analysis.md rename to use-cases/nlp/sentiment-analysis.mdx From 3c256d2410d237f1eb2c085d2647b6570689f17e Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Tue, 21 Mar 2023 09:12:37 +0300 Subject: [PATCH 034/149] add text classification doc --- use-cases/nlp/{sentiment-analysis.mdx => sentiment-analysis.md} | 0 use-cases/nlp/{text-classification.mdx => text-classification.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename use-cases/nlp/{sentiment-analysis.mdx => sentiment-analysis.md} (100%) rename use-cases/nlp/{text-classification.mdx => text-classification.md} (100%) diff --git a/use-cases/nlp/sentiment-analysis.mdx b/use-cases/nlp/sentiment-analysis.md similarity index 100% rename from use-cases/nlp/sentiment-analysis.mdx rename to use-cases/nlp/sentiment-analysis.md diff --git a/use-cases/nlp/text-classification.mdx b/use-cases/nlp/text-classification.md similarity index 100% rename from use-cases/nlp/text-classification.mdx rename to use-cases/nlp/text-classification.md From eaaca7a55e67e014b10904014e83e822702460dd Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Tue, 21 Mar 2023 09:25:17 +0300 Subject: [PATCH 035/149] Use Engine --- use-cases/nlp/sentiment-analysis.md | 4 ++-- use-cases/nlp/text-classification.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/use-cases/nlp/sentiment-analysis.md b/use-cases/nlp/sentiment-analysis.md index a2c924bd65..9ac3ea70cf 100644 --- a/use-cases/nlp/sentiment-analysis.md +++ b/use-cases/nlp/sentiment-analysis.md @@ -69,14 +69,14 @@ With Engine, we can compile an ONNX file and run inference on raw tensors. Here's an example, using a 90% pruned-quantized BERT trained on SST2 from SparseZoo: ```python -from deepsparse import compile_model +from deepsparse import Engine from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" batch_size = 1 -bert_engine = compile_model( +bert_engine = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) diff --git a/use-cases/nlp/text-classification.md b/use-cases/nlp/text-classification.md index 7b101c7a38..ec892220c5 100644 --- a/use-cases/nlp/text-classification.md +++ b/use-cases/nlp/text-classification.md @@ -67,14 +67,14 @@ With Engine, we can compile an ONNX file and run inference on raw tensors. Here's an example, using a 90% pruned-quantized oBERT trained on MNLI from SparseZoo: ```python -from deepsparse import compile_model +from deepsparse import Engine from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" batch_size = 1 -bert_engine = compile_model( +bert_engine = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) From 1b5cf027cdcf73a5e087610f9b5a0ca750036381 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Tue, 21 Mar 2023 16:08:39 +0300 Subject: [PATCH 036/149] add question answering document --- use-cases/nlp/question-answering.md | 234 ++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 use-cases/nlp/question-answering.md diff --git a/use-cases/nlp/question-answering.md b/use-cases/nlp/question-answering.md new file mode 100644 index 0000000000..ce835d0f2f --- /dev/null +++ b/use-cases/nlp/question-answering.md @@ -0,0 +1,234 @@ +# Deploying Question Answering Models with DeepSparse +This page explains how to benchmark and deploy a question answering model with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API. It enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 23-core server. + +### ONNX Runtime Baseline + +As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you have ORT installed (`pip install onnxruntime`). +````bash +deepsparse.benchmark \ + zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e onnxruntime +> Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 9.3279 +> Latency Mean (ms/batch): 6861.1296 +> Latency Median (ms/batch): 6861.1296 +> Latency Std (ms/batch): 876.7494 +> Iterations: 2 +```` + +ONNX Runtime achieves 9 items/second with batch 64 and sequence length 384. + +## DeepSparse Engine +Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the [SQuAD](https://huggingface.co/datasets/squad) dataset. +```bash +deepsparse.benchmark \ + zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e deepsparse + + > Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 83.3859 +> Latency Mean (ms/batch): 767.5012 +> Latency Median (ms/batch): 767.5222 +> Latency Std (ms/batch): 3.5643 +> Iterations: 14 +``` +DeepSparse achieves 83 items/second, an 9.2x speed-up over ONNX Runtime! +## DeepSparse Engine +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 90% pruned-quantized BERT trained on SQuAD from SparseZoo: +```python +from deepsparse import Engine +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +batch_size = 1 +bert_engine = Engine( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) + +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = bert_engine(inputs) +print(output) +#[array([[-6.904723 , -7.2960553, -6.903628 , -6.930577 , -6.899986 , +# ..... +# -6.555915 , -6.6454444, -6.4477777, -6.8030496]], dtype=float32)] +``` +## DeepSparse Pipelines +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +We will use the `Pipeline.create()` constructor to create an instance of a question answering Pipeline with a 90% pruned-quantized version of BERT trained on SQuAD. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. +```python + +from deepsparse import Pipeline +task = "question-answering" +qa_pipeline = Pipeline.create( + task=task, + model_path="zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none", + ) +q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" +question = "What is DeepSparse?" +output = qa_pipeline(question=question, context=q_context) +print(output) +# QuestionAnsweringOutput(score=23.620140075683594, answer='sparsity-aware inference runtime', start=14, end=46) +``` +#### Use Case Specific Arguments +The Question Answering Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Sequence Length +The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. + +The example below compiles the model and runs inference with sequence length 64. +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +batch_size = 1 +task = "question-answering" +sequence_length = 64 +pipeline = Pipeline.create( + task=task, + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + sequence_length=sequence_length, + batch_size =batch_size, # default batch size is 1 + ) + +# run inference on image file +q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" +question = "What is DeepSparse?" +output = qa_pipeline(question=question, context=q_context) +print(output) +# QuestionAnsweringOutput(score=23.620140075683594, answer='sparsity-aware inference runtime', start=14, end=46) +``` +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. + +The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +batch_size = 1 +task = "question-answering" +buckets = [16, 128] + +pipeline = Pipeline.create( + task=task, + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + sequence_length=buckets, # creates bucketed pipeline + batch_size =batch_size, # default batch size is 1 + ) + +# run inference on image file +q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" +question = "What is DeepSparse?" +output = qa_pipeline(question=question, context=q_context) +print(output) +# QuestionAnsweringOutput(score=23.620140075683594, answer='sparsity-aware inference runtime', start=14, end=46) +``` +### Cross Use Case Functionality +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +## DeepSparse Server +DeepSparse Server is built on top of FastAPI and Uvicorn, enabling you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. + +The CLI command below launches a question answering pipeline with a 90% pruned-quantized BERT model: + +```bash +deepsparse.server \ + --task question-answering \ + --model_path "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" # or path/to/onnx +``` +You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. + +Here is an example client request, using the Python requests library for formatting the HTTP: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = { + "question": "What is DeepSparse?", + "context": "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application", +} + +resp = requests.post(url=url, json=obj) + +# receive the post-processed output +print(resp.text) +# {"score":23.620140075683594,"answer":"sparsity-aware inference runtime","start":14,"end":46} +``` +#### Use Case Specific Arguments +To use the `sequence_length` argument, create a server configuration file for passing the arguments via `kwargs`. + +This configuration file sets sequence length to 64: +```yaml +# question-answering-config.yaml +endpoints: + - task: question-answering + model: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none + kwargs: + sequence_length: 64 # uses sequence length 64 +``` +Spin up the server: + +```bash +deepsparse.server \ + --config-file question-answering-config.yaml +``` +Making a request: +```python +import requests + +# Uvicorn is running on this port +url = "http://localhost:5543/predict" + +# send the data +obj = { + "question": "Who is Mark?", + "context": "Mark is batman." +} + +response = requests.post(url, json=obj) +# receive the post-processed output +print(response.text) +# {"score":22.506305694580078,"answer":"batman","start":8,"end":14} +``` +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file From dd2bed812aa094d22e96deb9bd04b422688af4a3 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Wed, 22 Mar 2023 11:20:49 +0300 Subject: [PATCH 037/149] add token classification document --- use-cases/nlp/question-answering.md | 4 +- use-cases/nlp/token-classification.md | 210 ++++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 use-cases/nlp/token-classification.md diff --git a/use-cases/nlp/question-answering.md b/use-cases/nlp/question-answering.md index ce835d0f2f..cde4a51acb 100644 --- a/use-cases/nlp/question-answering.md +++ b/use-cases/nlp/question-answering.md @@ -105,7 +105,7 @@ output = qa_pipeline(question=question, context=q_context) print(output) # QuestionAnsweringOutput(score=23.620140075683594, answer='sparsity-aware inference runtime', start=14, end=46) ``` -#### Use Case Specific Arguments +### Use Case Specific Arguments The Question Answering Pipeline contains additional arguments for configuring a `Pipeline`. #### Sequence Length @@ -231,4 +231,4 @@ print(response.text) ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. diff --git a/use-cases/nlp/token-classification.md b/use-cases/nlp/token-classification.md new file mode 100644 index 0000000000..61a577cfd5 --- /dev/null +++ b/use-cases/nlp/token-classification.md @@ -0,0 +1,210 @@ +# Deploying Token Classification Models with DeepSparse + +This page explains how to benchmark and deploy a token classification model with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 23-core server. + +### ONNX Runtime Baseline +As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you have ORT installed (`pip install onnxruntime`). + +```bash +deepsparse.benchmark \ + zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/base-none \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e onnxruntime + +> Original Model Path: zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/base-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 12.2691 +> Latency Mean (ms/batch): 5216.3342 +> Latency Median (ms/batch): 5216.3342 +> Latency Std (ms/batch): 27.7928 +> Iterations: 2 +``` +ONNX Runtime achieves 12 items/second with batch 64 and sequence length 384. +## DeepSparse Speedup +Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 80% pruned and quantized, while retaining >99% accuracy of the dense baseline on the [conll2003](https://huggingface.co/datasets/conll2003) dataset. +```bash +deepsparse.benchmark \ + zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni \ + -b 64 -s sync -nstreams 1 -i [64,384] \ + -e deepsparse +> Original Model Path: zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 99.7367 +> Latency Mean (ms/batch): 641.6757 +> Latency Median (ms/batch): 641.0878 +> Latency Std (ms/batch): 4.0909 +> Iterations: 16 +``` +DeepSparse achieves 100 items/second, a 8x speed-up over ONNX Runtime! +## DeepSparse Engine +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 80% pruned-quantized BERT trained on conll2003 from SparseZoo: +```python +from deepsparse import Engine +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" +batch_size = 1 +bert_engine = Engine( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) + +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = bert_engine(inputs) +print(output) +# array([[[ 2.0983224 , 1.2409506 , -1.7314302 , ..., -0.07210742, +#... +# -2.0502508 , -2.956191 ]]], dtype=float32)] +``` +## DeepSparse Pipelines +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +We will use the `Pipeline.create()` constructor to create an instance of a token classification Pipeline with a 80% pruned-quantized version of BERT trained on conll2003. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. +```python +from deepsparse import Pipeline +task = "ner" +model_path = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" +pipeline = Pipeline.create( + task=task, + model_path=model_path, + ) +output = pipeline("Mary is flying from Nairobi to New York") +print(output) +# predictions=[[TokenClassificationResult(entity='LABEL_1', score=0.9949890971183777, word='mary', start=0, end=4, index=1, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997545480728149, word='is', start=5, end=7, index=2, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997464418411255, word='flying', start=8, end=14, index=3, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997068643569946, word='from', start=15, end=19, index=4, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9992954730987549, word='nairobi', start=20, end=27, index=5, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997722506523132, word='to', start=28, end=30, index=6, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9994122385978699, word='new', start=31, end=34, index=7, is_grouped=False), TokenClassificationResult(entity='LABEL_6', score=0.9990378022193909, word='york', start=35, end=39, index=8, is_grouped=False)]] +``` +### Use Case Specific Arguments +The Token Classification Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Sequence Length +The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. + +The example below compiles the model and runs inference with sequence length of 64. +```python +from deepsparse import Pipeline +task = "ner" +sequence_length = 64 +model_path = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" + +pipeline = Pipeline.create( + task=task, + model_path=model_path, + sequence_length = sequence_length, + ) +output = pipeline("Mary is flying from Nairobi to New York to attend a conference on generative AI") +print(output) +# predictions=[[TokenClassificationResult(entity='LABEL_1', score=0.9950078129768372, word='mary', start=0, end=4, index=1, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999826192855835, word='is', start=5, end=7, index=2, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998066425323486, word='flying', start=8, end=14, index=3, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998202323913574, word='from', start=15, end=19, index=4, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9993807077407837, word='nairobi', start=20, end=27, index=5, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999809205532074, word='to', start=28, end=30, index=6, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.999479353427887, word='new', start=31, end=34, index=7, is_grouped=False), TokenClassificationResult(entity='LABEL_6', score=0.9990516901016235, word='york', start=35, end=39, index=8, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998992085456848, word='to', start=40, end=42, index=9, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999877393245697, word='attend', start=43, end=49, index=10, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998823404312134, word='a', start=50, end=51, index=11, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999748945236206, word='conference', start=52, end=62, index=12, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999583899974823, word='on', start=63, end=65, index=13, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.5017374753952026, word='genera', start=66, end=72, index=14, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.892431378364563, word='##tive', start=72, end=76, index=15, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.9190302491188049, word='ai', start=77, end=79, index=16, is_grouped=False)]] +``` +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. + +The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). +```python +from deepsparse import Pipeline +task = "ner" +buckets = [16, 128] +model_path = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" + +pipeline = Pipeline.create( + task=task, + model_path=model_path, + sequence_length = buckets, + ) +output = pipeline("Mary is flying from Nairobi to New York to attend a conference on generative AI") +print(output) +# predictions=[[TokenClassificationResult(entity='LABEL_1', score=0.9950078129768372, word='mary', start=0, end=4, index=1, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999826192855835, word='is', start=5, end=7, index=2, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998066425323486, word='flying', start=8, end=14, index=3, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998202323913574, word='from', start=15, end=19, index=4, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9993807077407837, word='nairobi', start=20, end=27, index=5, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999809205532074, word='to', start=28, end=30, index=6, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.999479353427887, word='new', start=31, end=34, index=7, is_grouped=False), TokenClassificationResult(entity='LABEL_6', score=0.9990516901016235, word='york', start=35, end=39, index=8, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998992085456848, word='to', start=40, end=42, index=9, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999877393245697, word='attend', start=43, end=49, index=10, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998823404312134, word='a', start=50, end=51, index=11, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999748945236206, word='conference', start=52, end=62, index=12, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999583899974823, word='on', start=63, end=65, index=13, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.5017374753952026, word='genera', start=66, end=72, index=14, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.892431378364563, word='##tive', start=72, end=76, index=15, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.9190302491188049, word='ai', start=77, end=79, index=16, is_grouped=False)]] +``` +### Cross Use Case Functionality +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +## DeepSparse Server +DeepSparse Server is built on top of FastAPI and Uvicorn, enabling you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. + +The CLI command below launches a token classification pipeline with a 80% pruned-quantized BERT model: +```bash +deepsparse.server +--task ner +--model_path "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" # or path/to/onnx +``` +You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. + +Here is an example client request, using the Python requests library for formatting the HTTP: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' +# send the data +obj = { + "inputs": "Mary is flying from Nairobi to New York to attend a conference on generative AI", +} +resp = requests.post(url=url, json=obj) +# receive the post-processed output +print(resp.text) +# {"predictions":[[{"entity":"LABEL_1","score":0.9950078129768372,"index":1,"word":"mary","start":0,"end":4,"is_grouped":false},{"entity":"LABEL_0","score":0.999826192855835,"index":2,"word":"is","start":5,"end":7,"is_grouped":false},{"entity":"LABEL_0","score":0.9998066425323486,"index":3,"word":"flying","start":8,"end":14,"is_grouped":false},{"entity":"LABEL_0","score":0.9998202323913574,"index":4,"word":"from","start":15,"end":19,"is_grouped":false},{"entity":"LABEL_5","score":0.9993807077407837,"index":5,"word":"nairobi","start":20,"end":27,"is_grouped":false},{"entity":"LABEL_0","score":0.999809205532074,"index":6,"word":"to","start":28,"end":30,"is_grouped":false},{"entity":"LABEL_5","score":0.999479353427887,"index":7,"word":"new","start":31,"end":34,"is_grouped":false},{"entity":"LABEL_6","score":0.9990516901016235,"index":8,"word":"york","start":35,"end":39,"is_grouped":false},{"entity":"LABEL_0","score":0.9998992085456848,"index":9,"word":"to","start":40,"end":42,"is_grouped":false},{"entity":"LABEL_0","score":0.999877393245697,"index":10,"word":"attend","start":43,"end":49,"is_grouped":false},{"entity":"LABEL_0","score":0.9998823404312134,"index":11,"word":"a","start":50,"end":51,"is_grouped":false},{"entity":"LABEL_0","score":0.999748945236206,"index":12,"word":"conference","start":52,"end":62,"is_grouped":false},{"entity":"LABEL_0","score":0.999583899974823,"index":13,"word":"on","start":63,"end":65,"is_grouped":false},{"entity":"LABEL_0","score":0.5017374753952026,"index":14,"word":"genera","start":66,"end":72,"is_grouped":false},{"entity":"LABEL_8","score":0.892431378364563,"index":15,"word":"##tive","start":72,"end":76,"is_grouped":false},{"entity":"LABEL_8","score":0.9190302491188049,"index":16,"word":"ai","start":77,"end":79,"is_grouped":false}]]} +``` +#### Use Case Specific Arguments +To use the `sequence_length` argument, create a server configuration file for passing the arguments via `kwargs`. + +This configuration file sets sequence length to 64: +```yaml +# ner-config.yaml +endpoints: + - task: ner + model: zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni + kwargs: + sequence_length: 64 # uses sequence length 64 +``` +Spin up the server: + +```bash +deepsparse.server \ + --config-file ner-config.yaml +``` +Making a request: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = { + "inputs": "Mary is flying from Nairobi to New York to attend a conference on generative AI", +} + +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# {"predictions":[[{"entity":"LABEL_1","score":0.9950078129768372,"index":1,"word":"mary","start":0,"end":4,"is_grouped":false},{"entity":"LABEL_0","score":0.999826192855835,"index":2,"word":"is","start":5,"end":7,"is_grouped":false},{"entity":"LABEL_0","score":0.9998066425323486,"index":3,"word":"flying","start":8,"end":14,"is_grouped":false},{"entity":"LABEL_0","score":0.9998202323913574,"index":4,"word":"from","start":15,"end":19,"is_grouped":false},{"entity":"LABEL_5","score":0.9993807077407837,"index":5,"word":"nairobi","start":20,"end":27,"is_grouped":false},{"entity":"LABEL_0","score":0.999809205532074,"index":6,"word":"to","start":28,"end":30,"is_grouped":false},{"entity":"LABEL_5","score":0.999479353427887,"index":7,"word":"new","start":31,"end":34,"is_grouped":false},{"entity":"LABEL_6","score":0.9990516901016235,"index":8,"word":"york","start":35,"end":39,"is_grouped":false},{"entity":"LABEL_0","score":0.9998992085456848,"index":9,"word":"to","start":40,"end":42,"is_grouped":false},{"entity":"LABEL_0","score":0.999877393245697,"index":10,"word":"attend","start":43,"end":49,"is_grouped":false},{"entity":"LABEL_0","score":0.9998823404312134,"index":11,"word":"a","start":50,"end":51,"is_grouped":false},{"entity":"LABEL_0","score":0.999748945236206,"index":12,"word":"conference","start":52,"end":62,"is_grouped":false},{"entity":"LABEL_0","score":0.999583899974823,"index":13,"word":"on","start":63,"end":65,"is_grouped":false},{"entity":"LABEL_0","score":0.5017374753952026,"index":14,"word":"genera","start":66,"end":72,"is_grouped":false},{"entity":"LABEL_8","score":0.892431378364563,"index":15,"word":"##tive","start":72,"end":76,"is_grouped":false},{"entity":"LABEL_8","score":0.9190302491188049,"index":16,"word":"ai","start":77,"end":79,"is_grouped":false}]]} +``` +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file From 2108ed67cbb662506c06f8aefd64a3db96e98104 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Wed, 22 Mar 2023 11:32:50 +0300 Subject: [PATCH 038/149] update benchmarks --- use-cases/nlp/question-answering.md | 30 +++++++++++++-------------- use-cases/nlp/text-classification.md | 30 +++++++++++++-------------- use-cases/nlp/token-classification.md | 2 +- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/use-cases/nlp/question-answering.md b/use-cases/nlp/question-answering.md index cde4a51acb..e4290fc11e 100644 --- a/use-cases/nlp/question-answering.md +++ b/use-cases/nlp/question-answering.md @@ -18,7 +18,7 @@ Confirm your machine is compatible with our [hardware requirements](/user-guide/ ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 23-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. ### ONNX Runtime Baseline @@ -28,36 +28,36 @@ deepsparse.benchmark \ zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none \ -b 64 -s sync -nstreams 1 -i [64,384] \ -e onnxruntime -> Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none +> Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 9.3279 -> Latency Mean (ms/batch): 6861.1296 -> Latency Median (ms/batch): 6861.1296 -> Latency Std (ms/batch): 876.7494 -> Iterations: 2 +> Throughput (items/sec): 13.1489 +> Latency Mean (ms/batch): 4867.3156 +> Latency Median (ms/batch): 4834.5695 +> Latency Std (ms/batch): 51.7144 +> Iterations: 3 ```` -ONNX Runtime achieves 9 items/second with batch 64 and sequence length 384. +ONNX Runtime achieves 13 items/second with batch 64 and sequence length 384. ## DeepSparse Engine Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the [SQuAD](https://huggingface.co/datasets/squad) dataset. ```bash deepsparse.benchmark \ - zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none \ + zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/base-none \ -b 64 -s sync -nstreams 1 -i [64,384] \ -e deepsparse - > Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none +> Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 83.3859 -> Latency Mean (ms/batch): 767.5012 -> Latency Median (ms/batch): 767.5222 -> Latency Std (ms/batch): 3.5643 +> Throughput (items/sec): 89.1442 +> Latency Mean (ms/batch): 717.9248 +> Latency Median (ms/batch): 717.2859 +> Latency Std (ms/batch): 4.5779 > Iterations: 14 ``` -DeepSparse achieves 83 items/second, an 9.2x speed-up over ONNX Runtime! +DeepSparse achieves 89 items/second, an 7x speed-up over ONNX Runtime! ## DeepSparse Engine Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. diff --git a/use-cases/nlp/text-classification.md b/use-cases/nlp/text-classification.md index ec892220c5..01a26473bf 100644 --- a/use-cases/nlp/text-classification.md +++ b/use-cases/nlp/text-classification.md @@ -19,27 +19,27 @@ Confirm your machine is compatible with our [hardware requirements](/user-guide/ ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 23-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. ### ONNX Runtime Baseline As a baseline, let's check out ONNX Runtime's performance on oBERT. Make sure you have ORT installed (`pip install onnxruntime`). ```bash deepsparse.benchmark \ - zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none \ + zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/base-none \ -b 64 -s sync -nstreams 1 -i [64,384] \ -e onnxruntime -> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none +> Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 10.7702 -> Latency Mean (ms/batch): 5942.3135 -> Latency Median (ms/batch): 5942.3135 -> Latency Std (ms/batch): 309.5893 -> Iterations: 2 +> Throughput (items/sec): 13.0320 +> Latency Mean (ms/batch): 4910.9819 +> Latency Median (ms/batch): 4900.1473 +> Latency Std (ms/batch): 63.6517 +> Iterations: 3 ``` -ONNX Runtime achieves 11 items/second with batch 64 and sequence length 384. +ONNX Runtime achieves 13 items/second with batch 64 and sequence length 384. ### DeepSparse Speedup Now, let's run DeepSparse on an inference-optimized sparse version of oBERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the MNLI dataset. @@ -52,13 +52,13 @@ Now, let's run DeepSparse on an inference-optimized sparse version of oBERT. Thi > Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 84.7056 -> Latency Mean (ms/batch): 755.5426 -> Latency Median (ms/batch): 758.5148 -> Latency Std (ms/batch): 5.9118 -> Iterations: 14 +> Throughput (items/sec): 89.7097 +> Latency Mean (ms/batch): 713.3987 +> Latency Median (ms/batch): 710.9072 +> Latency Std (ms/batch): 8.0025 +> Iterations: 15 ``` -DeepSparse achieves 85 items/second, an 7.7x speed-up over ONNX Runtime! +DeepSparse achieves 85 items/second, an 7x speed-up over ONNX Runtime! ## DeepSparse Engine Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended you use the Pipeline API but Engine is available as needed if you want to handle pre- or post-processing yourself. diff --git a/use-cases/nlp/token-classification.md b/use-cases/nlp/token-classification.md index 61a577cfd5..b1cadbafd6 100644 --- a/use-cases/nlp/token-classification.md +++ b/use-cases/nlp/token-classification.md @@ -18,7 +18,7 @@ Confirm your machine is compatible with our [hardware requirements](/user-guide/ ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 23-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. ### ONNX Runtime Baseline As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you have ORT installed (`pip install onnxruntime`). From d05713049f0eb409cca4803cd8ad6361d1b4dc89 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Wed, 22 Mar 2023 12:52:11 +0300 Subject: [PATCH 039/149] add transformers extraction embedding doc --- .../nlp/transformers-embedding-extraction.md | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 use-cases/nlp/transformers-embedding-extraction.md diff --git a/use-cases/nlp/transformers-embedding-extraction.md b/use-cases/nlp/transformers-embedding-extraction.md new file mode 100644 index 0000000000..88ac0133a2 --- /dev/null +++ b/use-cases/nlp/transformers-embedding-extraction.md @@ -0,0 +1,150 @@ +# Deploying Transformers Embedding Extraction Models with DeepSparse + +This page explains how to deploy a transformers embedding extraction Pipeline with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## DeepSparse Pipelines +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +We will use the `Pipeline.create()` constructor to create an instance of an embedding extraction Pipeline with a 80% pruned-quantized version of BERT trained on `wikipedia_bookcorpus`. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. + +With Transformers, you can use `task=transformer_embedding_extraction` for some extra utilities associated with embedding extraction. + +The first utility is automatic embedding layer detection. If you set `emb_extraction_layer=-1` (the default), the Pipeline automatically detects the final transformer layer before the projection head and removes the projection head for you. + +The second utility is automatic dimensionality reduction. You can use the `extraction_strategy` to perform a reduction on the sequence dimension rather than returning an embedding for each token. The options are: + +- `per_token`: returns the embedding for each token in the sequence (default) +- `reduce_mean`: returns the average token of the sequence +- `reduce_max`: returns the max token of the sequence +- `cls_token`: returns the cls token from the sequence + +An example using automatic embedding layer detection looks like this: + +```python +from deepsparse import Pipeline + +bert_emb_pipeline = Pipeline.create( + task="transformers_embedding_extraction", + model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", +# emb_extraction_layer=-1, # (default: detect last layer) +# extraction_strategy="per_token" # (default: concat embedding for each token) +) + +input_sequence = "The generalized embedding extraction Pipeline is the best!" +embedding = bert_emb_pipeline(input_sequence) +print(len(embedding.embeddings[0])) +# 98304 +``` +An example returning the average embeddings of the tokens looks like this: +```python +from deepsparse import Pipeline + +bert_emb_pipeline = Pipeline.create( + task="transformers_embedding_extraction", + model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", +# emb_extraction_layer=-1, # (default: detect last layer) + extraction_strategy="reduce_mean" +) + +input_sequence = "The generalized embedding extraction Pipeline is the best!" +embedding = bert_emb_pipeline(input_sequence) +print(len(embedding.embeddings[0])) +# 768 +``` +### Use Case Specific Arguments +The Transformers Embedding Extraction Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Sequence Length +The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. + +The example below compiles the model and runs inference with sequence length of 64. +```python +from deepsparse import Pipeline + +bert_emb_pipeline = Pipeline.create( + task="transformers_embedding_extraction", + model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", +# emb_extraction_layer=-1, # (default: detect last layer) + extraction_strategy="reduce_mean", + sequence_length = 64 +) + +input_sequence = "The transformers embedding extraction Pipeline is the best!" +embedding = bert_emb_pipeline(input_sequence) +print(len(embedding.embeddings[0])) +# 768 +``` +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. + +The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). +```python +from deepsparse import Pipeline + +buckets = [16, 128] +bert_emb_pipeline = Pipeline.create( + task="transformers_embedding_extraction", + model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", +# emb_extraction_layer=-1, # (default: detect last layer) + extraction_strategy="reduce_mean", + sequence_length = buckets +) + +input_sequence = "The transformers embedding extraction Pipeline is the best!" +embedding = bert_emb_pipeline(input_sequence) +print(len(embedding.embeddings[0])) +# 768 +``` +### Cross Use Case Functionality +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +## DeepSparse Server +DeepSparse Server is built on top of FastAPI and Uvicorn, enabling you to set up a REST endpoint for serving inferences over HTTP. + +The HTTP Server is a wrapper around the Pipeline. Therefore, it inherits all of the functionality we described above. It can be launched from the command line with a configuration file. + +This configuration file sets sequence length to 64 and `emb_extraction_layer` to -3: +```yaml +# transformer-config.yaml +endpoints: + - task: transformers_embedding_extraction + model: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none + kwargs: + emb_extraction_layer: -3 # (default: detect last layer) + sequence_length: 64 # uses sequence length 64 +``` + +Spin up the server: +```bash +deepsparse.server --config_file transformer-config.yaml +``` +Making requests: + +```python +import requests, json +url = "http://0.0.0.0:5543/predict" +obj = { + "inputs": "Mary is flying from Nairobi to New York to attend a conference on generative AI", +} +resp = requests.post(url=url, json=obj) +result = json.loads(resp.text) +print(len(result["embeddings"][0])) +# 49152 +``` +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file From ddb30912425dc6569e7616492073c0826ab53d7c Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Wed, 22 Mar 2023 14:25:10 +0300 Subject: [PATCH 040/149] add general embedding doc --- use-cases/cv/embedding-extraction.md | 84 ++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 use-cases/cv/embedding-extraction.md diff --git a/use-cases/cv/embedding-extraction.md b/use-cases/cv/embedding-extraction.md new file mode 100644 index 0000000000..0d8f3b1c92 --- /dev/null +++ b/use-cases/cv/embedding-extraction.md @@ -0,0 +1,84 @@ +# Deploying Embedding Extraction Models With DeepSparse +This page explains how to deploy an Embedding Extraction Pipeline with DeepSparse. + +## Installation Requirements +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +## Model Format +The Embedding Extraction Pipeline enables you to generate embeddings in any domain, meaning you can use it with any ONNX model. It (optionally) removes the projection head from the model, such that you can re-use SparseZoo models and custom models you have trained in the embedding extraction scenario. + +There are two options for passing a model to the Embedding Extraction Pipeline: + +- Pass a Local ONNX File +- Pass a SparseZoo Stub (which identifies an ONNX model in the SparseZoo) +## DeepSparse Pipelines +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +We will use the `Pipeline.create()` constructor to create an instance of an embedding extraction Pipeline with a 75% pruned-quantized version of ResNet-50 trained on `imagenet`. We can then pass images the `Pipeline` and receive the embeddings. All of the pre-processing is handled by the `Pipeline`. + +The Embedding Extraction Pipeline handles some useful actions around inference: + +- First, on initialization, the Pipeline (optionally) removes a projection head from a model. You can use the `emb_extraction_layer` argument to specify which layer to return. If your ONNX model has no projection head, you can set `emb_extraction_layer=None` (the default) to skip this step. + +- Second, as with all DeepSparse Pipelines, it handles pre-processing such that you can pass raw input. You will notice that in addition to the typical task argument used in `Pipeline.create()`, the Embedding Extraction Pipeline includes a `base_task` argument. This argument tells the Pipeline the domain of the model, such that the Pipeline can figure out what pre-processing to do. + +This is an example of extracting the last layer from ResNet-50: + +Download an image to use with the Pipeline. +```bash +wget https://huggingface.co/spaces/neuralmagic/cv-yolo/resolve/main/pets.jpg +``` +Define the Pipeline: +```python +from deepsparse import Pipeline + +# this step removes the projection head before compiling the model +rn50_embedding_pipeline = Pipeline.create( + task="embedding-extraction", + base_task="image-classification", # tells the pipeline to expect images and normalize input with ImageNet means/stds + model_path="zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/channel20_pruned75_quant-none-vnni", + emb_extraction_layer=-3, # extracts last layer before projection head and softmax +) + +# this step runs pre-processing, inference and returns an embedding +embedding = rn50_embedding_pipeline(images="pets.jpg") +print(len(embedding.embeddings[0][0])) +# 2048 +``` +# DeepSparse Server +As an alternative to the Python API, DeepSparse Server allows you to serve an Embedding Extraction Pipeline over HTTP. Configuring the server uses the same parameters and schemas as the Pipelines. + +Once launched, a `/docs` endpoint is created with full endpoint descriptions and support for making sample requests. + +This configuration file sets `emb_extraction_layer` to -3: +```yaml +# config.yaml +endpoints: + - task: embedding_extraction + model: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none + kwargs: + base_task: image_classification + emb_extraction_layer: -3 +``` +Spin up the server: +```bash +deepsparse.server --config_file general-embedding.yaml +``` +Make requests to the server: +```python +import requests, json +url = "http://0.0.0.0:5543/predict/from_files" +paths = ["pets.jpg"] +files = [("request", open(img, 'rb')) for img in paths] +resp = requests.post(url=url, files=files) +result = json.loads(resp.text) + +print(len(result["embeddings"][0][0])) +# 2048 +``` + +### Cross Use Case Functionality +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file From e99a19612f2e5647ab394a3f0ebe0318b363b515 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Thu, 23 Mar 2023 15:07:30 +0300 Subject: [PATCH 041/149] add image classification doc --- use-cases/cv/image-classification.md | 198 +++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 use-cases/cv/image-classification.md diff --git a/use-cases/cv/image-classification.md b/use-cases/cv/image-classification.md new file mode 100644 index 0000000000..9296c7d551 --- /dev/null +++ b/use-cases/cv/image-classification.md @@ -0,0 +1,198 @@ +# Deploying Image Classification Models with DeepSparse + +This page explains how to benchmark and deploy an image classification model with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. + +### ONNX Runtime Baseline + +As a baseline, let's check out ONNX Runtime's performance on ResNet-50. Make sure you have ORT installed (`pip install onnxruntime`). + +```bash +deepsparse.benchmark \ + zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/base-none \ + -b 64 -s sync -nstreams 1 \ + -e onnxruntime + +> Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/base-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 192.4109 +> Latency Mean (ms/batch): 332.6113 +> Latency Median (ms/batch): 331.6066 +> Latency Std (ms/batch): 2.9168 +> Iterations: 31 +``` +ONNX Runtime achieves 192 items/second with batch 64. + +### DeepSparse Speedup +Now, let's run DeepSparse on an inference-optimized sparse version of ResNet-50 . This model has been 90% pruned, while retaining >99% accuracy of the dense baseline on the `imagenet` dataset. +```bash +!deepsparse.benchmark \ + zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none \ + -b 64 -s sync -nstreams 1 \ + -e deepsparse + +> Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 1034.6594 +> Latency Mean (ms/batch): 61.8392 +> Latency Median (ms/batch): 61.6361 +> Latency Std (ms/batch): 0.7029 +> Iterations: 162 +``` +DeepSparse achieves 1035 items/second, an 5.3x speed-up over ONNX Runtime! +## DeepSparse Engine +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 90% pruned-quantized ResNet-50 trained on `imagenet` from SparseZoo: +```python +from deepsparse import compile_model +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none" +batch_size = 1 +bert_engine = compile_model( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) + +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = bert_engine(inputs) +print(output) +# [array([[-7.73529887e-01, 1.67251182e+00, -1.68212160e-01, +# .... +# 1.26290070e-05, 2.30549040e-06, 2.97072188e-06, 1.90549777e-04]], dtype=float32)] +``` +## DeepSparse Pipelines +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +Let's start by downloading a sample image: +```bash +wget https://huggingface.co/spaces/neuralmagic/image-classification/resolve/main/lion.jpeg +``` +We will use the `Pipeline.create()` constructor to create an instance of an image classification Pipeline with a 90% pruned-quantized version of ResNet-50 trained on `imagenet`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images) is handled by the `Pipeline`. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none" +pipeline = Pipeline.create( + task="image_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX +) + +# run inference on image file +prediction = pipeline(images="lion.jpeg") +print(prediction) +# labels=[456] scores=[17.108182907104492] +``` +### Use Case Specific Arguments +The Image Classification Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Top K and classes +The `top_k` argument allows you to define the number of classes to return. + +The example below runs inference with `top_k = 3`, meaning that the pipeline will return the top 3 classes. + +The `class_names` argument defines a dictionary containing the desired class mappings. + +```python +from deepsparse import Pipeline + +classes = {0: 'tench, Tinca tinca',1: 'goldfish, Carassius auratus',2: 'great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias',3: 'tiger shark, Galeocerdo cuvieri',4: 'hammerhead, hammerhead shark',5: 'electric ray, crampfish, numbfish, torpedo',6: 'stingray',7: 'cock', 8: 'hen', 9: 'ostrich, Struthio camelus', 10: 'brambling, Fringilla montifringilla', 11: 'goldfinch, Carduelis carduelis', 12: 'house finch, linnet, Carpodacus mexicanus', 13: 'junco, snowbird', 14: 'indigo bunting, indigo finch, indigo bird, Passerina cyanea', 15: 'robin, American robin, Turdus migratorius', 16: 'bulbul', 17: 'jay', 18: 'magpie', 19: 'chickadee', 20: 'water ouzel, dipper', 21: 'kite', 22: 'bald eagle, American eagle, Haliaeetus leucocephalus', 23: 'vulture', 24: 'great grey owl, great gray owl, Strix nebulosa', 25: 'European fire salamander, Salamandra salamandra', 26: 'common newt, Triturus vulgaris', 27: 'eft', 28: 'spotted salamander, Ambystoma maculatum', 29: 'axolotl, mud puppy, Ambystoma mexicanum', 30: 'bullfrog, Rana catesbeiana', 31: 'tree frog, tree-frog', 32: 'tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui', 33: 'loggerhead, loggerhead turtle, Caretta caretta', 34: 'leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea', 35: 'mud turtle', 36: 'terrapin', 37: 'box turtle, box tortoise', 38: 'banded gecko', 39: 'common iguana, iguana, Iguana iguana', 40: 'American chameleon, anole, Anolis carolinensis', 41: 'whiptail, whiptail lizard', 42: 'agama', 43: 'frilled lizard, Chlamydosaurus kingi', 44: 'alligator lizard', 45: 'Gila monster, Heloderma suspectum', 46: 'green lizard, Lacerta viridis', 47: 'African chameleon, Chamaeleo chamaeleon', 48: 'Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis', 49: 'African crocodile, Nile crocodile, Crocodylus niloticus', 50: 'American alligator, Alligator mississipiensis', 51: 'triceratops', 52: 'thunder snake, worm snake, Carphophis amoenus', 53: 'ringneck snake, ring-necked snake, ring snake', 54: 'hognose snake, puff adder, sand viper', 55: 'green snake, grass snake', 56: 'king snake, kingsnake', 57: 'garter snake, grass snake', 58: 'water snake', 59: 'vine snake', 60: 'night snake, Hypsiglena torquata', 61: 'boa constrictor, Constrictor constrictor', 62: 'rock python, rock snake, Python sebae', 63: 'Indian cobra, Naja naja', 64: 'green mamba', 65: 'sea snake', 66: 'horned viper, cerastes, sand viper, horned asp, Cerastes cornutus', 67: 'diamondback, diamondback rattlesnake, Crotalus adamanteus', 68: 'sidewinder, horned rattlesnake, Crotalus cerastes', 69: 'trilobite', 70: 'harvestman, daddy longlegs, Phalangium opilio', 71: 'scorpion', 72: 'black and gold garden spider, Argiope aurantia', 73: 'barn spider, Araneus cavaticus', 74: 'garden spider, Aranea diademata', 75: 'black widow, Latrodectus mactans', 76: 'tarantula', 77: 'wolf spider, hunting spider', 78: 'tick', 79: 'centipede', 80: 'black grouse', 81: 'ptarmigan', 82: 'ruffed grouse, partridge, Bonasa umbellus', 83: 'prairie chicken, prairie grouse, prairie fowl', 84: 'peacock', 85: 'quail', 86: 'partridge', 87: 'African grey, African gray, Psittacus erithacus', 88: 'macaw', 89: 'sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita', 90: 'lorikeet', 91: 'coucal', 92: 'bee eater', 93: 'hornbill', 94: 'hummingbird', 95: 'jacamar', 96: 'toucan', 97: 'drake', 98: 'red-breasted merganser, Mergus serrator', 99: 'goose', 100: 'black swan, Cygnus atratus', 101: 'tusker', 102: 'echidna, spiny anteater, anteater', 103: 'platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus', 104: 'wallaby, brush kangaroo', 105: 'koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus', 106: 'wombat', 107: 'jellyfish', 108: 'sea anemone, anemone', 109: 'brain coral', 110: 'flatworm, platyhelminth', 111: 'nematode, nematode worm, roundworm', 112: 'conch', 113: 'snail', 114: 'slug', 115: 'sea slug, nudibranch', 116: 'chiton, coat-of-mail shell, sea cradle, polyplacophore', 117: 'chambered nautilus, pearly nautilus, nautilus', 118: 'Dungeness crab, Cancer magister', 119: 'rock crab, Cancer irroratus', 120: 'fiddler crab', 121: 'king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica', 122: 'American lobster, Northern lobster, Maine lobster, Homarus americanus', 123: 'spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish', 124: 'crayfish, crawfish, crawdad, crawdaddy', 125: 'hermit crab', 126: 'isopod', 127: 'white stork, Ciconia ciconia', 128: 'black stork, Ciconia nigra', 129: 'spoonbill', 130: 'flamingo', 131: 'little blue heron, Egretta caerulea', 132: 'American egret, great white heron, Egretta albus', 133: 'bittern', 134: 'crane', 135: 'limpkin, Aramus pictus', 136: 'European gallinule, Porphyrio porphyrio', 137: 'American coot, marsh hen, mud hen, water hen, Fulica americana', 138: 'bustard', 139: 'ruddy turnstone, Arenaria interpres', 140: 'red-backed sandpiper, dunlin, Erolia alpina', 141: 'redshank, Tringa totanus', 142: 'dowitcher', 143: 'oystercatcher, oyster catcher', 144: 'pelican', 145: 'king penguin, Aptenodytes patagonica', 146: 'albatross, mollymawk', 147: 'grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus', 148: 'killer whale, killer, orca, grampus, sea wolf, Orcinus orca', 149: 'dugong, Dugong dugon', 150: 'sea lion', 151: 'Chihuahua', 152: 'Japanese spaniel', 153: 'Maltese dog, Maltese terrier, Maltese', 154: 'Pekinese, Pekingese, Peke', 155: 'Shih-Tzu', 156: 'Blenheim spaniel', 157: 'papillon', 158: 'toy terrier', 159: 'Rhodesian ridgeback', 160: 'Afghan hound, Afghan', 161: 'basset, basset hound', 162: 'beagle', 163: 'bloodhound, sleuthhound', 164: 'bluetick', 165: 'black-and-tan coonhound', 166: 'Walker hound, Walker foxhound', 167: 'English foxhound', 168: 'redbone', 169: 'borzoi, Russian wolfhound', 170: 'Irish wolfhound', 171: 'Italian greyhound', 172: 'whippet', 173: 'Ibizan hound, Ibizan Podenco', 174: 'Norwegian elkhound, elkhound', 175: 'otterhound, otter hound', 176: 'Saluki, gazelle hound', 177: 'Scottish deerhound, deerhound', 178: 'Weimaraner', 179: 'Staffordshire bullterrier, Staffordshire bull terrier', 180: 'American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier', 181: 'Bedlington terrier', 182: 'Border terrier', 183: 'Kerry blue terrier', 184: 'Irish terrier', 185: 'Norfolk terrier', 186: 'Norwich terrier', 187: 'Yorkshire terrier', 188: 'wire-haired fox terrier', 189: 'Lakeland terrier', 190: 'Sealyham terrier, Sealyham', 191: 'Airedale, Airedale terrier', 192: 'cairn, cairn terrier', 193: 'Australian terrier', 194: 'Dandie Dinmont, Dandie Dinmont terrier', 195: 'Boston bull, Boston terrier', 196: 'miniature schnauzer', 197: 'giant schnauzer', 198: 'standard schnauzer', 199: 'Scotch terrier, Scottish terrier, Scottie', 200: 'Tibetan terrier, chrysanthemum dog', 201: 'silky terrier, Sydney silky', 202: 'soft-coated wheaten terrier', 203: 'West Highland white terrier', 204: 'Lhasa, Lhasa apso', 205: 'flat-coated retriever', 206: 'curly-coated retriever', 207: 'golden retriever', 208: 'Labrador retriever', 209: 'Chesapeake Bay retriever', 210: 'German short-haired pointer', 211: 'vizsla, Hungarian pointer', 212: 'English setter', 213: 'Irish setter, red setter', 214: 'Gordon setter', 215: 'Brittany spaniel', 216: 'clumber, clumber spaniel', 217: 'English springer, English springer spaniel', 218: 'Welsh springer spaniel', 219: 'cocker spaniel, English cocker spaniel, cocker', 220: 'Sussex spaniel', 221: 'Irish water spaniel', 222: 'kuvasz', 223: 'schipperke', 224: 'groenendael', 225: 'malinois', 226: 'briard', 227: 'kelpie', 228: 'komondor', 229: 'Old English sheepdog, bobtail', 230: 'Shetland sheepdog, Shetland sheep dog, Shetland', 231: 'collie', 232: 'Border collie', 233: 'Bouvier des Flandres, Bouviers des Flandres', 234: 'Rottweiler', 235: 'German shepherd, German shepherd dog, German police dog, alsatian', 236: 'Doberman, Doberman pinscher', 237: 'miniature pinscher', 238: 'Greater Swiss Mountain dog', 239: 'Bernese mountain dog', 240: 'Appenzeller', 241: 'EntleBucher', 242: 'boxer', 243: 'bull mastiff', 244: 'Tibetan mastiff', 245: 'French bulldog', 246: 'Great Dane', 247: 'Saint Bernard, St Bernard', 248: 'Eskimo dog, husky', 249: 'malamute, malemute, Alaskan malamute', 250: 'Siberian husky', 251: 'dalmatian, coach dog, carriage dog', 252: 'affenpinscher, monkey pinscher, monkey dog', 253: 'basenji', 254: 'pug, pug-dog', 255: 'Leonberg', 256: 'Newfoundland, Newfoundland dog', 257: 'Great Pyrenees', 258: 'Samoyed, Samoyede', 259: 'Pomeranian', 260: 'chow, chow chow', 261: 'keeshond', 262: 'Brabancon griffon', 263: 'Pembroke, Pembroke Welsh corgi', 264: 'Cardigan, Cardigan Welsh corgi', 265: 'toy poodle', 266: 'miniature poodle', 267: 'standard poodle', 268: 'Mexican hairless', 269: 'timber wolf, grey wolf, gray wolf, Canis lupus', 270: 'white wolf, Arctic wolf, Canis lupus tundrarum', 271: 'red wolf, maned wolf, Canis rufus, Canis niger', 272: 'coyote, prairie wolf, brush wolf, Canis latrans', 273: 'dingo, warrigal, warragal, Canis dingo', 274: 'dhole, Cuon alpinus', 275: 'African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus', 276: 'hyena, hyaena', 277: 'red fox, Vulpes vulpes', 278: 'kit fox, Vulpes macrotis', 279: 'Arctic fox, white fox, Alopex lagopus', 280: 'grey fox, gray fox, Urocyon cinereoargenteus', 281: 'tabby, tabby cat', 282: 'tiger cat', 283: 'Persian cat', 284: 'Siamese cat, Siamese', 285: 'Egyptian cat', 286: 'cougar, puma, catamount, mountain lion, painter, panther, Felis concolor', 287: 'lynx, catamount', 288: 'leopard, Panthera pardus', 289: 'snow leopard, ounce, Panthera uncia', 290: 'jaguar, panther, Panthera onca, Felis onca', 291: 'lion, king of beasts, Panthera leo', 292: 'tiger, Panthera tigris', 293: 'cheetah, chetah, Acinonyx jubatus', 294: 'brown bear, bruin, Ursus arctos', 295: 'American black bear, black bear, Ursus americanus, Euarctos americanus', 296: 'ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus', 297: 'sloth bear, Melursus ursinus, Ursus ursinus', 298: 'mongoose', 299: 'meerkat, mierkat', 300: 'tiger beetle', 301: 'ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle', 302: 'ground beetle, carabid beetle', 303: 'long-horned beetle, longicorn, longicorn beetle', 304: 'leaf beetle, chrysomelid', 305: 'dung beetle', 306: 'rhinoceros beetle', 307: 'weevil', 308: 'fly', 309: 'bee', 310: 'ant, emmet, pismire', 311: 'grasshopper, hopper', 312: 'cricket', 313: 'walking stick, walkingstick, stick insect', 314: 'cockroach, roach', 315: 'mantis, mantid', 316: 'cicada, cicala', 317: 'leafhopper', 318: 'lacewing, lacewing fly', 319: "dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk", 320: 'damselfly', 321: 'admiral', 322: 'ringlet, ringlet butterfly', 323: 'monarch, monarch butterfly, milkweed butterfly, Danaus plexippus', 324: 'cabbage butterfly', 325: 'sulphur butterfly, sulfur butterfly', 326: 'lycaenid, lycaenid butterfly', 327: 'starfish, sea star', 328: 'sea urchin', 329: 'sea cucumber, holothurian', 330: 'wood rabbit, cottontail, cottontail rabbit', 331: 'hare', 332: 'Angora, Angora rabbit', 333: 'hamster', 334: 'porcupine, hedgehog', 335: 'fox squirrel, eastern fox squirrel, Sciurus niger', 336: 'marmot', 337: 'beaver', 338: 'guinea pig, Cavia cobaya', 339: 'sorrel', 340: 'zebra', 341: 'hog, pig, grunter, squealer, Sus scrofa', 342: 'wild boar, boar, Sus scrofa', 343: 'warthog', 344: 'hippopotamus, hippo, river horse, Hippopotamus amphibius', 345: 'ox', 346: 'water buffalo, water ox, Asiatic buffalo, Bubalus bubalis', 347: 'bison', 348: 'ram, tup', 349: 'bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis', 350: 'ibex, Capra ibex', 351: 'hartebeest', 352: 'impala, Aepyceros melampus', 353: 'gazelle', 354: 'Arabian camel, dromedary, Camelus dromedarius', 355: 'llama', 356: 'weasel', 357: 'mink', 358: 'polecat, fitch, foulmart, foumart, Mustela putorius', 359: 'black-footed ferret, ferret, Mustela nigripes', 360: 'otter', 361: 'skunk, polecat, wood pussy', 362: 'badger', 363: 'armadillo', 364: 'three-toed sloth, ai, Bradypus tridactylus', 365: 'orangutan, orang, orangutang, Pongo pygmaeus', 366: 'gorilla, Gorilla gorilla', 367: 'chimpanzee, chimp, Pan troglodytes', 368: 'gibbon, Hylobates lar', 369: 'siamang, Hylobates syndactylus, Symphalangus syndactylus', 370: 'guenon, guenon monkey', 371: 'patas, hussar monkey, Erythrocebus patas', 372: 'baboon', 373: 'macaque', 374: 'langur', 375: 'colobus, colobus monkey', 376: 'proboscis monkey, Nasalis larvatus', 377: 'marmoset', 378: 'capuchin, ringtail, Cebus capucinus', 379: 'howler monkey, howler', 380: 'titi, titi monkey', 381: 'spider monkey, Ateles geoffroyi', 382: 'squirrel monkey, Saimiri sciureus', 383: 'Madagascar cat, ring-tailed lemur, Lemur catta', 384: 'indri, indris, Indri indri, Indri brevicaudatus', 385: 'Indian elephant, Elephas maximus', 386: 'African elephant, Loxodonta africana', 387: 'lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens', 388: 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 389: 'barracouta, snoek', 390: 'eel', 391: 'coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch', 392: 'rock beauty, Holocanthus tricolor', 393: 'anemone fish', 394: 'sturgeon', 395: 'gar, garfish, garpike, billfish, Lepisosteus osseus', 396: 'lionfish', 397: 'puffer, pufferfish, blowfish, globefish', 398: 'abacus', 399: 'abaya', 400: "academic gown, academic robe, judge's robe", 401: 'accordion, piano accordion, squeeze box', 402: 'acoustic guitar', 403: 'aircraft carrier, carrier, flattop, attack aircraft carrier', 404: 'airliner', 405: 'airship, dirigible', 406: 'altar', 407: 'ambulance', 408: 'amphibian, amphibious vehicle', 409: 'analog clock', 410: 'apiary, bee house', 411: 'apron', 412: 'ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin', 413: 'assault rifle, assault gun', 414: 'backpack, back pack, knapsack, packsack, rucksack, haversack', 415: 'bakery, bakeshop, bakehouse', 416: 'balance beam, beam', 417: 'balloon', 418: 'ballpoint, ballpoint pen, ballpen, Biro', 419: 'Band Aid', 420: 'banjo', 421: 'bannister, banister, balustrade, balusters, handrail', 422: 'barbell', 423: 'barber chair', 424: 'barbershop', 425: 'barn', 426: 'barometer', 427: 'barrel, cask', 428: 'barrow, garden cart, lawn cart, wheelbarrow', 429: 'baseball', 430: 'basketball', 431: 'bassinet', 432: 'bassoon', 433: 'bathing cap, swimming cap', 434: 'bath towel', 435: 'bathtub, bathing tub, bath, tub', 436: 'beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon', 437: 'beacon, lighthouse, beacon light, pharos', 438: 'beaker', 439: 'bearskin, busby, shako', 440: 'beer bottle', 441: 'beer glass', 442: 'bell cote, bell cot', 443: 'bib', 444: 'bicycle-built-for-two, tandem bicycle, tandem', 445: 'bikini, two-piece', 446: 'binder, ring-binder', 447: 'binoculars, field glasses, opera glasses', 448: 'birdhouse', 449: 'boathouse', 450: 'bobsled, bobsleigh, bob', 451: 'bolo tie, bolo, bola tie, bola', 452: 'bonnet, poke bonnet', 453: 'bookcase', 454: 'bookshop, bookstore, bookstall', 455: 'bottlecap', 456: 'bow', 457: 'bow tie, bow-tie, bowtie', 458: 'brass, memorial tablet, plaque', 459: 'brassiere, bra, bandeau', 460: 'breakwater, groin, groyne, mole, bulwark, seawall, jetty', 461: 'breastplate, aegis, egis', 462: 'broom', 463: 'bucket, pail', 464: 'buckle', 465: 'bulletproof vest', 466: 'bullet train, bullet', 467: 'butcher shop, meat market', 468: 'cab, hack, taxi, taxicab', 469: 'caldron, cauldron', 470: 'candle, taper, wax light', 471: 'cannon', 472: 'canoe', 473: 'can opener, tin opener', 474: 'cardigan', 475: 'car mirror', 476: 'carousel, carrousel, merry-go-round, roundabout, whirligig', 477: "carpenter's kit, tool kit", 478: 'carton', 479: 'car wheel', 480: 'cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM', 481: 'cassette', 482: 'cassette player', 483: 'castle', 484: 'catamaran', 485: 'CD player', 486: 'cello, violoncello', 487: 'cellular telephone, cellular phone, cellphone, cell, mobile phone', 488: 'chain', 489: 'chainlink fence', 490: 'chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour', 491: 'chain saw, chainsaw', 492: 'chest', 493: 'chiffonier, commode', 494: 'chime, bell, gong', 495: 'china cabinet, china closet', 496: 'Christmas stocking', 497: 'church, church building', 498: 'cinema, movie theater, movie theatre, movie house, picture palace', 499: 'cleaver, meat cleaver, chopper', 500: 'cliff dwelling', 501: 'cloak', 502: 'clog, geta, patten, sabot', 503: 'cocktail shaker', 504: 'coffee mug', 505: 'coffeepot', 506: 'coil, spiral, volute, whorl, helix', 507: 'combination lock', 508: 'computer keyboard, keypad', 509: 'confectionery, confectionary, candy store', 510: 'container ship, containership, container vessel', 511: 'convertible', 512: 'corkscrew, bottle screw', 513: 'cornet, horn, trumpet, trump', 514: 'cowboy boot', 515: 'cowboy hat, ten-gallon hat', 516: 'cradle', 517: 'crane', 518: 'crash helmet', 519: 'crate', 520: 'crib, cot', 521: 'Crock Pot', 522: 'croquet ball', 523: 'crutch', 524: 'cuirass', 525: 'dam, dike, dyke', 526: 'desk', 527: 'desktop computer', 528: 'dial telephone, dial phone', 529: 'diaper, nappy, napkin', 530: 'digital clock', 531: 'digital watch', 532: 'dining table, board', 533: 'dishrag, dishcloth', 534: 'dishwasher, dish washer, dishwashing machine', 535: 'disk brake, disc brake', 536: 'dock, dockage, docking facility', 537: 'dogsled, dog sled, dog sleigh', 538: 'dome', 539: 'doormat, welcome mat', 540: 'drilling platform, offshore rig', 541: 'drum, membranophone, tympan', 542: 'drumstick', 543: 'dumbbell', 544: 'Dutch oven', 545: 'electric fan, blower', 546: 'electric guitar', 547: 'electric locomotive', 548: 'entertainment center', 549: 'envelope', 550: 'espresso maker', 551: 'face powder', 552: 'feather boa, boa', 553: 'file, file cabinet, filing cabinet', 554: 'fireboat', 555: 'fire engine, fire truck', 556: 'fire screen, fireguard', 557: 'flagpole, flagstaff', 558: 'flute, transverse flute', 559: 'folding chair', 560: 'football helmet', 561: 'forklift', 562: 'fountain', 563: 'fountain pen', 564: 'four-poster', 565: 'freight car', 566: 'French horn, horn', 567: 'frying pan, frypan, skillet', 568: 'fur coat', 569: 'garbage truck, dustcart', 570: 'gasmask, respirator, gas helmet', 571: 'gas pump, gasoline pump, petrol pump, island dispenser', 572: 'goblet', 573: 'go-kart', 574: 'golf ball', 575: 'golfcart, golf cart', 576: 'gondola', 577: 'gong, tam-tam', 578: 'gown', 579: 'grand piano, grand', 580: 'greenhouse, nursery, glasshouse', 581: 'grille, radiator grille', 582: 'grocery store, grocery, food market, market', 583: 'guillotine', 584: 'hair slide', 585: 'hair spray', 586: 'half track', 587: 'hammer', 588: 'hamper', 589: 'hand blower, blow dryer, blow drier, hair dryer, hair drier', 590: 'hand-held computer, hand-held microcomputer', 591: 'handkerchief, hankie, hanky, hankey', 592: 'hard disc, hard disk, fixed disk', 593: 'harmonica, mouth organ, harp, mouth harp', 594: 'harp', 595: 'harvester, reaper', 596: 'hatchet', 597: 'holster', 598: 'home theater, home theatre', 599: 'honeycomb', 600: 'hook, claw', 601: 'hoopskirt, crinoline', 602: 'horizontal bar, high bar', 603: 'horse cart, horse-cart', 604: 'hourglass', 605: 'iPod', 606: 'iron, smoothing iron', 607: "jack-o'-lantern", 608: 'jean, blue jean, denim', 609: 'jeep, landrover', 610: 'jersey, T-shirt, tee shirt', 611: 'jigsaw puzzle', 612: 'jinrikisha, ricksha, rickshaw', 613: 'joystick', 614: 'kimono', 615: 'knee pad', 616: 'knot', 617: 'lab coat, laboratory coat', 618: 'ladle', 619: 'lampshade, lamp shade', 620: 'laptop, laptop computer', 621: 'lawn mower, mower', 622: 'lens cap, lens cover', 623: 'letter opener, paper knife, paperknife', 624: 'library', 625: 'lifeboat', 626: 'lighter, light, igniter, ignitor', 627: 'limousine, limo', 628: 'liner, ocean liner', 629: 'lipstick, lip rouge', 630: 'Loafer', 631: 'lotion', 632: 'loudspeaker, speaker, speaker unit, loudspeaker system, speaker system', 633: "loupe, jeweler's loupe", 634: 'lumbermill, sawmill', 635: 'magnetic compass', 636: 'mailbag, postbag', 637: 'mailbox, letter box', 638: 'maillot', 639: 'maillot, tank suit', 640: 'manhole cover', 641: 'maraca', 642: 'marimba, xylophone', 643: 'mask', 644: 'matchstick', 645: 'maypole', 646: 'maze, labyrinth', 647: 'measuring cup', 648: 'medicine chest, medicine cabinet', 649: 'megalith, megalithic structure', 650: 'microphone, mike', 651: 'microwave, microwave oven', 652: 'military uniform', 653: 'milk can', 654: 'minibus', 655: 'miniskirt, mini', 656: 'minivan', 657: 'missile', 658: 'mitten', 659: 'mixing bowl', 660: 'mobile home, manufactured home', 661: 'Model T', 662: 'modem', 663: 'monastery', 664: 'monitor', 665: 'moped', 666: 'mortar', 667: 'mortarboard', 668: 'mosque', 669: 'mosquito net', 670: 'motor scooter, scooter', 671: 'mountain bike, all-terrain bike, off-roader', 672: 'mountain tent', 673: 'mouse, computer mouse', 674: 'mousetrap', 675: 'moving van', 676: 'muzzle', 677: 'nail', 678: 'neck brace', 679: 'necklace', 680: 'nipple', 681: 'notebook, notebook computer', 682: 'obelisk', 683: 'oboe, hautboy, hautbois', 684: 'ocarina, sweet potato', 685: 'odometer, hodometer, mileometer, milometer', 686: 'oil filter', 687: 'organ, pipe organ', 688: 'oscilloscope, scope, cathode-ray oscilloscope, CRO', 689: 'overskirt', 690: 'oxcart', 691: 'oxygen mask', 692: 'packet', 693: 'paddle, boat paddle', 694: 'paddlewheel, paddle wheel', 695: 'padlock', 696: 'paintbrush', 697: "pajama, pyjama, pj's, jammies", 698: 'palace', 699: 'panpipe, pandean pipe, syrinx', 700: 'paper towel', 701: 'parachute, chute', 702: 'parallel bars, bars', 703: 'park bench', 704: 'parking meter', 705: 'passenger car, coach, carriage', 706: 'patio, terrace', 707: 'pay-phone, pay-station', 708: 'pedestal, plinth, footstall', 709: 'pencil box, pencil case', 710: 'pencil sharpener', 711: 'perfume, essence', 712: 'Petri dish', 713: 'photocopier', 714: 'pick, plectrum, plectron', 715: 'pickelhaube', 716: 'picket fence, paling', 717: 'pickup, pickup truck', 718: 'pier', 719: 'piggy bank, penny bank', 720: 'pill bottle', 721: 'pillow', 722: 'ping-pong ball', 723: 'pinwheel', 724: 'pirate, pirate ship', 725: 'pitcher, ewer', 726: "plane, carpenter's plane, woodworking plane", 727: 'planetarium', 728: 'plastic bag', 729: 'plate rack', 730: 'plow, plough', 731: "plunger, plumber's helper", 732: 'Polaroid camera, Polaroid Land camera', 733: 'pole', 734: 'police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria', 735: 'poncho', 736: 'pool table, billiard table, snooker table', 737: 'pop bottle, soda bottle', 738: 'pot, flowerpot', 739: "potter's wheel", 740: 'power drill', 741: 'prayer rug, prayer mat', 742: 'printer', 743: 'prison, prison house', 744: 'projectile, missile', 745: 'projector', 746: 'puck, hockey puck', 747: 'punching bag, punch bag, punching ball, punchball', 748: 'purse', 749: 'quill, quill pen', 750: 'quilt, comforter, comfort, puff', 751: 'racer, race car, racing car', 752: 'racket, racquet', 753: 'radiator', 754: 'radio, wireless', 755: 'radio telescope, radio reflector', 756: 'rain barrel', 757: 'recreational vehicle, RV, R.V.', 758: 'reel', 759: 'reflex camera', 760: 'refrigerator, icebox', 761: 'remote control, remote', 762: 'restaurant, eating house, eating place, eatery', 763: 'revolver, six-gun, six-shooter', 764: 'rifle', 765: 'rocking chair, rocker', 766: 'rotisserie', 767: 'rubber eraser, rubber, pencil eraser', 768: 'rugby ball', 769: 'rule, ruler', 770: 'running shoe', 771: 'safe', 772: 'safety pin', 773: 'saltshaker, salt shaker', 774: 'sandal', 775: 'sarong', 776: 'sax, saxophone', 777: 'scabbard', 778: 'scale, weighing machine', 779: 'school bus', 780: 'schooner', 781: 'scoreboard', 782: 'screen, CRT screen', 783: 'screw', 784: 'screwdriver', 785: 'seat belt, seatbelt', 786: 'sewing machine', 787: 'shield, buckler', 788: 'shoe shop, shoe-shop, shoe store', 789: 'shoji', 790: 'shopping basket', 791: 'shopping cart', 792: 'shovel', 793: 'shower cap', 794: 'shower curtain', 795: 'ski', 796: 'ski mask', 797: 'sleeping bag', 798: 'slide rule, slipstick', 799: 'sliding door', 800: 'slot, one-armed bandit', 801: 'snorkel', 802: 'snowmobile', 803: 'snowplow, snowplough', 804: 'soap dispenser', 805: 'soccer ball', 806: 'sock', 807: 'solar dish, solar collector, solar furnace', 808: 'sombrero', 809: 'soup bowl', 810: 'space bar', 811: 'space heater', 812: 'space shuttle', 813: 'spatula', 814: 'speedboat', 815: "spider web, spider's web", 816: 'spindle', 817: 'sports car, sport car', 818: 'spotlight, spot', 819: 'stage', 820: 'steam locomotive', 821: 'steel arch bridge', 822: 'steel drum', 823: 'stethoscope', 824: 'stole', 825: 'stone wall', 826: 'stopwatch, stop watch', 827: 'stove', 828: 'strainer', 829: 'streetcar, tram, tramcar, trolley, trolley car', 830: 'stretcher', 831: 'studio couch, day bed', 832: 'stupa, tope', 833: 'submarine, pigboat, sub, U-boat', 834: 'suit, suit of clothes', 835: 'sundial', 836: 'sunglass', 837: 'sunglasses, dark glasses, shades', 838: 'sunscreen, sunblock, sun blocker', 839: 'suspension bridge', 840: 'swab, swob, mop', 841: 'sweatshirt', 842: 'swimming trunks, bathing trunks', 843: 'swing', 844: 'switch, electric switch, electrical switch', 845: 'syringe', 846: 'table lamp', 847: 'tank, army tank, armored combat vehicle, armoured combat vehicle', 848: 'tape player', 849: 'teapot', 850: 'teddy, teddy bear', 851: 'television, television system', 852: 'tennis ball', 853: 'thatch, thatched roof', 854: 'theater curtain, theatre curtain', 855: 'thimble', 856: 'thresher, thrasher, threshing machine', 857: 'throne', 858: 'tile roof', 859: 'toaster', 860: 'tobacco shop, tobacconist shop, tobacconist', 861: 'toilet seat', 862: 'torch', 863: 'totem pole', 864: 'tow truck, tow car, wrecker', 865: 'toyshop', 866: 'tractor', 867: 'trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi', 868: 'tray', 869: 'trench coat', 870: 'tricycle, trike, velocipede', 871: 'trimaran', 872: 'tripod', 873: 'triumphal arch', 874: 'trolleybus, trolley coach, trackless trolley', 875: 'trombone', 876: 'tub, vat', 877: 'turnstile', 878: 'typewriter keyboard', 879: 'umbrella', 880: 'unicycle, monocycle', 881: 'upright, upright piano', 882: 'vacuum, vacuum cleaner', 883: 'vase', 884: 'vault', 885: 'velvet', 886: 'vending machine', 887: 'vestment', 888: 'viaduct', 889: 'violin, fiddle', 890: 'volleyball', 891: 'waffle iron', 892: 'wall clock', 893: 'wallet, billfold, notecase, pocketbook', 894: 'wardrobe, closet, press', 895: 'warplane, military plane', 896: 'washbasin, handbasin, washbowl, lavabo, wash-hand basin', 897: 'washer, automatic washer, washing machine', 898: 'water bottle', 899: 'water jug', 900: 'water tower', 901: 'whiskey jug', 902: 'whistle', 903: 'wig', 904: 'window screen', 905: 'window shade', 906: 'Windsor tie', 907: 'wine bottle', 908: 'wing', 909: 'wok', 910: 'wooden spoon', 911: 'wool, woolen, woollen', 912: 'worm fence, snake fence, snake-rail fence, Virginia fence', 913: 'wreck', 914: 'yawl', 915: 'yurt', 916: 'web site, website, internet site, site', 917: 'comic book', 918: 'crossword puzzle, crossword', 919: 'street sign', 920: 'traffic light, traffic signal, stoplight', 921: 'book jacket, dust cover, dust jacket, dust wrapper', 922: 'menu', 923: 'plate', 924: 'guacamole', 925: 'consomme', 926: 'hot pot, hotpot', 927: 'trifle', 928: 'ice cream, icecream', 929: 'ice lolly, lolly, lollipop, popsicle', 930: 'French loaf', 931: 'bagel, beigel', 932: 'pretzel', 933: 'cheeseburger', 934: 'hotdog, hot dog, red hot', 935: 'mashed potato', 936: 'head cabbage', 937: 'broccoli', 938: 'cauliflower', 939: 'zucchini, courgette', 940: 'spaghetti squash', 941: 'acorn squash', 942: 'butternut squash', 943: 'cucumber, cuke', 944: 'artichoke, globe artichoke', 945: 'bell pepper', 946: 'cardoon', 947: 'mushroom', 948: 'Granny Smith', 949: 'strawberry', 950: 'orange', 951: 'lemon', 952: 'fig', 953: 'pineapple, ananas', 954: 'banana', 955: 'jackfruit, jak, jack', 956: 'custard apple', 957: 'pomegranate', 958: 'hay', 959: 'carbonara', 960: 'chocolate sauce, chocolate syrup', 961: 'dough', 962: 'meat loaf, meatloaf', 963: 'pizza, pizza pie', 964: 'potpie', 965: 'burrito', 966: 'red wine', 967: 'espresso', 968: 'cup', 969: 'eggnog', 970: 'alp', 971: 'bubble', 972: 'cliff, drop, drop-off', 973: 'coral reef', 974: 'geyser', 975: 'lakeside, lakeshore', 976: 'promontory, headland, head, foreland', 977: 'sandbar, sand bar', 978: 'seashore, coast, seacoast, sea-coast', 979: 'valley, vale', 980: 'volcano', 981: 'ballplayer, baseball player', 982: 'groom, bridegroom', 983: 'scuba diver', 984: 'rapeseed', 985: 'daisy', 986: "yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum", 987: 'corn', 988: 'acorn', 989: 'hip, rose hip, rosehip', 990: 'buckeye, horse chestnut, conker', 991: 'coral fungus', 992: 'agaric', 993: 'gyromitra', 994: 'stinkhorn, carrion fungus', 995: 'earthstar', 996: 'hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa', 997: 'bolete', 998: 'ear, spike, capitulum', 999: 'toilet tissue, toilet paper, bathroom tissue'} +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" +pipeline = Pipeline.create( + task="image_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + class_names = classes, + top_k = 3 +) + +# run inference on image file +prediction = pipeline(images="lion.jpeg") +print(prediction) +# labels=['lion, king of beasts, Panthera leo', 'chow, chow chow', 'Tibetan mastiff'] scores=[24.792997360229492, 19.385034561157227, 16.349166870117188] +``` +### Cross Use Case Functionality +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. + +## DeepSparse Server +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. + +The CLI command below launches an image classification pipeline with a 95% pruned ResNet model: + +```bash +deepsparse.server --task image_classification --model_path "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none" --port 5543 +``` +You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. + +Here is an example client request, using the Python requests library for formatting the HTTP: + +```python +import requests + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['lion.jpeg'] # just put the name of images in here +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +print(resp.text) +# {"labels":[291],"scores":[24.185693740844727]} +``` +####Use Case Specific Arguments +To use the `top_k` argument, create a Server configuration file for passing the argument via kwargs. + +This configuration file sets `top_k` classes to 3: +```yaml +# image_classification-config.yaml +endpoints: + - task: image_classification + model: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none + kwargs: + top_k: 3 +``` +Start the server: +```bash +deepsparse.server --config-file image_classification-config.yaml +``` +Run inference: +```python +import requests + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['lion.jpeg'] # just put the name of images in here +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +print(resp.text) +# {"labels":[291,260,244],"scores":[24.185693740844727,18.982254028320312,16.390701293945312]} +``` +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file From a7acf3cd071e3b1bc25039d714fdef6c77369fe8 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Thu, 23 Mar 2023 15:09:13 +0300 Subject: [PATCH 042/149] add image classification doc --- use-cases/cv/image-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use-cases/cv/image-classification.md b/use-cases/cv/image-classification.md index 9296c7d551..0177bdaf58 100644 --- a/use-cases/cv/image-classification.md +++ b/use-cases/cv/image-classification.md @@ -166,7 +166,7 @@ resp = requests.post(url=url, files=files) print(resp.text) # {"labels":[291],"scores":[24.185693740844727]} ``` -####Use Case Specific Arguments +#### Use Case Specific Arguments To use the `top_k` argument, create a Server configuration file for passing the argument via kwargs. This configuration file sets `top_k` classes to 3: From 2b74cf18feb2e110843aeef219657f9b80f70e7c Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Thu, 23 Mar 2023 16:10:45 +0300 Subject: [PATCH 043/149] add yolo document --- use-cases/cv/yolov5-object-detection.md | 199 ++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 use-cases/cv/yolov5-object-detection.md diff --git a/use-cases/cv/yolov5-object-detection.md b/use-cases/cv/yolov5-object-detection.md new file mode 100644 index 0000000000..388fdda3e6 --- /dev/null +++ b/use-cases/cv/yolov5-object-detection.md @@ -0,0 +1,199 @@ +# Deploying YOLOv5 Object Detection Models with DeepSparse + +This page explains how to benchmark and deploy a YOLOv5 object detection model with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. + +### ONNX Runtime Baseline + +As a baseline, let's check out ONNX Runtime's performance on YOLOv5. Make sure you have ORT installed (`pip install onnxruntime`). +```bash +deepsparse.benchmark \ + zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 \ + -b 64 -s sync -nstreams 1 \ + -e onnxruntime +> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 15.1734 +> Latency Mean (ms/batch): 4217.1713 +> Latency Median (ms/batch): 4088.7618 +> Latency Std (ms/batch): 274.9809 +> Iterations: 3 +``` +ONNX Runtime achieves 15 items/second with batch 64. +### DeepSparse Speedup +Now, let's run DeepSparse on an inference-optimized sparse version of YOLOv5 . This model has been 94% pruned-quantized, while retaining >99% accuracy of the dense baseline on the `coco` dataset. +```bash +!deepsparse.benchmark \ + zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 \ + -b 64 -s sync -nstreams 1 \ + -e deepsparse +> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 237.4027 +> Latency Mean (ms/batch): 269.5658 +> Latency Median (ms/batch): 268.4632 +> Latency Std (ms/batch): 3.4354 +> Iterations: 38 +``` +DeepSparse achieves 237 items/second, a 16x speed-up over ONNX Runtime! +## DeepSparse Engine +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 98% pruned-quantized YOLOv5 trained on `coco` from SparseZoo: +```python +from deepsparse import compile_model +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-l/pytorch/ultralytics/coco/pruned-aggressive_98" +batch_size = 1 +bert_engine = compile_model( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = bert_engine(inputs) +print(output) +# [array([[[5.54789925e+00, 4.28643513e+00, 9.98156166e+00, ..., +# ... +# -6.13238716e+00, -6.80812788e+00, -5.50403357e+00]]]]], dtype=float32)] +``` +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +Let's start by downloading a sample image: +```bash +wget https://huggingface.co/spaces/neuralmagic/cv-yolo/resolve/main/Fruits.png +``` +We will use the `Pipeline.create()` constructor to create an instance of an object detection Pipeline with a 96% pruned version of YOLOv5 trained on `coco`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images) is handled by the `Pipeline`. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned-aggressive_96" +yolo_pipeline = Pipeline.create( + task="yolo", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX +) +images = ["Fruits.png"] + +# run inference on image file +pipeline_outputs = yolo_pipeline(images=images, conf_thres=0.001) +print(len(pipeline_outputs.boxes[0])) +print(len(pipeline_outputs.scores[0])) +print(len(pipeline_outputs.labels[0])) +# 135 +# 135 +# 135 +``` +### Use Case Specific Arguments +The Object Detection Pipeline contains additional arguments for configuring a `Pipeline`. + +#### IOU Threshold +In the example below, we define a `iou_thres` of 0.6. You can adjust this depending on your use case. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned-aggressive_96" +yolo_pipeline = Pipeline.create( + task="yolo", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size = 3 +) +images = ["Fruits.png"] * 3 + +# run inference on image file +pipeline_outputs = yolo_pipeline(images=images, iou_thres=0.6, conf_thres=0.001) +print(len(pipeline_outputs.boxes[0])) +print(len(pipeline_outputs.scores[0])) +print(len(pipeline_outputs.labels[0])) +# 300 +# 300 +# 300 +``` +### Cross Use Case Functionality +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +## DeepSparse Server +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. + +The CLI command below launches an object detection pipeline with a 94% pruned-quantized YOLOv5 model: + +```bash +deepsparse.server task yolo --model_path "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94" --port 5543 +``` +```python +import requests +import json + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['pets.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) # dictionary of annotation results +bounding_boxes = annotations["boxes"] +labels = annotations["labels"] +print(labels) +# [['16.0', '16.0', '16.0', '15.0', '15.0']] +``` +#### Use Case Specific Arguments +To use the `class_names` argument, create a Server configuration file for passing the argument via kwargs. + +This configuration file sets `class_names` to `coco`: +```yaml +# yolo-config.yaml +endpoints: + - task: yolo + model: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 + kwargs: + class_names: coco +``` +Start the server: +```bash +deepsparse.server --config-file yolo-config.yaml +``` +Run inference: +```python +import requests +import json + +url = 'http://0.0.0.0:5555/predict/from_files' +path = ['pets.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) # dictionary of annotation results +bounding_boxes = annotations["boxes"] +labels = annotations["labels"] +print(labels) +# [['dog', 'dog', 'dog', 'cat', 'cat']] +``` +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file From 9fed9c8bcbf49598aa6f3bfa57530a1bb49f3736 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Fri, 24 Mar 2023 09:22:35 +0300 Subject: [PATCH 044/149] add YOLACT doc --- use-cases/cv/image-segmentation-yolact.md | 206 ++++++++++++++++++++++ use-cases/cv/images/result-0.jpg | Bin 0 -> 331832 bytes 2 files changed, 206 insertions(+) create mode 100644 use-cases/cv/image-segmentation-yolact.md create mode 100644 use-cases/cv/images/result-0.jpg diff --git a/use-cases/cv/image-segmentation-yolact.md b/use-cases/cv/image-segmentation-yolact.md new file mode 100644 index 0000000000..bded2c42e3 --- /dev/null +++ b/use-cases/cv/image-segmentation-yolact.md @@ -0,0 +1,206 @@ +# Deploying Image Segmentation Models with DeepSparse + +This page explains how to benchmark and deploy an image segmentation with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. + +### ONNX Runtime Baseline + +As a baseline, let's check out ONNX Runtime's performance on YOLACT. Make sure you have ORT installed (`pip install onnxruntime`). + +```bash +deepsparse.benchmark \ + zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/base-none \ + -b 64 -s sync -nstreams 1 \ + -e onnxruntime + +> Original Model Path: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/base-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 8.7788 +> Latency Mean (ms/batch): 7290.2676 +> Latency Median (ms/batch): 7290.2676 +> Latency Std (ms/batch): 418.0256 +> Iterations: 2 +``` +ONNX Runtime achieves 9 items/second with batch 64. +### DeepSparse Speedup +Now, let's run DeepSparse on an inference-optimized sparse version of YOLACT. This model has been 90% pruned, while retaining >99% accuracy of the dense baseline on the `coco` dataset. +```bash +deepsparse.benchmark \ + zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned90-none \ + -b 64 -s sync -nstreams 1 \ + -e deepsparse + +> Original Model Path: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned90-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 27.7439 +> Latency Mean (ms/batch): 2306.7927 +> Latency Median (ms/batch): 2297.4245 +> Latency Std (ms/batch): 16.7005 +> Iterations: 5 +``` +DeepSparse achieves 28 items/second, a 3x speed-up over ONNX Runtime! +## DeepSparse Engine +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 90% pruned YOLACT model trained on `coco` from SparseZoo: +```python +from deepsparse import compile_model +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned90-none" +batch_size = 1 +bert_engine = compile_model( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) + +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = bert_engine(inputs) +print(output) +# [array([[[ 0.444973 , -0.02015 , -1.3631972 , -0.9219434 ], +# ... +# 9.50585604e-02, 4.13608968e-01, 1.57236055e-01]]]], dtype=float32)] +``` +## DeepSparse Pipelines +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +Let's start by downloading a sample image: +```bash +wget https://huggingface.co/spaces/neuralmagic/cv-yolact/resolve/main/thailand.jpeg +``` +We will use the `Pipeline.create()` constructor to create an instance of an image segmentation Pipeline with a 82% pruned-quantized version of YOLACT trained on `coco`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images) is handled by the `Pipeline`. + +```python +from deepsparse.pipeline import Pipeline + +model_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" +images = ["thailand.jpeg"] + +yolact_pipeline = Pipeline.create( + task="yolact", + model_path=model_stub, +) + +predictions = yolact_pipeline(images=images, confidence_threshold=0.2,nms_threshold = 0.5) +# predictions has attributes `boxes`, `classes`, `masks` and `scores` +predictions.classes[0] +# [20,......, 5] +``` +### Use Case Specific Arguments +The Image Segmentation Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Classes +The `class_names` argument defines a dictionary containing the desired class mappings. + +```python +from deepsparse.pipeline import Pipeline + +model_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" +images = ["thailand.jpg"] + +yolact_pipeline = Pipeline.create( + task="yolact", + model_path=model_stub, + class_names="coco", +) + +predictions = yolact_pipeline(images=images, confidence_threshold=0.2,nms_threshold = 0.5) +# predictions has attributes `boxes`, `classes`, `masks` and `scores` +predictions.classes[0] +['elephant','elephant','person',...'zebra','stop sign','bus'] +``` +### Annotate CLI +You can also use the annotate command to have the engine save an annotated photo on disk. +```bash +deepsparse.instance_segmentation.annotate --source thailand.jpg #Try --source 0 to annotate your live webcam feed +``` +Running the above command will create an `annotation-results` folder and save the annotated image inside. + +If a `--model_filepath` arg isn't provided, then `zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none` will be used by default. + +![Annotation Results](images/result-0.jpg) + +### Cross Use Case Functionality +Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. + +## DeepSparse Server +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. + +The CLI command below launches an image segmentation pipeline with a 82% pruned-quantized YOLACT model: + +```bash +deepsparse.server \ + task yolact \ + --model_path "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" --port 5543 +``` +Run inference: +```python +import requests +import json + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['thailand.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) # dictionary of annotation results +boxes, classes, masks, scores = annotations["boxes"], annotations["classes"], annotations["masks"], annotations["scores"] +``` +#### Use Case Specific Arguments +To use the `class_names` argument, create a Server configuration file for passing the argument via kwargs. + +This configuration file sets `class_names` to `coco`: + +```yaml +# yolact-config.yaml +endpoints: + - task: yolact + model: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none + kwargs: + class_names: coco +``` +Start the server: +```bash +deepsparse.server --config-file yolact-config.yaml +``` +Run inference: +```python +import requests +import json + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['pets.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) # dictionary of annotation results +boxes, classes, masks, scores = annotations["boxes"], annotations["classes"], annotations["masks"], annotations["scores"] +``` +### Cross Use Case Functionality + +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. diff --git a/use-cases/cv/images/result-0.jpg b/use-cases/cv/images/result-0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f485f70677594953b69225fb1bf8e364ea1438a2 GIT binary patch literal 331832 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Ҋl*^L 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%0AjN>@t^}FAn@9!Uf9%tP5 z^S-XfME3zx%y~vV%e}o3@~-#CMB3x39@qrCe~ZTTBlJu}@xVZ!AM&sF@P8cIp3K3n zRDIH%`e$v8gydEA1f zOXmURjQTK@>BH3i#KG|ydi<~b292`NfdeG>{gtqPwm@;P%g@sJlpCmYr=erJbxHc}Y%58Q=cCiThYuTP=o7WP%fDp2O-fK-h&tG$YM-7oK{kIXQq>68gEve$X`&}5C8Hb&=rco#uzoDXC%vcZ&>TxxMGZ4 z=yJbd_8~4&@7V3`b%iV(sC_8FYHMcuQo!~#ePLlu!ceTeTp=$zZ!rp9^**5TdKbsT9Z}*6z9)=Y-k9?Ab7kJF4 zND)L@WUW(E6`!Gq52x5ekv39ualqP9`HrN0($e_v=zdgag&l0Jo6)9o_jAG%D(gx2 zszFPd5f*)Ogfk^DBivbi^GE3Y#!>YXTO*z6I@ui>;#k9OK`>6nBUX%pBpNUwS=$M} z?zrU5(_1Y6h`U7aDGvz@ttxF*0f$%p%aGtU;+(Z=QHs>rCo&0!BEI6f|0aF}zOxj|Zd)md^eUHwUr);OjOgnXNrTg|S6-VZO zC>;{j_US1Pwafei8M9CIteYFvY8aExm78w^jvCMCozoPjhA%3$;^qr^xz92U+(Y(6zL-K{t(Ch3KK~*n(_gOzJhb{Uo0bwF)}@Iw$9P z{~&u2WAN{_f)Q1&SJBz>X;RfN-zU@iV%C4JHD;dC>ZQq@$LyH*eDu%vTaY^q)6AqQ z%)s`$yw9qw!ZK|~D{epvWp75b7PM{TFT+UJ)i_IKc9Hly|2@NiUuUmT^_E7c){2+Pq)b7zZHb(`qQslg#~fxolQ*({uLF z?_kv1RqV@va8Zw1#ql2pc48Nn4lPc4^smEj*PODuS60DIXu&u-V8S<)%>QTHtN;FB zdp;s<?FW7V^w!&jTwV1*owjad!FNNwe?wK z4GQaQeiEtJPk>BxU@@1JX?T6F0Tuegp?LAex*8<^okedIsG9;Bi=$@qCBDk-VAOZ| zrZ`3fE+ZIsytU+Hmw=V=CrT;XW<_Jf9JcSVUNMUrI)P0Wpn$^a)s#a^xi~L>A9NQ>g=)&k}1AJsJB|ZCde_>d4$5PGVe@a zl+b$qa7`Xnt(*%P$Q3W3E)u@htb$39$Or>DI04nDz!+*t*)N^qjI%y~l3w!t80w{m z^#df)>wnc4E7^ClKJtVIlW()Czw@dkd_~iOep*N0mU=6hqw^A)f+L);YWOg3u4^@I z->jqw%&n1XO_ zb1X=VBF4&Wtf|nd5PF;3j^3&41`N;tagg+#T{Pt*o0T^P&a*iAx<%(+m;@uSfF*)T9DA}H!-E{HN zAXn{{uUOAwy@q@#aHxbE@%g=N=6hX4iYAM=0nxzg$r`R)D6wR%V;wIyzUzxru7}KoQ|9Q{ zzrge9!>xBvGLia&01(>IL*T&C3eHr}fS>nD_skJ2CbQOCYA~6<;eI!1a7@Dj4C=4X za`#r5U;O(0GM{j9f5<}B%;mpd2BgQV^yyG)1=lC7r=$AchKC_U-%bBp?Wv~7f_ z6eC?Pmdswgmtm(|XRh>VRzFWIbt654@7?O{6Ce2szo~W<;A?$m_Fs#C8a_8__a0DB z7>gYq{9n!(a_1x#7!4(%6Z4s4#&wP|2Z$Y`TF~WLP&NGtsuFqU_2$?EA$1@OLM{{= zcTYUTFt;^u2|<7E^9xL%dk)-OQK~GJd(<%6tAV-x`8#WeMXc~z8!w;I9d#N^IF%lo zEp)TXGqX~1mFCOwtwK;kz?BDSm~^2QHW}Prp#BVSGF;YQ z@9XWMVPgsrcZQR_g9>7XWS!bkbZ@ciYv_c5R53!OfmyP3LPy5~`N|J4Wm^Hsd8h~) zxJH@MpTHr{`urX(`J~c%Ik{le5zU4jG?5AD9nGEHCAEFO(u2~`X6#9i>HB^G;+Epe zi%!mVSsvYmeno*HPZ|7)K`VqILG*;EV>@@eFCdM%pcUF7hG^zVOr#}NLL!*`8Z2;c zL8|CTGa>uWL4XoC?v7?^JfGd$aM}QSNME!iNVyNRZQquw2wi4gKUz;oYhAQ-df@1d z8lqk1p3PmLn9c;Hx$#|-dhyo4LCZeiC6*N0ofY7QxuQ=08rB6_6vbr;4VJAyRSU8Z z$`(tN+N9&3hotEzDJdz{pQq_ZD+Sne_MUnHw~QQ{yzW2f?8o_d`csKBd&8SC(aX;_ zU<2Q4-$Jmg%S!tAu9(0EV?)csUAmVGSu1-kurySphs(buq_V`cVA?W)7VZ%ez78jl zz(G=Xb;nI_dfh{)RdZYEG`;jp#Fmi5I;a$fU39>&K517N46!&WDa_Xnw z%8EoFJF$jLxc2u5&0PEFYN5(){!`|&!rU|?N-OyW0p_D`7mI16LlbDI#{+VzT31Q4 ze3pO9Of&_EodU@sgN|SM<$sSHZT3U6>HoCJvT=jaK*{0hKckmZU>yhUA8zPFKHOzs zBh-OZC$dycGgbO?BiDCBtXv3doum4A!*+)ZIpE?O6cACtQz~Y3-|3HuP58ECXK5n` zq}iKZ(^Cb7N3QM~Q(s9^X6EfEl|<5g7d_ubE&Tb8*a)xr!>URk-a$QaVkgtS>js^NPmd z7zr+^0!O&ek(dNvK7t65O{`KHs331j!<#E;?&`!`arU0xN-CU8D}4KH2OK6Sj^-|z z1_NAXbb+~3*Z_J7zF_m=+bOxq5q}Tgn!g%kD0oZgW5BkOab`|swX+4D$Ubq8m%G&| zlv4ZyA6EeU6A^8D3m=u-I}H>J`_G2aPG}F~8}Nd`Z$t{Kfv+SLXKq#xy&OvdoIiT< z9=R&L@37#|BOhP;!qj&f2@6({HHeSm%`VU$#sL| z_uljS%nFw5RPLpKs(tqr>f&RYG6eA+Ps602#GM-qz&PIWZ{ty2i+ITOzNXq9KlH{uE{(APHBN77 z$pHJE-FdVUwb1qRx{7zKK&Du_Pxm5ve5a%m@;7p(;CP1wmO)ghr0loB=1YADE|04M z&MP}T&M4fs?Z_Q_bFD6obd!}MpV;&siYMR3vo8$@=Zeo`puSJ4>3TQN46Jd*y{diT z!o+~muwkOoY#0Iipf2o>e6?mdYIp;K-F9T37;@qC9rO!IG%g0G4{ml#gXct50aTCf zah)7Ke)dF+G6_yd{R=|2K65J9w0=tR9%YE_#HVm2^?3WO%DoU2pgnEEQ(2q48%(t$ zK7t^b>7^G50?JPS*Xax}a;l{TKn0cTBh8#XB3Ok9mB^88h-XZ_>rTEGADo7i)%8Tg z!?ePdg^@u6t`vv5qn%lW(U;NuGtwI9)8~k(-*fhgYPm7#i?}rm@J>)8>m?hr1}yjg=KEqbJb*ynud-FgUYV8|2F@vEn4|-r0ET(8 zrTRcHOF6Z@`9F?RQzed5-pQv3{{+KZZ%_x9Q;<#4f`|#8-rwx?^=t82J;gK+{|NPF zuG(X%CoC%0IdaByy}G04Tr*Og?QT^-Ib%{V`tye4wc+y(G&0e)p1bvnvZ2r~-}^z} zsR8Ms0la1#nTTOcS!>lr_RrcJ6$tqJ3|%xj`PTYlqi;6&qrXASb%**Sxa`$Jn!MEv zdm5Zcs<=M+&4rI{RALK%zaEPNMA|l zDe`ob-&?(Dmj+EAXR}W|BP(I+Ap~oMrZ{_|gh+Qku|2qowKoF9#xW(6INyJ`n>tl` zg$y!a!f2DW<1)?HXt=hq@C}{wfJxJGpo~zu)7B?MGN;=DNW{?+euIr9NPU!v7-m`T zHz_~;TvUDbZBqDvA2P1wCn$}!MFbwwn-%8F8`)q}P=i4O^i|t&uS*%HW$$zUI-!4= zR(YnsOWAo_WQM)@8Gs#t2QSp_O6DfvMAkEgA*Qn1tuYY4W)q0IwEbL07uDjF86Ia9 z*RJ~ReS-%vs3&lpXg1#RtknI^aICyi1!X0k{YRur79&^E^Wr{VB3xEC+=HZjldzVF z{-jv%^k=2Dv8jo7cGiw}TyW-?ktjB*v+x&uvHPWJ^rCscKEG~+<?d&9AD@4OXoa zWZkmQ!!kS!Tvdz;7=P;1fW>DFSX+>4{`ScKN&8ieehFy1ZyJWh{l{^9_W(FE&&0

gEwYS?cO~ZClZ{sUGt7m%Q#|NX&$Zb3Fb}4P!73C zod*5^$$s?QppJ>(0|76%ZAF?kC*$afC_;%N2{r#5i%KtGodvGS{K#F`c1_|^Q>h!0 z0bh}4Wxk-Y@=rjoD%xNn%+@RX*y z^c%59j%#{U@BfPm3 zI%W`MCTcq`xV_GIqNrwXE7AKpC7Jar00locA$pTO%+u?be2Tqem8BJbzb-L4JF=)p k Date: Fri, 24 Mar 2023 09:35:12 +0300 Subject: [PATCH 045/149] update yolov5 doc --- use-cases/cv/images/result.jpg | Bin 0 -> 390867 bytes use-cases/cv/yolov5-object-detection.md | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 use-cases/cv/images/result.jpg diff --git a/use-cases/cv/images/result.jpg b/use-cases/cv/images/result.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a6df579ca56fda9aa82ad4de951c384ed0c87ba GIT binary patch literal 390867 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

5-AzQCQgu&mzfTtFlW-s#G^)U;QxBs)-QsYo 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 Date: Fri, 24 Mar 2023 09:49:49 +0300 Subject: [PATCH 046/149] update yolov5 doc --- use-cases/cv/yolov5-object-detection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use-cases/cv/yolov5-object-detection.md b/use-cases/cv/yolov5-object-detection.md index bf5cb95f12..7152096a7e 100644 --- a/use-cases/cv/yolov5-object-detection.md +++ b/use-cases/cv/yolov5-object-detection.md @@ -42,7 +42,7 @@ ONNX Runtime achieves 15 items/second with batch 64. ### DeepSparse Speedup Now, let's run DeepSparse on an inference-optimized sparse version of YOLOv5 . This model has been 94% pruned-quantized, while retaining >99% accuracy of the dense baseline on the `coco` dataset. ```bash -!deepsparse.benchmark \ +deepsparse.benchmark \ zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 \ -b 64 -s sync -nstreams 1 \ -e deepsparse From 46e78c2e8c226eece171215bed3943a7f44c3e9b Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Mon, 3 Apr 2023 11:27:33 +0300 Subject: [PATCH 047/149] Update yolov5-object-detection.md --- use-cases/cv/yolov5-object-detection.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/use-cases/cv/yolov5-object-detection.md b/use-cases/cv/yolov5-object-detection.md index 7152096a7e..df606156b9 100644 --- a/use-cases/cv/yolov5-object-detection.md +++ b/use-cases/cv/yolov5-object-detection.md @@ -63,14 +63,14 @@ With Engine, we can compile an ONNX file and run inference on raw tensors. Here's an example, using a 98% pruned-quantized YOLOv5 trained on `coco` from SparseZoo: ```python -from deepsparse import compile_model +from deepsparse import Engine from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:cv/detection/yolov5-l/pytorch/ultralytics/coco/pruned-aggressive_98" batch_size = 1 -bert_engine = compile_model( +bert_engine = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) @@ -204,4 +204,4 @@ print(labels) ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. From 92e4dc8e15c0f2cdf69398e0751b75e788710357 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Mon, 3 Apr 2023 11:29:04 +0300 Subject: [PATCH 048/149] Update image-classification.md --- use-cases/cv/image-classification.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/use-cases/cv/image-classification.md b/use-cases/cv/image-classification.md index 0177bdaf58..69b72add6f 100644 --- a/use-cases/cv/image-classification.md +++ b/use-cases/cv/image-classification.md @@ -67,14 +67,14 @@ With Engine, we can compile an ONNX file and run inference on raw tensors. Here's an example, using a 90% pruned-quantized ResNet-50 trained on `imagenet` from SparseZoo: ```python -from deepsparse import compile_model +from deepsparse import Engine from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none" batch_size = 1 -bert_engine = compile_model( +bert_engine = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) @@ -195,4 +195,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file +Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. From d259fbca979a378187078dc71b13e91d0a998b7e Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Mon, 3 Apr 2023 11:29:39 +0300 Subject: [PATCH 049/149] Update image-segmentation-yolact.md --- use-cases/cv/image-segmentation-yolact.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/use-cases/cv/image-segmentation-yolact.md b/use-cases/cv/image-segmentation-yolact.md index bded2c42e3..c2d5ed130a 100644 --- a/use-cases/cv/image-segmentation-yolact.md +++ b/use-cases/cv/image-segmentation-yolact.md @@ -66,14 +66,14 @@ With Engine, we can compile an ONNX file and run inference on raw tensors. Here's an example, using a 90% pruned YOLACT model trained on `coco` from SparseZoo: ```python -from deepsparse import compile_model +from deepsparse import Engine from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned90-none" batch_size = 1 -bert_engine = compile_model( +bert_engine = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) From 59c3efe7a0f5eed53dbf04244ba99738d25074b3 Mon Sep 17 00:00:00 2001 From: Michael Goin Date: Wed, 12 Apr 2023 19:16:50 -0400 Subject: [PATCH 050/149] Apply suggestions from code review Co-authored-by: Jeannie Finks <74554921+jeanniefinks@users.noreply.github.com> --- user-guide/README.md | 14 +++++++------- user-guide/deepsparse-benchmarking.md | 6 +++--- user-guide/deepsparse-pipelines.md | 16 ++++++++-------- user-guide/deepsparse-server.md | 12 ++++++------ user-guide/installation.md | 2 +- user-guide/scheduler.md | 4 ++-- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/user-guide/README.md b/user-guide/README.md index 4eca2a82c7..95d0d1e6d6 100644 --- a/user-guide/README.md +++ b/user-guide/README.md @@ -51,16 +51,16 @@ DeepSparse achieves a ***12x speedup*** over ORT! ## Deployment APIs -Now that we have seen DeepSparse's performance gains, let's take a look at how we can add DeepSparse to an application. +Now that we have seen DeepSparse's performance gains, we can add DeepSparse to an application. DeepSparse includes three deployment APIs: -- Engine is the lowest-level API. With Engine, you pass tensors and recieve the raw logits. +- Engine is the lowest-level API. With Engine, you pass tensors and receive the raw logits. - Pipeline wraps the Engine with pre- and post-processing. With Pipeline, you pass raw data and -recieve the prediction. +receive the prediction. - Server wraps Pipelines with a REST API using FastAPI. With Server, you send raw data over HTTP -and recieve the prediction. +and receive the prediction. -Let's walk through a simple example of each API to give a sense of usage. As an example, we will use +The following are simple examples of each API to get a sense of how it is used. For the example, we will use the sentiment analysis use-case with a 90% pruned-quantized version of BERT. ### Engine @@ -123,7 +123,7 @@ outputs = engine.run(inputs) Pipeline is the default API for interacting with DeepSparse. Similar to Hugging Face Pipelines, DeepSparse Pipelines wrap Engine with pre- and post-processing (as well as other utilities), -enabling you to send raw data to DeepSparse and recieve the post-processed prediction. +enabling you to send raw data to DeepSparse and receive the post-processed prediction. The example below downloads a 90% pruned-quantized BERT model for sentiment analysis in ONNX format from SparseZoo, sets up a pipeline, and runs inference on sample data. @@ -150,7 +150,7 @@ print(prediction) ### [Server](deepsparse-server.md) Server wraps Pipelines with REST APIs, that make it easy to stand up a model serving endpoint -running DeepSparse. This enables you to send raw data to DeepSparse over HTTP and recieve the post-processed +running DeepSparse. This enables you to send raw data to DeepSparse over HTTP and receive the post-processed predictions. DeepSparse Server is launched from the command line, configured via arguments or a server configuration file. diff --git a/user-guide/deepsparse-benchmarking.md b/user-guide/deepsparse-benchmarking.md index b47ae4de16..0578a92a4d 100644 --- a/user-guide/deepsparse-benchmarking.md +++ b/user-guide/deepsparse-benchmarking.md @@ -75,11 +75,11 @@ faster than ORT at high batch sizes. All in all, in this example **DeepSparse is Run `deepsparse.benchmark -h` to see full command line arguments. -Let's walk through a few examples of common functionality. +These are a few examples of common functionality. ### Pass Your Local ONNX Model -Beyond passing SparseZoo stubs, you can also pass a local path to an ONNX file to DeepSparse. As an example, let's download an ONNX file from SparseZoo using the CLI to a local directory called `./yolov5-download`. +Beyond passing SparseZoo stubs, you can also pass a local path to an ONNX file to DeepSparse. As an example, download an ONNX file from SparseZoo using the CLI to a local directory called `./yolov5-download`. ```bash sparsezoo.download zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none --save-dir yolov5-download @@ -135,7 +135,7 @@ deepsparse.benchmark zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/s >> Throughput (items/sec): 121.7578 ``` -Here's an example doing a YOLOv5s inference with a 320x320 image (rather than 640x640 as above) +Here's an example doing a YOLOv5s inference with a 320x320 image (rather than 640x640 as above): ```bash deepsparse.benchmark zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none -i [1,3,320,320] diff --git a/user-guide/deepsparse-pipelines.md b/user-guide/deepsparse-pipelines.md index 2093ebcb20..7c411dcd3d 100644 --- a/user-guide/deepsparse-pipelines.md +++ b/user-guide/deepsparse-pipelines.md @@ -3,11 +3,11 @@ Pipelines are the default API for deploying a model with DeepSparse. Similar to Hugging Face Pipelines, DeepSparse Pipelines wrap inference with task-specific -pre- and post-processing, enabling you to pass raw data and recieve the predictions. +pre- and post-processing, enabling you to pass raw data and receive the predictions. ## Quickstart -Let's try a quick example of the Pipeline API. All we have to do is pass a task and model to the +Let us try a quick example of the Pipeline API. All we have to do is pass a task and model to the the `Pipeline.create` function, and then we can run inference on raw data using DeepSparse! This example creates a sentiment analysis Pipeline with a 90% pruned-quantized verion of BERT @@ -61,8 +61,8 @@ of numpy arrays that can be passed directly to the inference forward pass - `process_outputs_fn` - an optional function that handles post-processing of the list of numpy arrays that are the output of the engine forward pass -Let's demonstrate an example of how we could replicate the functionality of the image classification -pipeline as a custom Pipeline. +To replicate the functionality of the image classification +pipeline as a custom Pipeline, an example is provided. Download an image and ONNX file (a 95% pruned-quantized ResNet-50) for the demo: ``` @@ -176,7 +176,7 @@ print(output) We can utilize an the multi-stream capabilites of DeepSparse to make requests with dynamic batch sizes. -Let's create an example with a single sentiment analysis Pipeline with dynamic batch sizes by +Let us create an example with a single sentiment analysis Pipeline with dynamic batch sizes by setting the `batch_size` argument to None. Under the hood, the pipeline will split the batch into multiple asynchronous requests using the multi-stream scheduler. @@ -206,12 +206,12 @@ print(output_b4) ### Deploy Multiple Models on the Same System Some deployment scenarios will require running multiple instances of DeepSparse on a single -machine. DeepSparse includes a concepts called Context. Contexts can be used to run multiple +machine. DeepSparse includes a concept called Context. Contexts can be used to run multiple models with the same scheduler, enabling DeepSparse to manage the resources of the system effectively, keeping engines that are running different models from fighting over resources. -Let's create an example with multiple sentiment analysis Pipelines, one with batch size 1 (for maximum latency) -and one with batch size 32 (for maximum throughput). +To create an example with multiple sentiment analysis Pipelines, one with batch size 1 (for maximum latency) +and one with batch size 32 (for maximum throughput): ```python from concurrent.futures import ThreadPoolExecutor diff --git a/user-guide/deepsparse-server.md b/user-guide/deepsparse-server.md index 40cb18462a..101768bec8 100644 --- a/user-guide/deepsparse-server.md +++ b/user-guide/deepsparse-server.md @@ -20,7 +20,7 @@ ONNX file from the SparseZoo. To deploy your own model, pass a path to a `model. folder containing the `model.onnx` and supporting files (e.g., the Hugging Face `tokenizer.json` and `config.json`). Let's make a request over HTTP. Since the Server is a wrapper around Pipelines, -we can send raw data to the endpoint and recieve the post-processed predictions: +we can send raw data to the endpoint and receive the post-processed predictions: ```python import requests @@ -68,7 +68,7 @@ You can configure DeepSparse Server via YAML files. ### Basic Example -Let's walk through a basic example deploying via a configuration file. +Let us walk through a basic example of deploying via a configuration file. The following creates an endpoint running a 90% pruned-quantized version of BERT trained on the SST2 dataset for the sentiment analysis task. @@ -114,7 +114,7 @@ utilizes its multi-stream scheduler, which processes multiple requests at the sa In deployment scenarios with low batch sizes and high core counts, using the "multi-stream" scheduler can increase throughput by allowing DeepSparse to better saturate the cores. -The following configuration creates a Server with DeepSparse running on 2 cores, with 2 input streams, +The following configuration creates a Server with DeepSparse running on two cores, with two input streams, DeepSparse threads pinned to cores, and PyTorch provided with 2 threads. ```yaml @@ -192,7 +192,7 @@ print(f"From the dense model: {requests.post(dense_url, json=obj).text}") ### Endpoint Level Configuration -We can also configure properties of each endpoint, including task specific +We can also configure the properties of each endpoint, including task-specific arguments from within the YAML file. For instance, the following configuration file creates two endpoints. @@ -265,11 +265,11 @@ print(requests.post(sentiment_analysis_url, json=short_obj).text) # >>> {"labels":["positive","negative","positive"],"scores":[0.9665533900260925,0.9952980279922485,0.9939143061637878]} ``` -Checkout the use case pages for detailed documentation on task-specific arguments that can be applied to the Server via `kwargs`. +Check out the use case pages for detailed documentation on task-specific arguments that can be applied to the Server via `kwargs`. ## Custom Use Cases -Stay tuned for documentation on Using a custom DeepSparse Pipeline wihin the Server! +Stay tuned for documentation on using a custom DeepSparse Pipeline within the Server! ## Logging diff --git a/user-guide/installation.md b/user-guide/installation.md index 59bc31b072..48903c7271 100644 --- a/user-guide/installation.md +++ b/user-guide/installation.md @@ -2,7 +2,7 @@ DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.10.1, ONNX opset version 11+ and is [manylinux compliant](https://peps.python.org/pep-0513/). -It currently supports Intel and AMD AVX2, AVX512, and VNNI x86 instruction sets. +It currently supports Intel and AMD AVX2, AVX-512, and VNNI x86 instruction sets. ## General Install diff --git a/user-guide/scheduler.md b/user-guide/scheduler.md index b0669ca7ad..d3631cdd69 100644 --- a/user-guide/scheduler.md +++ b/user-guide/scheduler.md @@ -1,4 +1,4 @@ -# Inference Types with DeepSparse Scheduler +# Inference Types With DeepSparse Scheduler This page explains the various settings for DeepSparse, which enable you to tune the performance to your workload. @@ -22,7 +22,7 @@ There are circumstances in which more cores does not imply better performance. I An alternative, multi-stream scheduler is provided with the software. In cases where parallelism is low, sending multiple requests simultaneously can more adequately saturate the available cores. In other words, if speedup can't be achieved by adding more cores, then perhaps speedup can be achieved by adding more work. -If increasing core count does not decrease latency, that's a strong indicator that parallelism is low in your particular model/batch-size combination. It may be that total throughput can be increased by making more requests simultaneously. Using the [deepsparse.engine.Scheduler API,](https://docs.neuralmagic.com/deepsparse/api/deepsparse.html) the multi-stream scheduler can be selected, and requests made by multiple Python threads will be handled concurrently. +If increasing core count does not decrease latency, that's a strong indicator that parallelism is low in your particular model/batch-size combination. It may be that total throughput can be increased by making more requests simultaneously. Using the [deepsparse.engine.Scheduler API,](https://docs.neuralmagic.com/archive/deepsparse/api/deepsparse.html#module-deepsparse.engine) the multi-stream scheduler can be selected, and requests made by multiple Python threads will be handled concurrently. *Multi-stream scheduling; requests execute in parallel and may better utilize hardware resources:* From 6a1a7d91bfdb0363640546357ca41d11a3422ae0 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 17 Apr 2023 15:18:21 -0400 Subject: [PATCH 051/149] RS Edits to CV --- use-cases/cv/image-classification.md | 125 ++++++---- use-cases/cv/image-segmentation-yolact.md | 69 +++--- use-cases/cv/object-detection-yolov5.md | 265 ++++++++++++++++++++++ use-cases/cv/yolov5-object-detection.md | 207 ----------------- 4 files changed, 389 insertions(+), 277 deletions(-) create mode 100644 use-cases/cv/object-detection-yolov5.md delete mode 100644 use-cases/cv/yolov5-object-detection.md diff --git a/use-cases/cv/image-classification.md b/use-cases/cv/image-classification.md index 69b72add6f..9916869c4b 100644 --- a/use-cases/cv/image-classification.md +++ b/use-cases/cv/image-classification.md @@ -11,15 +11,17 @@ and post-processing steps, allowing you to make requests on raw data and receive - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. +We will walk through an example of each. + ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on an AWS `c6i.2xlarge` instance (4 cores). ### ONNX Runtime Baseline @@ -34,32 +36,28 @@ deepsparse.benchmark \ > Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 192.4109 -> Latency Mean (ms/batch): 332.6113 -> Latency Median (ms/batch): 331.6066 -> Latency Std (ms/batch): 2.9168 -> Iterations: 31 +> Throughput (items/sec): 71.83 ``` -ONNX Runtime achieves 192 items/second with batch 64. +ONNX Runtime achieves 72 items/second with batch 64. ### DeepSparse Speedup -Now, let's run DeepSparse on an inference-optimized sparse version of ResNet-50 . This model has been 90% pruned, while retaining >99% accuracy of the dense baseline on the `imagenet` dataset. + +Now, let's run DeepSparse on an inference-optimized sparse version of ResNet-50. This model has been 95% pruned, while retaining >99% accuracy of the dense baseline on the `imagenet` dataset. + ```bash -!deepsparse.benchmark \ - zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none \ +deepsparse.benchmark \ + zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none \ -b 64 -s sync -nstreams 1 \ -e deepsparse > Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 1034.6594 -> Latency Mean (ms/batch): 61.8392 -> Latency Median (ms/batch): 61.6361 -> Latency Std (ms/batch): 0.7029 -> Iterations: 162 +> Throughput (items/sec): 345.69 ``` -DeepSparse achieves 1035 items/second, an 5.3x speed-up over ONNX Runtime! + +DeepSparse achieves 346 items/second, an 4.8x speed-up over ONNX Runtime! + ## DeepSparse Engine Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. @@ -72,17 +70,18 @@ from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 -sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none" +sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" batch_size = 1 -bert_engine = Engine( +compiled_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) -output = bert_engine(inputs) +output = compiled_model(inputs) print(output) + # [array([[-7.73529887e-01, 1.67251182e+00, -1.68212160e-01, # .... # 1.26290070e-05, 2.30549040e-06, 2.97072188e-06, 1.90549777e-04]], dtype=float32)] @@ -96,30 +95,73 @@ Let's start by downloading a sample image: ```bash wget https://huggingface.co/spaces/neuralmagic/image-classification/resolve/main/lion.jpeg ``` -We will use the `Pipeline.create()` constructor to create an instance of an image classification Pipeline with a 90% pruned-quantized version of ResNet-50 trained on `imagenet`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images) is handled by the `Pipeline`. + +We will use the `Pipeline.create()` constructor to create an instance of an image classification Pipeline with a 90% pruned-quantized version of ResNet-50. We can then pass images to the Pipeline and receive the predictions. All the pre-processing (such as resizing the images and normalizing the inputs) is handled by the `Pipeline`. + +Passing the image as a JPEG to the Pipeline: ```python from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none" +sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" pipeline = Pipeline.create( task="image_classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX ) # run inference on image file -prediction = pipeline(images="lion.jpeg") -print(prediction) -# labels=[456] scores=[17.108182907104492] +prediction = pipeline(images=["lion.jpeg"]) +print(prediction.labels) +# [291] << class index of "lion" in imagenet ``` + +Passing the image as a numpy array to the Pipeline: + +```python +from deepsparse import Pipeline +from PIL import Image +import numpy as np + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" +pipeline = Pipeline.create( + task="image_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX +) + +im = np.array(Image.open("lion.jpeg")) + +# run inference on image file +prediction = pipeline(images=[im]) +print(prediction.labels) + +# [291] << class index of "lion" in imagenet +``` + ### Use Case Specific Arguments The Image Classification Pipeline contains additional arguments for configuring a `Pipeline`. -#### Top K and classes -The `top_k` argument allows you to define the number of classes to return. +#### Top K -The example below runs inference with `top_k = 3`, meaning that the pipeline will return the top 3 classes. +The `top_k` argument specifies the number of classes to return in the prediction. + +```python +from deepsparse import Pipeline + +sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" +pipeline = Pipeline.create( + task="image_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + top_k=3, +) + +# run inference on image file +prediction = pipeline(images="lion.jpeg") +print(prediction.labels) +# labels=[291, 260, 244] +``` +#### Class Names The `class_names` argument defines a dictionary containing the desired class mappings. @@ -127,22 +169,22 @@ The `class_names` argument defines a dictionary containing the desired class map from deepsparse import Pipeline classes = {0: 'tench, Tinca tinca',1: 'goldfish, Carassius auratus',2: 'great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias',3: 'tiger shark, Galeocerdo cuvieri',4: 'hammerhead, hammerhead shark',5: 'electric ray, crampfish, numbfish, torpedo',6: 'stingray',7: 'cock', 8: 'hen', 9: 'ostrich, Struthio camelus', 10: 'brambling, Fringilla montifringilla', 11: 'goldfinch, Carduelis carduelis', 12: 'house finch, linnet, Carpodacus mexicanus', 13: 'junco, snowbird', 14: 'indigo bunting, indigo finch, indigo bird, Passerina cyanea', 15: 'robin, American robin, Turdus migratorius', 16: 'bulbul', 17: 'jay', 18: 'magpie', 19: 'chickadee', 20: 'water ouzel, dipper', 21: 'kite', 22: 'bald eagle, American eagle, Haliaeetus leucocephalus', 23: 'vulture', 24: 'great grey owl, great gray owl, Strix nebulosa', 25: 'European fire salamander, Salamandra salamandra', 26: 'common newt, Triturus vulgaris', 27: 'eft', 28: 'spotted salamander, Ambystoma maculatum', 29: 'axolotl, mud puppy, Ambystoma mexicanum', 30: 'bullfrog, Rana catesbeiana', 31: 'tree frog, tree-frog', 32: 'tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui', 33: 'loggerhead, loggerhead turtle, Caretta caretta', 34: 'leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea', 35: 'mud turtle', 36: 'terrapin', 37: 'box turtle, box tortoise', 38: 'banded gecko', 39: 'common iguana, iguana, Iguana iguana', 40: 'American chameleon, anole, Anolis carolinensis', 41: 'whiptail, whiptail lizard', 42: 'agama', 43: 'frilled lizard, Chlamydosaurus kingi', 44: 'alligator lizard', 45: 'Gila monster, Heloderma suspectum', 46: 'green lizard, Lacerta viridis', 47: 'African chameleon, Chamaeleo chamaeleon', 48: 'Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis', 49: 'African crocodile, Nile crocodile, Crocodylus niloticus', 50: 'American alligator, Alligator mississipiensis', 51: 'triceratops', 52: 'thunder snake, worm snake, Carphophis amoenus', 53: 'ringneck snake, ring-necked snake, ring snake', 54: 'hognose snake, puff adder, sand viper', 55: 'green snake, grass snake', 56: 'king snake, kingsnake', 57: 'garter snake, grass snake', 58: 'water snake', 59: 'vine snake', 60: 'night snake, Hypsiglena torquata', 61: 'boa constrictor, Constrictor constrictor', 62: 'rock python, rock snake, Python sebae', 63: 'Indian cobra, Naja naja', 64: 'green mamba', 65: 'sea snake', 66: 'horned viper, cerastes, sand viper, horned asp, Cerastes cornutus', 67: 'diamondback, diamondback rattlesnake, Crotalus adamanteus', 68: 'sidewinder, horned rattlesnake, Crotalus cerastes', 69: 'trilobite', 70: 'harvestman, daddy longlegs, Phalangium opilio', 71: 'scorpion', 72: 'black and gold garden spider, Argiope aurantia', 73: 'barn spider, Araneus cavaticus', 74: 'garden spider, Aranea diademata', 75: 'black widow, Latrodectus mactans', 76: 'tarantula', 77: 'wolf spider, hunting spider', 78: 'tick', 79: 'centipede', 80: 'black grouse', 81: 'ptarmigan', 82: 'ruffed grouse, partridge, Bonasa umbellus', 83: 'prairie chicken, prairie grouse, prairie fowl', 84: 'peacock', 85: 'quail', 86: 'partridge', 87: 'African grey, African gray, Psittacus erithacus', 88: 'macaw', 89: 'sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita', 90: 'lorikeet', 91: 'coucal', 92: 'bee eater', 93: 'hornbill', 94: 'hummingbird', 95: 'jacamar', 96: 'toucan', 97: 'drake', 98: 'red-breasted merganser, Mergus serrator', 99: 'goose', 100: 'black swan, Cygnus atratus', 101: 'tusker', 102: 'echidna, spiny anteater, anteater', 103: 'platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus', 104: 'wallaby, brush kangaroo', 105: 'koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus', 106: 'wombat', 107: 'jellyfish', 108: 'sea anemone, anemone', 109: 'brain coral', 110: 'flatworm, platyhelminth', 111: 'nematode, nematode worm, roundworm', 112: 'conch', 113: 'snail', 114: 'slug', 115: 'sea slug, nudibranch', 116: 'chiton, coat-of-mail shell, sea cradle, polyplacophore', 117: 'chambered nautilus, pearly nautilus, nautilus', 118: 'Dungeness crab, Cancer magister', 119: 'rock crab, Cancer irroratus', 120: 'fiddler crab', 121: 'king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica', 122: 'American lobster, Northern lobster, Maine lobster, Homarus americanus', 123: 'spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish', 124: 'crayfish, crawfish, crawdad, crawdaddy', 125: 'hermit crab', 126: 'isopod', 127: 'white stork, Ciconia ciconia', 128: 'black stork, Ciconia nigra', 129: 'spoonbill', 130: 'flamingo', 131: 'little blue heron, Egretta caerulea', 132: 'American egret, great white heron, Egretta albus', 133: 'bittern', 134: 'crane', 135: 'limpkin, Aramus pictus', 136: 'European gallinule, Porphyrio porphyrio', 137: 'American coot, marsh hen, mud hen, water hen, Fulica americana', 138: 'bustard', 139: 'ruddy turnstone, Arenaria interpres', 140: 'red-backed sandpiper, dunlin, Erolia alpina', 141: 'redshank, Tringa totanus', 142: 'dowitcher', 143: 'oystercatcher, oyster catcher', 144: 'pelican', 145: 'king penguin, Aptenodytes patagonica', 146: 'albatross, mollymawk', 147: 'grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus', 148: 'killer whale, killer, orca, grampus, sea wolf, Orcinus orca', 149: 'dugong, Dugong dugon', 150: 'sea lion', 151: 'Chihuahua', 152: 'Japanese spaniel', 153: 'Maltese dog, Maltese terrier, Maltese', 154: 'Pekinese, Pekingese, Peke', 155: 'Shih-Tzu', 156: 'Blenheim spaniel', 157: 'papillon', 158: 'toy terrier', 159: 'Rhodesian ridgeback', 160: 'Afghan hound, Afghan', 161: 'basset, basset hound', 162: 'beagle', 163: 'bloodhound, sleuthhound', 164: 'bluetick', 165: 'black-and-tan coonhound', 166: 'Walker hound, Walker foxhound', 167: 'English foxhound', 168: 'redbone', 169: 'borzoi, Russian wolfhound', 170: 'Irish wolfhound', 171: 'Italian greyhound', 172: 'whippet', 173: 'Ibizan hound, Ibizan Podenco', 174: 'Norwegian elkhound, elkhound', 175: 'otterhound, otter hound', 176: 'Saluki, gazelle hound', 177: 'Scottish deerhound, deerhound', 178: 'Weimaraner', 179: 'Staffordshire bullterrier, Staffordshire bull terrier', 180: 'American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier', 181: 'Bedlington terrier', 182: 'Border terrier', 183: 'Kerry blue terrier', 184: 'Irish terrier', 185: 'Norfolk terrier', 186: 'Norwich terrier', 187: 'Yorkshire terrier', 188: 'wire-haired fox terrier', 189: 'Lakeland terrier', 190: 'Sealyham terrier, Sealyham', 191: 'Airedale, Airedale terrier', 192: 'cairn, cairn terrier', 193: 'Australian terrier', 194: 'Dandie Dinmont, Dandie Dinmont terrier', 195: 'Boston bull, Boston terrier', 196: 'miniature schnauzer', 197: 'giant schnauzer', 198: 'standard schnauzer', 199: 'Scotch terrier, Scottish terrier, Scottie', 200: 'Tibetan terrier, chrysanthemum dog', 201: 'silky terrier, Sydney silky', 202: 'soft-coated wheaten terrier', 203: 'West Highland white terrier', 204: 'Lhasa, Lhasa apso', 205: 'flat-coated retriever', 206: 'curly-coated retriever', 207: 'golden retriever', 208: 'Labrador retriever', 209: 'Chesapeake Bay retriever', 210: 'German short-haired pointer', 211: 'vizsla, Hungarian pointer', 212: 'English setter', 213: 'Irish setter, red setter', 214: 'Gordon setter', 215: 'Brittany spaniel', 216: 'clumber, clumber spaniel', 217: 'English springer, English springer spaniel', 218: 'Welsh springer spaniel', 219: 'cocker spaniel, English cocker spaniel, cocker', 220: 'Sussex spaniel', 221: 'Irish water spaniel', 222: 'kuvasz', 223: 'schipperke', 224: 'groenendael', 225: 'malinois', 226: 'briard', 227: 'kelpie', 228: 'komondor', 229: 'Old English sheepdog, bobtail', 230: 'Shetland sheepdog, Shetland sheep dog, Shetland', 231: 'collie', 232: 'Border collie', 233: 'Bouvier des Flandres, Bouviers des Flandres', 234: 'Rottweiler', 235: 'German shepherd, German shepherd dog, German police dog, alsatian', 236: 'Doberman, Doberman pinscher', 237: 'miniature pinscher', 238: 'Greater Swiss Mountain dog', 239: 'Bernese mountain dog', 240: 'Appenzeller', 241: 'EntleBucher', 242: 'boxer', 243: 'bull mastiff', 244: 'Tibetan mastiff', 245: 'French bulldog', 246: 'Great Dane', 247: 'Saint Bernard, St Bernard', 248: 'Eskimo dog, husky', 249: 'malamute, malemute, Alaskan malamute', 250: 'Siberian husky', 251: 'dalmatian, coach dog, carriage dog', 252: 'affenpinscher, monkey pinscher, monkey dog', 253: 'basenji', 254: 'pug, pug-dog', 255: 'Leonberg', 256: 'Newfoundland, Newfoundland dog', 257: 'Great Pyrenees', 258: 'Samoyed, Samoyede', 259: 'Pomeranian', 260: 'chow, chow chow', 261: 'keeshond', 262: 'Brabancon griffon', 263: 'Pembroke, Pembroke Welsh corgi', 264: 'Cardigan, Cardigan Welsh corgi', 265: 'toy poodle', 266: 'miniature poodle', 267: 'standard poodle', 268: 'Mexican hairless', 269: 'timber wolf, grey wolf, gray wolf, Canis lupus', 270: 'white wolf, Arctic wolf, Canis lupus tundrarum', 271: 'red wolf, maned wolf, Canis rufus, Canis niger', 272: 'coyote, prairie wolf, brush wolf, Canis latrans', 273: 'dingo, warrigal, warragal, Canis dingo', 274: 'dhole, Cuon alpinus', 275: 'African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus', 276: 'hyena, hyaena', 277: 'red fox, Vulpes vulpes', 278: 'kit fox, Vulpes macrotis', 279: 'Arctic fox, white fox, Alopex lagopus', 280: 'grey fox, gray fox, Urocyon cinereoargenteus', 281: 'tabby, tabby cat', 282: 'tiger cat', 283: 'Persian cat', 284: 'Siamese cat, Siamese', 285: 'Egyptian cat', 286: 'cougar, puma, catamount, mountain lion, painter, panther, Felis concolor', 287: 'lynx, catamount', 288: 'leopard, Panthera pardus', 289: 'snow leopard, ounce, Panthera uncia', 290: 'jaguar, panther, Panthera onca, Felis onca', 291: 'lion, king of beasts, Panthera leo', 292: 'tiger, Panthera tigris', 293: 'cheetah, chetah, Acinonyx jubatus', 294: 'brown bear, bruin, Ursus arctos', 295: 'American black bear, black bear, Ursus americanus, Euarctos americanus', 296: 'ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus', 297: 'sloth bear, Melursus ursinus, Ursus ursinus', 298: 'mongoose', 299: 'meerkat, mierkat', 300: 'tiger beetle', 301: 'ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle', 302: 'ground beetle, carabid beetle', 303: 'long-horned beetle, longicorn, longicorn beetle', 304: 'leaf beetle, chrysomelid', 305: 'dung beetle', 306: 'rhinoceros beetle', 307: 'weevil', 308: 'fly', 309: 'bee', 310: 'ant, emmet, pismire', 311: 'grasshopper, hopper', 312: 'cricket', 313: 'walking stick, walkingstick, stick insect', 314: 'cockroach, roach', 315: 'mantis, mantid', 316: 'cicada, cicala', 317: 'leafhopper', 318: 'lacewing, lacewing fly', 319: "dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk", 320: 'damselfly', 321: 'admiral', 322: 'ringlet, ringlet butterfly', 323: 'monarch, monarch butterfly, milkweed butterfly, Danaus plexippus', 324: 'cabbage butterfly', 325: 'sulphur butterfly, sulfur butterfly', 326: 'lycaenid, lycaenid butterfly', 327: 'starfish, sea star', 328: 'sea urchin', 329: 'sea cucumber, holothurian', 330: 'wood rabbit, cottontail, cottontail rabbit', 331: 'hare', 332: 'Angora, Angora rabbit', 333: 'hamster', 334: 'porcupine, hedgehog', 335: 'fox squirrel, eastern fox squirrel, Sciurus niger', 336: 'marmot', 337: 'beaver', 338: 'guinea pig, Cavia cobaya', 339: 'sorrel', 340: 'zebra', 341: 'hog, pig, grunter, squealer, Sus scrofa', 342: 'wild boar, boar, Sus scrofa', 343: 'warthog', 344: 'hippopotamus, hippo, river horse, Hippopotamus amphibius', 345: 'ox', 346: 'water buffalo, water ox, Asiatic buffalo, Bubalus bubalis', 347: 'bison', 348: 'ram, tup', 349: 'bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis', 350: 'ibex, Capra ibex', 351: 'hartebeest', 352: 'impala, Aepyceros melampus', 353: 'gazelle', 354: 'Arabian camel, dromedary, Camelus dromedarius', 355: 'llama', 356: 'weasel', 357: 'mink', 358: 'polecat, fitch, foulmart, foumart, Mustela putorius', 359: 'black-footed ferret, ferret, Mustela nigripes', 360: 'otter', 361: 'skunk, polecat, wood pussy', 362: 'badger', 363: 'armadillo', 364: 'three-toed sloth, ai, Bradypus tridactylus', 365: 'orangutan, orang, orangutang, Pongo pygmaeus', 366: 'gorilla, Gorilla gorilla', 367: 'chimpanzee, chimp, Pan troglodytes', 368: 'gibbon, Hylobates lar', 369: 'siamang, Hylobates syndactylus, Symphalangus syndactylus', 370: 'guenon, guenon monkey', 371: 'patas, hussar monkey, Erythrocebus patas', 372: 'baboon', 373: 'macaque', 374: 'langur', 375: 'colobus, colobus monkey', 376: 'proboscis monkey, Nasalis larvatus', 377: 'marmoset', 378: 'capuchin, ringtail, Cebus capucinus', 379: 'howler monkey, howler', 380: 'titi, titi monkey', 381: 'spider monkey, Ateles geoffroyi', 382: 'squirrel monkey, Saimiri sciureus', 383: 'Madagascar cat, ring-tailed lemur, Lemur catta', 384: 'indri, indris, Indri indri, Indri brevicaudatus', 385: 'Indian elephant, Elephas maximus', 386: 'African elephant, Loxodonta africana', 387: 'lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens', 388: 'giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca', 389: 'barracouta, snoek', 390: 'eel', 391: 'coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch', 392: 'rock beauty, Holocanthus tricolor', 393: 'anemone fish', 394: 'sturgeon', 395: 'gar, garfish, garpike, billfish, Lepisosteus osseus', 396: 'lionfish', 397: 'puffer, pufferfish, blowfish, globefish', 398: 'abacus', 399: 'abaya', 400: "academic gown, academic robe, judge's robe", 401: 'accordion, piano accordion, squeeze box', 402: 'acoustic guitar', 403: 'aircraft carrier, carrier, flattop, attack aircraft carrier', 404: 'airliner', 405: 'airship, dirigible', 406: 'altar', 407: 'ambulance', 408: 'amphibian, amphibious vehicle', 409: 'analog clock', 410: 'apiary, bee house', 411: 'apron', 412: 'ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin', 413: 'assault rifle, assault gun', 414: 'backpack, back pack, knapsack, packsack, rucksack, haversack', 415: 'bakery, bakeshop, bakehouse', 416: 'balance beam, beam', 417: 'balloon', 418: 'ballpoint, ballpoint pen, ballpen, Biro', 419: 'Band Aid', 420: 'banjo', 421: 'bannister, banister, balustrade, balusters, handrail', 422: 'barbell', 423: 'barber chair', 424: 'barbershop', 425: 'barn', 426: 'barometer', 427: 'barrel, cask', 428: 'barrow, garden cart, lawn cart, wheelbarrow', 429: 'baseball', 430: 'basketball', 431: 'bassinet', 432: 'bassoon', 433: 'bathing cap, swimming cap', 434: 'bath towel', 435: 'bathtub, bathing tub, bath, tub', 436: 'beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon', 437: 'beacon, lighthouse, beacon light, pharos', 438: 'beaker', 439: 'bearskin, busby, shako', 440: 'beer bottle', 441: 'beer glass', 442: 'bell cote, bell cot', 443: 'bib', 444: 'bicycle-built-for-two, tandem bicycle, tandem', 445: 'bikini, two-piece', 446: 'binder, ring-binder', 447: 'binoculars, field glasses, opera glasses', 448: 'birdhouse', 449: 'boathouse', 450: 'bobsled, bobsleigh, bob', 451: 'bolo tie, bolo, bola tie, bola', 452: 'bonnet, poke bonnet', 453: 'bookcase', 454: 'bookshop, bookstore, bookstall', 455: 'bottlecap', 456: 'bow', 457: 'bow tie, bow-tie, bowtie', 458: 'brass, memorial tablet, plaque', 459: 'brassiere, bra, bandeau', 460: 'breakwater, groin, groyne, mole, bulwark, seawall, jetty', 461: 'breastplate, aegis, egis', 462: 'broom', 463: 'bucket, pail', 464: 'buckle', 465: 'bulletproof vest', 466: 'bullet train, bullet', 467: 'butcher shop, meat market', 468: 'cab, hack, taxi, taxicab', 469: 'caldron, cauldron', 470: 'candle, taper, wax light', 471: 'cannon', 472: 'canoe', 473: 'can opener, tin opener', 474: 'cardigan', 475: 'car mirror', 476: 'carousel, carrousel, merry-go-round, roundabout, whirligig', 477: "carpenter's kit, tool kit", 478: 'carton', 479: 'car wheel', 480: 'cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM', 481: 'cassette', 482: 'cassette player', 483: 'castle', 484: 'catamaran', 485: 'CD player', 486: 'cello, violoncello', 487: 'cellular telephone, cellular phone, cellphone, cell, mobile phone', 488: 'chain', 489: 'chainlink fence', 490: 'chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour', 491: 'chain saw, chainsaw', 492: 'chest', 493: 'chiffonier, commode', 494: 'chime, bell, gong', 495: 'china cabinet, china closet', 496: 'Christmas stocking', 497: 'church, church building', 498: 'cinema, movie theater, movie theatre, movie house, picture palace', 499: 'cleaver, meat cleaver, chopper', 500: 'cliff dwelling', 501: 'cloak', 502: 'clog, geta, patten, sabot', 503: 'cocktail shaker', 504: 'coffee mug', 505: 'coffeepot', 506: 'coil, spiral, volute, whorl, helix', 507: 'combination lock', 508: 'computer keyboard, keypad', 509: 'confectionery, confectionary, candy store', 510: 'container ship, containership, container vessel', 511: 'convertible', 512: 'corkscrew, bottle screw', 513: 'cornet, horn, trumpet, trump', 514: 'cowboy boot', 515: 'cowboy hat, ten-gallon hat', 516: 'cradle', 517: 'crane', 518: 'crash helmet', 519: 'crate', 520: 'crib, cot', 521: 'Crock Pot', 522: 'croquet ball', 523: 'crutch', 524: 'cuirass', 525: 'dam, dike, dyke', 526: 'desk', 527: 'desktop computer', 528: 'dial telephone, dial phone', 529: 'diaper, nappy, napkin', 530: 'digital clock', 531: 'digital watch', 532: 'dining table, board', 533: 'dishrag, dishcloth', 534: 'dishwasher, dish washer, dishwashing machine', 535: 'disk brake, disc brake', 536: 'dock, dockage, docking facility', 537: 'dogsled, dog sled, dog sleigh', 538: 'dome', 539: 'doormat, welcome mat', 540: 'drilling platform, offshore rig', 541: 'drum, membranophone, tympan', 542: 'drumstick', 543: 'dumbbell', 544: 'Dutch oven', 545: 'electric fan, blower', 546: 'electric guitar', 547: 'electric locomotive', 548: 'entertainment center', 549: 'envelope', 550: 'espresso maker', 551: 'face powder', 552: 'feather boa, boa', 553: 'file, file cabinet, filing cabinet', 554: 'fireboat', 555: 'fire engine, fire truck', 556: 'fire screen, fireguard', 557: 'flagpole, flagstaff', 558: 'flute, transverse flute', 559: 'folding chair', 560: 'football helmet', 561: 'forklift', 562: 'fountain', 563: 'fountain pen', 564: 'four-poster', 565: 'freight car', 566: 'French horn, horn', 567: 'frying pan, frypan, skillet', 568: 'fur coat', 569: 'garbage truck, dustcart', 570: 'gasmask, respirator, gas helmet', 571: 'gas pump, gasoline pump, petrol pump, island dispenser', 572: 'goblet', 573: 'go-kart', 574: 'golf ball', 575: 'golfcart, golf cart', 576: 'gondola', 577: 'gong, tam-tam', 578: 'gown', 579: 'grand piano, grand', 580: 'greenhouse, nursery, glasshouse', 581: 'grille, radiator grille', 582: 'grocery store, grocery, food market, market', 583: 'guillotine', 584: 'hair slide', 585: 'hair spray', 586: 'half track', 587: 'hammer', 588: 'hamper', 589: 'hand blower, blow dryer, blow drier, hair dryer, hair drier', 590: 'hand-held computer, hand-held microcomputer', 591: 'handkerchief, hankie, hanky, hankey', 592: 'hard disc, hard disk, fixed disk', 593: 'harmonica, mouth organ, harp, mouth harp', 594: 'harp', 595: 'harvester, reaper', 596: 'hatchet', 597: 'holster', 598: 'home theater, home theatre', 599: 'honeycomb', 600: 'hook, claw', 601: 'hoopskirt, crinoline', 602: 'horizontal bar, high bar', 603: 'horse cart, horse-cart', 604: 'hourglass', 605: 'iPod', 606: 'iron, smoothing iron', 607: "jack-o'-lantern", 608: 'jean, blue jean, denim', 609: 'jeep, landrover', 610: 'jersey, T-shirt, tee shirt', 611: 'jigsaw puzzle', 612: 'jinrikisha, ricksha, rickshaw', 613: 'joystick', 614: 'kimono', 615: 'knee pad', 616: 'knot', 617: 'lab coat, laboratory coat', 618: 'ladle', 619: 'lampshade, lamp shade', 620: 'laptop, laptop computer', 621: 'lawn mower, mower', 622: 'lens cap, lens cover', 623: 'letter opener, paper knife, paperknife', 624: 'library', 625: 'lifeboat', 626: 'lighter, light, igniter, ignitor', 627: 'limousine, limo', 628: 'liner, ocean liner', 629: 'lipstick, lip rouge', 630: 'Loafer', 631: 'lotion', 632: 'loudspeaker, speaker, speaker unit, loudspeaker system, speaker system', 633: "loupe, jeweler's loupe", 634: 'lumbermill, sawmill', 635: 'magnetic compass', 636: 'mailbag, postbag', 637: 'mailbox, letter box', 638: 'maillot', 639: 'maillot, tank suit', 640: 'manhole cover', 641: 'maraca', 642: 'marimba, xylophone', 643: 'mask', 644: 'matchstick', 645: 'maypole', 646: 'maze, labyrinth', 647: 'measuring cup', 648: 'medicine chest, medicine cabinet', 649: 'megalith, megalithic structure', 650: 'microphone, mike', 651: 'microwave, microwave oven', 652: 'military uniform', 653: 'milk can', 654: 'minibus', 655: 'miniskirt, mini', 656: 'minivan', 657: 'missile', 658: 'mitten', 659: 'mixing bowl', 660: 'mobile home, manufactured home', 661: 'Model T', 662: 'modem', 663: 'monastery', 664: 'monitor', 665: 'moped', 666: 'mortar', 667: 'mortarboard', 668: 'mosque', 669: 'mosquito net', 670: 'motor scooter, scooter', 671: 'mountain bike, all-terrain bike, off-roader', 672: 'mountain tent', 673: 'mouse, computer mouse', 674: 'mousetrap', 675: 'moving van', 676: 'muzzle', 677: 'nail', 678: 'neck brace', 679: 'necklace', 680: 'nipple', 681: 'notebook, notebook computer', 682: 'obelisk', 683: 'oboe, hautboy, hautbois', 684: 'ocarina, sweet potato', 685: 'odometer, hodometer, mileometer, milometer', 686: 'oil filter', 687: 'organ, pipe organ', 688: 'oscilloscope, scope, cathode-ray oscilloscope, CRO', 689: 'overskirt', 690: 'oxcart', 691: 'oxygen mask', 692: 'packet', 693: 'paddle, boat paddle', 694: 'paddlewheel, paddle wheel', 695: 'padlock', 696: 'paintbrush', 697: "pajama, pyjama, pj's, jammies", 698: 'palace', 699: 'panpipe, pandean pipe, syrinx', 700: 'paper towel', 701: 'parachute, chute', 702: 'parallel bars, bars', 703: 'park bench', 704: 'parking meter', 705: 'passenger car, coach, carriage', 706: 'patio, terrace', 707: 'pay-phone, pay-station', 708: 'pedestal, plinth, footstall', 709: 'pencil box, pencil case', 710: 'pencil sharpener', 711: 'perfume, essence', 712: 'Petri dish', 713: 'photocopier', 714: 'pick, plectrum, plectron', 715: 'pickelhaube', 716: 'picket fence, paling', 717: 'pickup, pickup truck', 718: 'pier', 719: 'piggy bank, penny bank', 720: 'pill bottle', 721: 'pillow', 722: 'ping-pong ball', 723: 'pinwheel', 724: 'pirate, pirate ship', 725: 'pitcher, ewer', 726: "plane, carpenter's plane, woodworking plane", 727: 'planetarium', 728: 'plastic bag', 729: 'plate rack', 730: 'plow, plough', 731: "plunger, plumber's helper", 732: 'Polaroid camera, Polaroid Land camera', 733: 'pole', 734: 'police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria', 735: 'poncho', 736: 'pool table, billiard table, snooker table', 737: 'pop bottle, soda bottle', 738: 'pot, flowerpot', 739: "potter's wheel", 740: 'power drill', 741: 'prayer rug, prayer mat', 742: 'printer', 743: 'prison, prison house', 744: 'projectile, missile', 745: 'projector', 746: 'puck, hockey puck', 747: 'punching bag, punch bag, punching ball, punchball', 748: 'purse', 749: 'quill, quill pen', 750: 'quilt, comforter, comfort, puff', 751: 'racer, race car, racing car', 752: 'racket, racquet', 753: 'radiator', 754: 'radio, wireless', 755: 'radio telescope, radio reflector', 756: 'rain barrel', 757: 'recreational vehicle, RV, R.V.', 758: 'reel', 759: 'reflex camera', 760: 'refrigerator, icebox', 761: 'remote control, remote', 762: 'restaurant, eating house, eating place, eatery', 763: 'revolver, six-gun, six-shooter', 764: 'rifle', 765: 'rocking chair, rocker', 766: 'rotisserie', 767: 'rubber eraser, rubber, pencil eraser', 768: 'rugby ball', 769: 'rule, ruler', 770: 'running shoe', 771: 'safe', 772: 'safety pin', 773: 'saltshaker, salt shaker', 774: 'sandal', 775: 'sarong', 776: 'sax, saxophone', 777: 'scabbard', 778: 'scale, weighing machine', 779: 'school bus', 780: 'schooner', 781: 'scoreboard', 782: 'screen, CRT screen', 783: 'screw', 784: 'screwdriver', 785: 'seat belt, seatbelt', 786: 'sewing machine', 787: 'shield, buckler', 788: 'shoe shop, shoe-shop, shoe store', 789: 'shoji', 790: 'shopping basket', 791: 'shopping cart', 792: 'shovel', 793: 'shower cap', 794: 'shower curtain', 795: 'ski', 796: 'ski mask', 797: 'sleeping bag', 798: 'slide rule, slipstick', 799: 'sliding door', 800: 'slot, one-armed bandit', 801: 'snorkel', 802: 'snowmobile', 803: 'snowplow, snowplough', 804: 'soap dispenser', 805: 'soccer ball', 806: 'sock', 807: 'solar dish, solar collector, solar furnace', 808: 'sombrero', 809: 'soup bowl', 810: 'space bar', 811: 'space heater', 812: 'space shuttle', 813: 'spatula', 814: 'speedboat', 815: "spider web, spider's web", 816: 'spindle', 817: 'sports car, sport car', 818: 'spotlight, spot', 819: 'stage', 820: 'steam locomotive', 821: 'steel arch bridge', 822: 'steel drum', 823: 'stethoscope', 824: 'stole', 825: 'stone wall', 826: 'stopwatch, stop watch', 827: 'stove', 828: 'strainer', 829: 'streetcar, tram, tramcar, trolley, trolley car', 830: 'stretcher', 831: 'studio couch, day bed', 832: 'stupa, tope', 833: 'submarine, pigboat, sub, U-boat', 834: 'suit, suit of clothes', 835: 'sundial', 836: 'sunglass', 837: 'sunglasses, dark glasses, shades', 838: 'sunscreen, sunblock, sun blocker', 839: 'suspension bridge', 840: 'swab, swob, mop', 841: 'sweatshirt', 842: 'swimming trunks, bathing trunks', 843: 'swing', 844: 'switch, electric switch, electrical switch', 845: 'syringe', 846: 'table lamp', 847: 'tank, army tank, armored combat vehicle, armoured combat vehicle', 848: 'tape player', 849: 'teapot', 850: 'teddy, teddy bear', 851: 'television, television system', 852: 'tennis ball', 853: 'thatch, thatched roof', 854: 'theater curtain, theatre curtain', 855: 'thimble', 856: 'thresher, thrasher, threshing machine', 857: 'throne', 858: 'tile roof', 859: 'toaster', 860: 'tobacco shop, tobacconist shop, tobacconist', 861: 'toilet seat', 862: 'torch', 863: 'totem pole', 864: 'tow truck, tow car, wrecker', 865: 'toyshop', 866: 'tractor', 867: 'trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi', 868: 'tray', 869: 'trench coat', 870: 'tricycle, trike, velocipede', 871: 'trimaran', 872: 'tripod', 873: 'triumphal arch', 874: 'trolleybus, trolley coach, trackless trolley', 875: 'trombone', 876: 'tub, vat', 877: 'turnstile', 878: 'typewriter keyboard', 879: 'umbrella', 880: 'unicycle, monocycle', 881: 'upright, upright piano', 882: 'vacuum, vacuum cleaner', 883: 'vase', 884: 'vault', 885: 'velvet', 886: 'vending machine', 887: 'vestment', 888: 'viaduct', 889: 'violin, fiddle', 890: 'volleyball', 891: 'waffle iron', 892: 'wall clock', 893: 'wallet, billfold, notecase, pocketbook', 894: 'wardrobe, closet, press', 895: 'warplane, military plane', 896: 'washbasin, handbasin, washbowl, lavabo, wash-hand basin', 897: 'washer, automatic washer, washing machine', 898: 'water bottle', 899: 'water jug', 900: 'water tower', 901: 'whiskey jug', 902: 'whistle', 903: 'wig', 904: 'window screen', 905: 'window shade', 906: 'Windsor tie', 907: 'wine bottle', 908: 'wing', 909: 'wok', 910: 'wooden spoon', 911: 'wool, woolen, woollen', 912: 'worm fence, snake fence, snake-rail fence, Virginia fence', 913: 'wreck', 914: 'yawl', 915: 'yurt', 916: 'web site, website, internet site, site', 917: 'comic book', 918: 'crossword puzzle, crossword', 919: 'street sign', 920: 'traffic light, traffic signal, stoplight', 921: 'book jacket, dust cover, dust jacket, dust wrapper', 922: 'menu', 923: 'plate', 924: 'guacamole', 925: 'consomme', 926: 'hot pot, hotpot', 927: 'trifle', 928: 'ice cream, icecream', 929: 'ice lolly, lolly, lollipop, popsicle', 930: 'French loaf', 931: 'bagel, beigel', 932: 'pretzel', 933: 'cheeseburger', 934: 'hotdog, hot dog, red hot', 935: 'mashed potato', 936: 'head cabbage', 937: 'broccoli', 938: 'cauliflower', 939: 'zucchini, courgette', 940: 'spaghetti squash', 941: 'acorn squash', 942: 'butternut squash', 943: 'cucumber, cuke', 944: 'artichoke, globe artichoke', 945: 'bell pepper', 946: 'cardoon', 947: 'mushroom', 948: 'Granny Smith', 949: 'strawberry', 950: 'orange', 951: 'lemon', 952: 'fig', 953: 'pineapple, ananas', 954: 'banana', 955: 'jackfruit, jak, jack', 956: 'custard apple', 957: 'pomegranate', 958: 'hay', 959: 'carbonara', 960: 'chocolate sauce, chocolate syrup', 961: 'dough', 962: 'meat loaf, meatloaf', 963: 'pizza, pizza pie', 964: 'potpie', 965: 'burrito', 966: 'red wine', 967: 'espresso', 968: 'cup', 969: 'eggnog', 970: 'alp', 971: 'bubble', 972: 'cliff, drop, drop-off', 973: 'coral reef', 974: 'geyser', 975: 'lakeside, lakeshore', 976: 'promontory, headland, head, foreland', 977: 'sandbar, sand bar', 978: 'seashore, coast, seacoast, sea-coast', 979: 'valley, vale', 980: 'volcano', 981: 'ballplayer, baseball player', 982: 'groom, bridegroom', 983: 'scuba diver', 984: 'rapeseed', 985: 'daisy', 986: "yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum", 987: 'corn', 988: 'acorn', 989: 'hip, rose hip, rosehip', 990: 'buckeye, horse chestnut, conker', 991: 'coral fungus', 992: 'agaric', 993: 'gyromitra', 994: 'stinkhorn, carrion fungus', 995: 'earthstar', 996: 'hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa', 997: 'bolete', 998: 'ear, spike, capitulum', 999: 'toilet tissue, toilet paper, bathroom tissue'} + # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" pipeline = Pipeline.create( - task="image_classification", - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - class_names = classes, - top_k = 3 + task="image_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + class_names=classes, ) # run inference on image file prediction = pipeline(images="lion.jpeg") -print(prediction) -# labels=['lion, king of beasts, Panthera leo', 'chow, chow chow', 'Tibetan mastiff'] scores=[24.792997360229492, 19.385034561157227, 16.349166870117188] +print(prediction.labels) +# labels=['lion, king of beasts, Panthera leo'] ``` ### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +Check out the Pipeline User Guide for more details on configuring a Pipeline. ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. @@ -150,7 +192,9 @@ Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to The CLI command below launches an image classification pipeline with a 95% pruned ResNet model: ```bash -deepsparse.server --task image_classification --model_path "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none" --port 5543 +deepsparse.server \ + --task image_classification \ + --model_path zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none ``` You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. @@ -167,7 +211,7 @@ print(resp.text) # {"labels":[291],"scores":[24.185693740844727]} ``` #### Use Case Specific Arguments -To use the `top_k` argument, create a Server configuration file for passing the argument via kwargs. +To use the `top_k` argument, create a server configuration file for passing the argument via kwargs. This configuration file sets `top_k` classes to 3: ```yaml @@ -178,11 +222,14 @@ endpoints: kwargs: top_k: 3 ``` + Start the server: ```bash deepsparse.server --config-file image_classification-config.yaml ``` -Run inference: + +Make a request over HTTP: + ```python import requests diff --git a/use-cases/cv/image-segmentation-yolact.md b/use-cases/cv/image-segmentation-yolact.md index c2d5ed130a..4cac0f0155 100644 --- a/use-cases/cv/image-segmentation-yolact.md +++ b/use-cases/cv/image-segmentation-yolact.md @@ -11,15 +11,17 @@ and post-processing steps, allowing you to make requests on raw data and receive - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. +We will walk through an example of each. + ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server and YOLO](https://docs.neuralmagic.com/get-started/install/deepsparse). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. The numbers below were run on a 4 core `c6i.2xlarge` instance in AWS. ### ONNX Runtime Baseline @@ -34,58 +36,62 @@ deepsparse.benchmark \ > Original Model Path: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 8.7788 -> Latency Mean (ms/batch): 7290.2676 -> Latency Median (ms/batch): 7290.2676 -> Latency Std (ms/batch): 418.0256 -> Iterations: 2 +> Throughput (items/sec): 3.5290 ``` -ONNX Runtime achieves 9 items/second with batch 64. + +ONNX Runtime achieves 3.5 items/second with batch 64. + ### DeepSparse Speedup -Now, let's run DeepSparse on an inference-optimized sparse version of YOLACT. This model has been 90% pruned, while retaining >99% accuracy of the dense baseline on the `coco` dataset. +Now, let's run DeepSparse on an inference-optimized sparse version of YOLACT. This model has been 82.5% pruned and quantized to INT8, while retaining >99% accuracy of the dense baseline on the `coco` dataset. + ```bash deepsparse.benchmark \ - zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned90-none \ + zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none \ -b 64 -s sync -nstreams 1 \ -e deepsparse -> Original Model Path: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned90-none +> Original Model Path: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 27.7439 -> Latency Mean (ms/batch): 2306.7927 -> Latency Median (ms/batch): 2297.4245 -> Latency Std (ms/batch): 16.7005 -> Iterations: 5 +> Throughput (items/sec): 23.2061 ``` -DeepSparse achieves 28 items/second, a 3x speed-up over ONNX Runtime! + +DeepSparse achieves 23 items/second, a 6.6x speed-up over ONNX Runtime! + ## DeepSparse Engine Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. With Engine, we can compile an ONNX file and run inference on raw tensors. -Here's an example, using a 90% pruned YOLACT model trained on `coco` from SparseZoo: +Here's an example, using a 82.5% pruned-quantized YOLACT model from SparseZoo: + ```python from deepsparse import Engine from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 -sparsezoo_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned90-none" +sparsezoo_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" batch_size = 1 -bert_engine = Engine( +compiled_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) -output = bert_engine(inputs) +output = compiled_model(inputs) + +print(output[0].shape) print(output) + +# (1, 19248, 4) + # [array([[[ 0.444973 , -0.02015 , -1.3631972 , -0.9219434 ], # ... # 9.50585604e-02, 4.13608968e-01, 1.57236055e-01]]]], dtype=float32)] ``` + ## DeepSparse Pipelines Pipeline is the default interface for interacting with DeepSparse. @@ -101,18 +107,18 @@ We will use the `Pipeline.create()` constructor to create an instance of an imag from deepsparse.pipeline import Pipeline model_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" -images = ["thailand.jpeg"] - yolact_pipeline = Pipeline.create( task="yolact", model_path=model_stub, ) -predictions = yolact_pipeline(images=images, confidence_threshold=0.2,nms_threshold = 0.5) +images = ["thailand.jpeg"] +predictions = yolact_pipeline(images=images) # predictions has attributes `boxes`, `classes`, `masks` and `scores` predictions.classes[0] # [20,......, 5] ``` + ### Use Case Specific Arguments The Image Segmentation Pipeline contains additional arguments for configuring a `Pipeline`. @@ -131,15 +137,16 @@ yolact_pipeline = Pipeline.create( class_names="coco", ) -predictions = yolact_pipeline(images=images, confidence_threshold=0.2,nms_threshold = 0.5) +predictions = yolact_pipeline(images=images, confidence_threshold=0.2, nms_threshold=0.5) # predictions has attributes `boxes`, `classes`, `masks` and `scores` predictions.classes[0] ['elephant','elephant','person',...'zebra','stop sign','bus'] ``` + ### Annotate CLI You can also use the annotate command to have the engine save an annotated photo on disk. ```bash -deepsparse.instance_segmentation.annotate --source thailand.jpg #Try --source 0 to annotate your live webcam feed +deepsparse.instance_segmentation.annotate --source thailand.jpeg #Try --source 0 to annotate your live webcam feed ``` Running the above command will create an `annotation-results` folder and save the annotated image inside. @@ -157,7 +164,7 @@ The CLI command below launches an image segmentation pipeline with a 82% pruned- ```bash deepsparse.server \ - task yolact \ + --task yolact \ --model_path "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" --port 5543 ``` Run inference: @@ -166,7 +173,7 @@ import requests import json url = 'http://0.0.0.0:5543/predict/from_files' -path = ['thailand.jpg'] # list of images for inference +path = ['thailand.jpeg'] # list of images for inference files = [('request', open(img, 'rb')) for img in path] resp = requests.post(url=url, files=files) annotations = json.loads(resp.text) # dictionary of annotation results @@ -195,7 +202,7 @@ import requests import json url = 'http://0.0.0.0:5543/predict/from_files' -path = ['pets.jpg'] # list of images for inference +path = ['thailand.jpeg'] # list of images for inference files = [('request', open(img, 'rb')) for img in path] resp = requests.post(url=url, files=files) annotations = json.loads(resp.text) # dictionary of annotation results @@ -203,4 +210,4 @@ boxes, classes, masks, scores = annotations["boxes"], annotations["classes"], an ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. +Check out the Server User Guide for more details on configuring the Server. diff --git a/use-cases/cv/object-detection-yolov5.md b/use-cases/cv/object-detection-yolov5.md new file mode 100644 index 0000000000..0b00eae5b5 --- /dev/null +++ b/use-cases/cv/object-detection-yolov5.md @@ -0,0 +1,265 @@ +# Deploying YOLOv5 Object Detection Models with DeepSparse + +This page explains how to benchmark and deploy a YOLOv5 object detection model with DeepSparse. + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. + +We will walk through an example of each. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server and YOLO](https://docs.neuralmagic.com/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). + +## Benchmarking + +We can use the benchmarking utility to demonstrate the DeepSparse's performance. The numbers below were run on a 4 core `c6i.2xlarge` instance in AWS. + +### ONNX Runtime Baseline + +As a baseline, let's check out ONNX Runtime's performance on YOLOv5s. Make sure you have ORT installed (`pip install onnxruntime`). + +```bash +deepsparse.benchmark \ + zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none \ + -b 64 -s sync -nstreams 1 \ + -e onnxruntime + +> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 13.1266 +``` +ONNX Runtime achieves 13 items/second with batch 64. + +### DeepSparse Speedup +Now, let's run DeepSparse on an inference-optimized sparse version of YOLOv5 . This model has been 94% pruned-quantized, while retaining >99% accuracy of the dense baseline on the `coco` dataset. +```bash +deepsparse.benchmark \ + zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none \ + -b 64 -s sync -nstreams 1 + +> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 72.55 +``` +DeepSparse achieves 73 items/second, a 5.5x speed-up over ONNX Runtime! + +## DeepSparse Engine +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. + +With Engine, we can compile an ONNX file and run inference on raw tensors. + +Here's an example, using a 85% pruned-quantized YOLOv5 from SparseZoo: + +```python +from deepsparse import Engine +from deepsparse.utils import generate_random_inputs, model_to_path +import numpy as np + +# download onnx from sparsezoo and compile with batchsize 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none" +batch_size = 1 +compiled_model = Engine( + model=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=batch_size # defaults to batch size 1 +) +# input is raw numpy tensors, output is raw scores for classes +inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) +output = compiled_model(inputs) + +print(output[0].shape) +print(output[0]) + +# (1,25200, 85) +# [array([[[5.54789925e+00, 4.28643513e+00, 9.98156166e+00, ..., +# ... +# -6.13238716e+00, -6.80812788e+00, -5.50403357e+00]]]]], dtype=float32)] +``` + +## DeepSparse Pipeline +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +Let's start by downloading a sample image: +```bash +wget -O basilica.jpg https://raw.githubusercontent.com/neuralmagic/deepsparse/main/src/deepsparse/yolo/sample_images/basilica.jpg +``` +We will use the `Pipeline.create()` constructor to create an instance of an object detection Pipeline with a 96% pruned version of YOLOv5 trained on `coco`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images) is handled by the `Pipeline`. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none" +yolo_pipeline = Pipeline.create( + task="yolo", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX +) +images = ["basilica.jpg"] + +# run inference on image file +pipeline_outputs = yolo_pipeline(images=images) +print(pipeline_outputs.boxes) +print(pipeline_outputs.labels) + +# [[[262.56866455078125, 483.48693108558655, 514.8401184082031, 611.7606239318848], [542.7222747802734, 385.72591066360474, 591.0432586669922, 412.0340189933777], [728.4929351806641, 403.6355793476105, 769.6295471191406, 493.7961976528168], [466.83229064941406, 383.6878204345703, 530.7117462158203, 408.8705735206604], [309.2399597167969, 396.0068359375, 362.10223388671875, 435.58393812179565], [56.86535453796387, 409.39830899238586, 99.50672149658203, 497.8857614994049], [318.8877868652344, 388.9980583190918, 449.08460998535156, 587.5987024307251], [793.9356079101562, 390.5112290382385, 861.0441284179688, 489.4586777687073], [449.93934631347656, 441.90707445144653, 574.4951934814453, 539.5000758171082], [99.09783554077148, 381.93165946006775, 135.13665390014648, 458.19711089134216], [154.37461853027344, 386.8395175933838, 188.95138549804688, 469.1738815307617], [14.558289527893066, 396.7127945423126, 54.229820251464844, 487.2396695613861], [704.1891632080078, 398.2202727794647, 739.6305999755859, 471.5654203891754], [731.9091796875, 380.60836935043335, 761.627197265625, 414.56129932403564]]] << list of bounding boxes >> + +# [['3.0', '2.0', '0.0', '2.0', '2.0', '0.0', '0.0', '0.0', '3.0', '0.0', '0.0', '0.0', '0.0', '0.0']] << list of label ids >> +``` + +### Use Case Specific Arguments +The Object Detection Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Image Shape + +DeepSparse runs with static shapes. By default, YOLOv5 inferences run with images of shape 640x640. The Pipeline accepts images of any size and scales the images to image shape specified by the ONNX graph. + +We can override the image shape used by DeepSparse with the `image_size` argument. In the example below, we run the inferences at 320x320. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none" +yolo_pipeline = Pipeline.create( + task="yolo", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + image_size=(320,320) +) +images = ["basilica.jpg"] + +# run inference on image file +pipeline_outputs = yolo_pipeline(images=images) +print(pipeline_outputs.boxes) +print(pipeline_outputs.labels) +``` + +#### Class Names +We can specify class names for the labels by passing a dictionary. In the example below, we just use +the first 4 classes from COCO for the sake of a quick example. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none" +yolo_pipeline = Pipeline.create( + task="yolo", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + class_names={"0":"person", "1":"bicycle", "2":"car", "3":"motorcycle"} + +) +images = ["basilica.jpg"] + +# run inference on image file +pipeline_outputs = yolo_pipeline(images=images) +print(pipeline_outputs.labels) +# [['motorcycle', 'car', 'person', 'car', 'car', 'person', 'person', 'person', 'motorcycle', 'person', 'person', 'person', 'person', 'person']] +``` + +#### IOU and Conf Threshold +We can also configure the thresholds for making detections in YOLO. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none" +yolo_pipeline = Pipeline.create( + task="yolo", + model_path=sparsezoo_stub +) + +images = ["basilica.jpg"] + +# low threshold inference +pipeline_outputs_low_conf = yolo_pipeline(images=images, iou_thres=0.3, conf_thres=0.1) +print(len(pipeline_outputs_low_conf.boxes[0])) +# 37 <> + +# high threshold inference +pipeline_outputs_high_conf = yolo_pipeline(images=images, iou_thres=0.5, conf_thres=0.8) +print(len(pipeline_outputs_high_conf.boxes[0])) +# 1 << only one prediction>> +``` + +## DeepSparse Server + +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. + +Spin up the server: +```bash +deepsparse.server \ + --task yolo \ + --model_path zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none +``` + +Making a request. +```python +import requests +import json + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['basilica.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) # dictionary of annotation results +bounding_boxes = annotations["boxes"] +labels = annotations["labels"] +print(labels) + +# [['3.0', '2.0', '2.0', '0.0', '0.0', '2.0', '0.0', '0.0', '0.0', '3.0', '0.0', '0.0', '0.0', '0.0', '3.0', '9.0', '0.0', '2.0', '0.0', '0.0']] +``` + +#### Use Case Specific Arguments + +To use the `image_size` or `class_names` argument, create a Server configuration file for passing the arguments via kwargs. + +This configuration file sets `class_names` to `coco`: + +```yaml +# yolo-config.yaml +endpoints: + - task: yolo + model: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none + kwargs: + class_names: + '0': person + '1': bicycle + '2': car + '3': motorcycle + image_size: 320 +``` + +Start the server: +```bash +deepsparse.server --config-file yolo-config.yaml +``` + +Making a request: +```python +import requests, json + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['basilica.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) +bounding_boxes = annotations["boxes"] +labels = annotations["labels"] +print(labels) +# [['person', 'person', 'car', 'person', 'motorcycle', 'person', 'person', 'person', 'motorcycle', 'person']] +``` + +### Cross Use Case Functionality + +Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file diff --git a/use-cases/cv/yolov5-object-detection.md b/use-cases/cv/yolov5-object-detection.md deleted file mode 100644 index df606156b9..0000000000 --- a/use-cases/cv/yolov5-object-detection.md +++ /dev/null @@ -1,207 +0,0 @@ -# Deploying YOLOv5 Object Detection Models with DeepSparse - -This page explains how to benchmark and deploy a YOLOv5 object detection model with DeepSparse. - -There are three interfaces for interacting with DeepSparse: -- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. - -- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing -and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. - -- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving -endpoint running DeepSparse with a single CLI. - -## Installation Requirements - -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). - -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). - -## Benchmarking - -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. - -### ONNX Runtime Baseline - -As a baseline, let's check out ONNX Runtime's performance on YOLOv5. Make sure you have ORT installed (`pip install onnxruntime`). -```bash -deepsparse.benchmark \ - zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 \ - -b 64 -s sync -nstreams 1 \ - -e onnxruntime -> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 -> Batch Size: 64 -> Scenario: sync -> Throughput (items/sec): 15.1734 -> Latency Mean (ms/batch): 4217.1713 -> Latency Median (ms/batch): 4088.7618 -> Latency Std (ms/batch): 274.9809 -> Iterations: 3 -``` -ONNX Runtime achieves 15 items/second with batch 64. -### DeepSparse Speedup -Now, let's run DeepSparse on an inference-optimized sparse version of YOLOv5 . This model has been 94% pruned-quantized, while retaining >99% accuracy of the dense baseline on the `coco` dataset. -```bash -deepsparse.benchmark \ - zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 \ - -b 64 -s sync -nstreams 1 \ - -e deepsparse -> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 -> Batch Size: 64 -> Scenario: sync -> Throughput (items/sec): 237.4027 -> Latency Mean (ms/batch): 269.5658 -> Latency Median (ms/batch): 268.4632 -> Latency Std (ms/batch): 3.4354 -> Iterations: 38 -``` -DeepSparse achieves 237 items/second, a 16x speed-up over ONNX Runtime! -## DeepSparse Engine -Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. - -With Engine, we can compile an ONNX file and run inference on raw tensors. - -Here's an example, using a 98% pruned-quantized YOLOv5 trained on `coco` from SparseZoo: -```python -from deepsparse import Engine -from deepsparse.utils import generate_random_inputs, model_to_path -import numpy as np - -# download onnx from sparsezoo and compile with batchsize 1 -sparsezoo_stub = "zoo:cv/detection/yolov5-l/pytorch/ultralytics/coco/pruned-aggressive_98" -batch_size = 1 -bert_engine = Engine( - model=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size=batch_size # defaults to batch size 1 -) -# input is raw numpy tensors, output is raw scores for classes -inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) -output = bert_engine(inputs) -print(output) -# [array([[[5.54789925e+00, 4.28643513e+00, 9.98156166e+00, ..., -# ... -# -6.13238716e+00, -6.80812788e+00, -5.50403357e+00]]]]], dtype=float32)] -``` -Pipeline is the default interface for interacting with DeepSparse. - -Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. - -Let's start by downloading a sample image: -```bash -wget https://huggingface.co/spaces/neuralmagic/cv-yolo/resolve/main/Fruits.png -``` -We will use the `Pipeline.create()` constructor to create an instance of an object detection Pipeline with a 96% pruned version of YOLOv5 trained on `coco`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images) is handled by the `Pipeline`. - -```python -from deepsparse import Pipeline - -# download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned-aggressive_96" -yolo_pipeline = Pipeline.create( - task="yolo", - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX -) -images = ["Fruits.png"] - -# run inference on image file -pipeline_outputs = yolo_pipeline(images=images, conf_thres=0.001) -print(len(pipeline_outputs.boxes[0])) -print(len(pipeline_outputs.scores[0])) -print(len(pipeline_outputs.labels[0])) -# 135 -# 135 -# 135 -``` -### Use Case Specific Arguments -The Object Detection Pipeline contains additional arguments for configuring a `Pipeline`. - -#### IOU Threshold -In the example below, we define a `iou_thres` of 0.6. You can adjust this depending on your use case. - -```python -from deepsparse import Pipeline - -# download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned-aggressive_96" -yolo_pipeline = Pipeline.create( - task="yolo", - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size = 3 -) -images = ["Fruits.png"] * 3 - -# run inference on image file -pipeline_outputs = yolo_pipeline(images=images, iou_thres=0.6, conf_thres=0.001) -print(len(pipeline_outputs.boxes[0])) -print(len(pipeline_outputs.scores[0])) -print(len(pipeline_outputs.labels[0])) -# 300 -# 300 -# 300 -``` -### Annotate CLI -You can also use the annotate command to have the engine save an annotated photo on disk. -```bash -deepsparse.object_detection.annotate --model_filepath zoo:cv/detection/yolov5-x6/pytorch/ultralytics/coco/pruned75-none --source thailand.jpg -``` -Running the above command will create an `annotation-results` folder and save the annotated image inside. - -![Annotation Results](images/result.jpg) -### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. -## DeepSparse Server -Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. - -The CLI command below launches an object detection pipeline with a 94% pruned-quantized YOLOv5 model: - -```bash -deepsparse.server task yolo --model_path "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94" --port 5543 -``` -```python -import requests -import json - -url = 'http://0.0.0.0:5543/predict/from_files' -path = ['pets.jpg'] # list of images for inference -files = [('request', open(img, 'rb')) for img in path] -resp = requests.post(url=url, files=files) -annotations = json.loads(resp.text) # dictionary of annotation results -bounding_boxes = annotations["boxes"] -labels = annotations["labels"] -print(labels) -# [['16.0', '16.0', '16.0', '15.0', '15.0']] -``` -#### Use Case Specific Arguments -To use the `class_names` argument, create a Server configuration file for passing the argument via kwargs. - -This configuration file sets `class_names` to `coco`: -```yaml -# yolo-config.yaml -endpoints: - - task: yolo - model: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 - kwargs: - class_names: coco -``` -Start the server: -```bash -deepsparse.server --config-file yolo-config.yaml -``` -Run inference: -```python -import requests -import json - -url = 'http://0.0.0.0:5555/predict/from_files' -path = ['pets.jpg'] # list of images for inference -files = [('request', open(img, 'rb')) for img in path] -resp = requests.post(url=url, files=files) -annotations = json.loads(resp.text) # dictionary of annotation results -bounding_boxes = annotations["boxes"] -labels = annotations["labels"] -print(labels) -# [['dog', 'dog', 'dog', 'cat', 'cat']] -``` -### Cross Use Case Functionality - -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. From ae25136ea8313398623f232b5898438b2976a471 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 17 Apr 2023 15:33:51 -0400 Subject: [PATCH 052/149] updated embedding extraction example --- use-cases/cv/embedding-extraction.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/use-cases/cv/embedding-extraction.md b/use-cases/cv/embedding-extraction.md index 0d8f3b1c92..8b6b03b8ad 100644 --- a/use-cases/cv/embedding-extraction.md +++ b/use-cases/cv/embedding-extraction.md @@ -5,6 +5,7 @@ This page explains how to deploy an Embedding Extraction Pipeline with DeepSpars This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). + ## Model Format The Embedding Extraction Pipeline enables you to generate embeddings in any domain, meaning you can use it with any ONNX model. It (optionally) removes the projection head from the model, such that you can re-use SparseZoo models and custom models you have trained in the embedding extraction scenario. @@ -12,12 +13,13 @@ There are two options for passing a model to the Embedding Extraction Pipeline: - Pass a Local ONNX File - Pass a SparseZoo Stub (which identifies an ONNX model in the SparseZoo) + ## DeepSparse Pipelines Pipeline is the default interface for interacting with DeepSparse. Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. -We will use the `Pipeline.create()` constructor to create an instance of an embedding extraction Pipeline with a 75% pruned-quantized version of ResNet-50 trained on `imagenet`. We can then pass images the `Pipeline` and receive the embeddings. All of the pre-processing is handled by the `Pipeline`. +We will use the `Pipeline.create()` constructor to create an instance of an embedding extraction Pipeline with a 95% pruned-quantized version of ResNet-50 trained on `imagenet`. We can then pass images the `Pipeline` and receive the embeddings. All of the pre-processing is handled by the `Pipeline`. The Embedding Extraction Pipeline handles some useful actions around inference: @@ -29,9 +31,10 @@ This is an example of extracting the last layer from ResNet-50: Download an image to use with the Pipeline. ```bash -wget https://huggingface.co/spaces/neuralmagic/cv-yolo/resolve/main/pets.jpg +wget https://huggingface.co/spaces/neuralmagic/image-classification/resolve/main/lion.jpeg ``` -Define the Pipeline: + +Run the following to extract the embedding: ```python from deepsparse import Pipeline @@ -39,15 +42,16 @@ from deepsparse import Pipeline rn50_embedding_pipeline = Pipeline.create( task="embedding-extraction", base_task="image-classification", # tells the pipeline to expect images and normalize input with ImageNet means/stds - model_path="zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/channel20_pruned75_quant-none-vnni", + model_path="zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none", emb_extraction_layer=-3, # extracts last layer before projection head and softmax ) # this step runs pre-processing, inference and returns an embedding -embedding = rn50_embedding_pipeline(images="pets.jpg") +embedding = rn50_embedding_pipeline(images="lion.jpeg") print(len(embedding.embeddings[0][0])) -# 2048 +# 2048 << size of final layer>> ``` + # DeepSparse Server As an alternative to the Python API, DeepSparse Server allows you to serve an Embedding Extraction Pipeline over HTTP. Configuring the server uses the same parameters and schemas as the Pipelines. @@ -65,7 +69,7 @@ endpoints: ``` Spin up the server: ```bash -deepsparse.server --config_file general-embedding.yaml +deepsparse.server --config_file config.yaml ``` Make requests to the server: ```python @@ -77,8 +81,9 @@ resp = requests.post(url=url, files=files) result = json.loads(resp.text) print(len(result["embeddings"][0][0])) -# 2048 + +# 2048 << size of final layer>> ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file +Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file From 94b2f048478156ed2fe82991463ffaee827947f9 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 17 Apr 2023 17:59:00 -0400 Subject: [PATCH 053/149] updated sentiment analysis and text classification examples --- use-cases/nlp/sentiment-analysis.md | 58 +++---- use-cases/nlp/text-classification.md | 244 +++++++++++++-------------- 2 files changed, 144 insertions(+), 158 deletions(-) diff --git a/use-cases/nlp/sentiment-analysis.md b/use-cases/nlp/sentiment-analysis.md index 9ac3ea70cf..44f9055030 100644 --- a/use-cases/nlp/sentiment-analysis.md +++ b/use-cases/nlp/sentiment-analysis.md @@ -5,22 +5,19 @@ This page explains how to benchmark and deploy a sentiment analysis model with D There are three interfaces for interacting with DeepSparse: - **Engine** is the lowest-level API. It enables you to compile a model and run inference on raw input tensors. -- **Pipeline** is the default DeepSparse API. Similiar in concept to Hugging Face Pipelines, it wraps Engine with pre-preprocessing -and post-processing, allowing you to make requests on raw data and recieve post-processed predictions. +- **Pipeline** is the default DeepSparse API. Similiar in concept to Hugging Face Pipelines, it wraps Engine with pre-preprocessing and post-processing, allowing you to make requests on raw data and recieve post-processed predictions. -- **Server** is a REST API wrapper around Pipelines built on FastAPI and Uvicorn. It enables you to stand up a model serving -endpoint running DeepSparse with a single CLI. +- **Server** is a REST API wrapper around Pipelines built on FastAPI and Uvicorn. It enables you to stand up a model serving endpoint running DeepSparse with a single CLI. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 32 -core AWS c6i.16xlarge instance. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 4 core AWS `c6i.2xlarge` instance. ### ONNX Runtime Baseline @@ -29,16 +26,16 @@ As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you ```bash deepsparse.benchmark \ zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none \ - -b 64 -s sync -nstreams 1 -i [64,384] \ + -b 64 -s sync -nstreams 1 \ -e onnxruntime > Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 18.6350 +> Throughput (items/sec): 19.61 ``` -ONNX Runtime achieves 19 items/second with batch 64 and sequence length 384. +ONNX Runtime achieves 20 items/second with batch 64 and sequence length 128. ### DeepSparse Speedup @@ -48,21 +45,20 @@ retaining >99% accuracy of the dense baseline on the SST2 dataset. ```bash deepsparse.benchmark \ zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none \ - -b 64 -s sync -nstreams 1 -i [64,384] \ + -b 64 -s sync -nstreams 1 \ -e deepsparse > Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 217.6529 +> Throughput (items/sec): 125.80 ``` -DeepSparse achieves 218 items/second, ***an 11.5x speed-up over ONNX Runtime!*** +DeepSparse achieves 126 items/second, an 6.4x speed-up over ONNX Runtime! ## DeepSparse Engine -Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended you use the Pipeline -API but Engine is available as needed if you want to handle pre- or post-processing yourself. +Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended you use the Pipeline API but Engine is available as needed if you want to handle pre- or post-processing yourself. With Engine, we can compile an ONNX file and run inference on raw tensors. @@ -76,14 +72,14 @@ import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" batch_size = 1 -bert_engine = Engine( +compiled_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) -output = bert_engine(inputs) +output = compiled_model(inputs) print(output) # >> [array([[-0.3380675 , 0.09602544]], dtype=float32)] @@ -126,8 +122,7 @@ The Sentiment Analysis Pipeline contains additional arguments for configuring a #### Sequence Length -The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. Inside the DeepSparse pipelines, -the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. +DeepSparse uses static input shapes. We can use the `sequence_length` argument to adjust the ONNX graph to handle a specific sequence length. Inside the DeepSparse pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. The example below compiles the model and runs inference with sequence length 64. @@ -141,7 +136,7 @@ sequence_length = 64 sa_pipeline = Pipeline.create( task="sentiment-analysis", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size=1 # default batch size is 1 + batch_size=1, # default batch size is 1 sequence_length=64 # default sequence length is 128 ) @@ -152,14 +147,12 @@ print(prediction) # >>> labels=['positive'] scores=[0.9955807328224182] ``` -Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, -the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) -and direct inputs towards the smallest bucket into which it fits. +If your input data has a variable distribution of seuqence lengths, you can simulate dynamic shape infernece by passing a list of sequence lengths to DeepSparse, which a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline compile multiple versions of the model at each sequence length (utilizing a shared scheduler) and directs inputs towards the smallest bucket into which it fits. The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). ```python -from deepsparse import Pipeline +from deepsparse import Pipeline, Context # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" @@ -167,9 +160,10 @@ batch_size = 1 buckets = [16, 128] sa_pipeline = Pipeline.create( task="sentiment-analysis", - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size=1, # default batch size is 1 - sequence_length=buckets # creates bucketed pipeline + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + sequence_length=buckets, # creates bucketed pipeline + context = Context(num_streams=1) # creates scheduler with one stream ) # run inference on short sequence @@ -228,7 +222,7 @@ print(prediction_b2) ### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +Check out the Pipeline User Guide for more details on configuring a Pipeline. ## DeepSparse Server @@ -273,8 +267,8 @@ This configuration file sets sequence length to 64 and returns all scores: ```yaml # sentiment-analysis-config.yaml endpoints: - - task: text-classification - model: zoo:nlp/document_classification/obert-base/pytorch/huggingface/imdb/pruned90_quant-none + - task: sentiment-analysis + model: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none kwargs: sequence_length: 64 # uses sequence length 64 return_all_scores: True # returns all scores @@ -304,4 +298,4 @@ print(resp.text) ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. +Check out the Server User Guide for more details on configuring the Server. diff --git a/use-cases/nlp/text-classification.md b/use-cases/nlp/text-classification.md index 01a26473bf..5a75e1aff7 100644 --- a/use-cases/nlp/text-classification.md +++ b/use-cases/nlp/text-classification.md @@ -13,52 +13,46 @@ endpoint running DeepSparse with a single CLI. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 4 core AWS `c6i.2xlarge` instance. ### ONNX Runtime Baseline As a baseline, let's check out ONNX Runtime's performance on oBERT. Make sure you have ORT installed (`pip install onnxruntime`). + ```bash deepsparse.benchmark \ zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/base-none \ - -b 64 -s sync -nstreams 1 -i [64,384] \ + -b 64 -s sync -nstreams 1 \ -e onnxruntime > Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 13.0320 -> Latency Mean (ms/batch): 4910.9819 -> Latency Median (ms/batch): 4900.1473 -> Latency Std (ms/batch): 63.6517 -> Iterations: 3 +> Throughput (items/sec): 19.3496 ``` -ONNX Runtime achieves 13 items/second with batch 64 and sequence length 384. + +ONNX Runtime achieves 19 items/second with batch 64 and sequence length 128. ### DeepSparse Speedup Now, let's run DeepSparse on an inference-optimized sparse version of oBERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the MNLI dataset. ```bash -!deepsparse.benchmark \ +deepsparse.benchmark \ zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none \ - -b 64 -s sync -nstreams 1 -i [64,384] \ + -b 64 -s sync -nstreams 1 \ -e deepsparse > Original Model Path: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 89.7097 -> Latency Mean (ms/batch): 713.3987 -> Latency Median (ms/batch): 710.9072 -> Latency Std (ms/batch): 8.0025 -> Iterations: 15 +> Throughput (items/sec): 124.0120 ``` -DeepSparse achieves 85 items/second, an 7x speed-up over ONNX Runtime! +DeepSparse achieves 124 items/second, an 6.5x speed-up over ONNX Runtime! ## DeepSparse Engine Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended you use the Pipeline API but Engine is available as needed if you want to handle pre- or post-processing yourself. @@ -74,14 +68,14 @@ import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" batch_size = 1 -bert_engine = Engine( +compiled_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) -output = bert_engine(inputs) +output = compiled_model(inputs) print(output) # [array([[-0.9264987, -1.6990623, 2.3935342]], dtype=float32)] @@ -91,56 +85,19 @@ Pipeline is the default interface for interacting with DeepSparse. Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. -We will use the `Pipeline.create()` constructor to create an instance of a text classification Pipeline with a 90% pruned-quantized version of oBERT trained on MNLI. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. -```python -from deepsparse import Pipeline +We will use the `Pipeline.create()` constructor to create an instance of a text classification Pipeline with a 90% pruned-quantized version of oBERT. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. -# download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" -batch_size = 1 -pipeline = Pipeline.create( - task="text-classification", - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size=1 # default batch size is 1 -) - -# run inference on image file -prediction = pipeline("The text classification pipeline is fast and easy to use") -print(prediction) -# labels=['entailment'] scores=[0.5807693004608154] +The Text Classification Pipeline can handle multi-input and single-input as well as single-label and multi-label classification. -``` -#### Zero Shot Classification -Given certain categories, zero shot classification aims at determining the class that best fits the given text. +#### Single-Input Single-Label Example (SST2) -Here's an example of a zero shot text classification example with a DistilBERT that's 80% pruned and quantized on the MNLI dataset. +Here's an example with a single input and single label prediction with a model trained on SST2: ```python from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/mnli/pruned80_quant-none-vnni" -batch_size = 1 -pipeline = Pipeline.create( - task="zero_shot_text_classification", - model_path="zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/mnli/pruned80_quant-none-vnni", - ) - -# run inference on image file -prediction = pipeline(sequences = "Today is election day in America",labels=['politics', 'public health', 'Europe'],) -print(prediction) -# sequences='Today is election day in America' labels=['politics', 'Europe', 'public health'] scores=[0.8922364115715027, 0.06215662881731987, 0.04560691863298416] -``` -### Example with QQP -[QQP( Quora Question Pairs2)](https://huggingface.co/datasets/glue) is a dataset that is part of the GLUE benchmark. The goal is to determine if a pair of questions are semantically equivalent. - -Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90%(90% of the weights have been removed without loss of accuracy) and quantized on the QQP dataset. -```python -from deepsparse import Pipeline - -# download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/qqp/pruned90_quant-none" -batch_size = 1 +sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" pipeline = Pipeline.create( task="text-classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX @@ -148,18 +105,17 @@ pipeline = Pipeline.create( ) # run inference on image file -prediction = pipeline([[ - "Should I have a hair transplant at age 24? How much would it cost?", - "How much cost does hair transplant require?", - ]]) +sequences = ["I think DeepSparse Pipelines are awesome!"] +prediction = pipeline(sequences) print(prediction) -# labels=['not_duplicate'] scores=[0.6760590076446533] +# labels=['0'] scores=[0.9986200332641602] ``` -### Example with MNLI -[MNLI( Multi-Genre Natural Language Inference)](https://huggingface.co/datasets/glue) is a dataset with textual entailment annotations. The goal is to predict entailment, contradiction and neutrality given a premise and hypothesis. -Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90%(90% of the weights have been removed without loss of accuracy) and quantized on the MNLI dataset. +#### Multi-Input Single-Label Example (MNLI) + +Here's an example with a single input and single label prediction with a model trained on MNLI: + ```python from deepsparse import Pipeline @@ -172,43 +128,47 @@ pipeline = Pipeline.create( batch_size=1 # default batch size is 1 ) -# run inference on image file -prediction = pipeline([[ - "Timely access to information is in the best interests of both GAO and the agencies", - "It is in everyone's best interest to have access to information in a timely manner", - ]]) +# run inference +sequences = [[ + "The text classification pipeline is fast and easy to use!", + "The pipeline for text classification makes it simple to get started" +]] +prediction = pipeline(sequences) print(prediction) -# labels=['entailment'] scores=[0.9688315987586975] +# labels=['entailment'] scores=[0.6885718107223511] ``` -### Example with Document Classification -Document classification involves classifying text in a long document. +#### Multi-Input Single-Label Example (QQP) -Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90% and quantized on the IMDB dataset. +Here's an example with a single input and single label prediction with a model trained on QQP: ```python from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/document_classification/obert-base/pytorch/huggingface/imdb/pruned90_quant-none" +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/qqp/pruned90_quant-none" +batch_size = 1 pipeline = Pipeline.create( task="text-classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=1 # default batch size is 1 ) -# run inference on image file -prediction = pipeline(["Phil the Alien is one of those quirky films where the humour is based around the oddness of everything rather than actual punchlines.

At first it was very odd and pretty funny but as the movie progressed I didn't find the jokes or oddness funny anymore.

Its a low budget film (thats never a problem in itself), there were some pretty interesting characters, but eventually I just lost interest.

I imagine this film would appeal to a stoner who is currently partaking.

For something similar but better try Brother from another planet"]) +# run inference +sequences = [[ + "Which is the best gaming laptop under 40k?", + "Which is the best gaming laptop under 40,000 rs?", +]] +prediction = pipeline(sequences) print(prediction) -# labels=['0'] scores=[0.9986200332641602] - +# labels=['duplicate'] scores=[0.9978139996528625] ``` -### Example with GoEmotions -[The GoEmotions](https://huggingface.co/datasets/go_emotions) dataset contains Reddit comments labeled for 27 emotion categories or Neutral. The goal is to perform multi-class, multi-label emotion classification. -Let's illustrate that using a SparseZoo `obert` model that has been pruned to 90% and quantized on the GoEmotions dataset. +### Single-Input Multi-Label Example (GoEmotions) + +Here's an example with a single input and multi label prediction with a model trained on GoEmotions: ```python from deepsparse import Pipeline @@ -222,58 +182,60 @@ pipeline = Pipeline.create( ) # run inference on image file -prediction = pipeline(["Thank you for asking questions and recognizing that there may be things that you don’t know or understand about police tactics. Seriously. Thank you."]) +prediction = pipeline(["I am so glad you came today"]) print(prediction) -#labels=['gratitude'] scores=[0.9986923336982727] +# labels=['joy'] scores=[0.9472543597221375] ``` + ### Use Case Specific Arguments The Text Classification Pipeline contains additional arguments for configuring a `Pipeline`. #### Sequence Length -The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. +The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. The defaul sequence length for text classification is 128. -The example below compiles the model and runs inference with sequence length 64. +The example below runs document classification using a model trained on IMBD at sequence length 512. ```python from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/mnli/pruned80_quant-none-vnni" -batch_size = 1 -sequence_length = 64 +sparsezoo_stub = "zoo:nlp/document_classification/obert-base/pytorch/huggingface/imdb/pruned90_quant-none" pipeline = Pipeline.create( - task="zero_shot_text_classification", - model_path=sparsezoo_stub, - sequence_length=sequence_length, - batch_size =batch_size, - ) + task="text_classification", + model_path=sparsezoo_stub, + sequence_length=512, + batch_size=1, +) # run inference on image file -prediction = pipeline(sequences = "Today is election day",labels=['politics', 'public health', 'Europe'],) +sequences = [[ + "I rented I AM CURIOUS-YELLOW from my video store because of all the controversy that surrounded it when it was first released in 1967. I also heard that at first it was seized by U.S. customs if it ever tried to enter this country, therefore being a fan of films considered 'controversial' I really had to see this for myself.

The plot is centered around a young Swedish drama student named Lena who wants to learn everything she can about life. In particular she wants to focus her attentions to making some sort of documentary on what the average Swede thought about certain political issues such as the Vietnam War and race issues in the United States. In between asking politicians and ordinary denizens of Stockholm about their opinions on politics, she has sex with her drama teacher, classmates, and married men.

What kills me about I AM CURIOUS-YELLOW is that 40 years ago, this was considered pornographic. Really, the sex and nudity scenes are few and far between, even then it's not shot like some cheaply made porno. While my countrymen mind find it shocking, in reality sex and nudity are a major staple in Swedish cinema. Even Ingmar Bergman, arguably their answer to good old boy John Ford, had sex scenes in his films.

I do commend the filmmakers for the fact that any sex shown in the film is shown for artistic purposes rather than just to shock people and make money to be shown in pornographic theaters in America. I AM CURIOUS-YELLOW is a good film for anyone wanting to study the meat and potatoes (no pun intended) of Swedish cinema. But really, this film doesn't have much of a plot." +]] +prediction = pipeline(sequences) print(prediction) -# sequences='Today is election day' labels=['politics', 'Europe', 'public health'] scores=[0.9697986245155334, 0.01720993034541607, 0.012991504743695259] +# labels=['0'] scores=[0.9984526634216309] (negative prediction) ``` -Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. + +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the DeepSparse Pipeline will compile multiple versions of the model (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which an input fits. The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). ```python -from deepsparse import Pipeline +from deepsparse import Pipeline, Context # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" -batch_size = 1 -buckets = [16, 128] pipeline = Pipeline.create( task="text-classification", - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + model_path=sparsezoo_stub, batch_size=1, - sequence_length=buckets # creates bucketed pipeline + sequence_length=[32, 128], + context=Context(num_streams=1) ) # run inference on image file prediction = pipeline([[ - "Timely access to information is in the best interests of both GAO and the agencies", - "It is in everyone's best interest to have access to information in a timely manner", + "Timely access to information is in the best interests of the agencies", + "It is everyone's best interest to get info in a timely manner", ]]) print(prediction) @@ -285,7 +247,6 @@ prediction = pipeline([[ print(prediction) #labels=['entailment'] scores=[0.9688315987586975] #labels=['entailment'] scores=[0.985545814037323] - ``` #### Return All Scores @@ -308,7 +269,7 @@ pipeline_b1 = Pipeline.create( pipeline_b2 = Pipeline.create( task="text-classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size=2, # default batch size is 1 + batch_size=2, # default batch size is 1 return_all_scores=True # default is false ) @@ -321,28 +282,30 @@ prediction_b1 = pipeline_b1(sequences_b1) print(prediction_b1) # run inference with b2 -sequences_b2 = sequences_b1 * batch_size +sequences_b2 = sequences_b1 * 2 prediction_b2 = pipeline_b2(sequences_b2) print(prediction_b2) # labels=[['entailment', 'neutral', 'contradiction']] scores=[[0.9688315987586975, 0.030656637623906136, 0.0005117706023156643]] # labels=[['entailment', 'neutral', 'contradiction'], ['entailment', 'neutral', 'contradiction']] scores=[[0.9688315987586975, 0.030656637623906136, 0.0005117706023156643], [0.9688315987586975, 0.030656637623906136, 0.0005117706023156643]] ``` + ### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +Check out the Pipeline User Guide for more details on configuring a Pipeline. + ## DeepSparse Server -Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. DeepSparse Server wraps the Pipeline API, so it inherits all the utilities provided by Pipelines. -The CLI command below launches a text classification pipeline with a 90% pruned-quantized oBERT model: +#### Single Input Usage + +The CLI command below launches a single-input text classification pipeline with a 90% pruned-quantized oBERT model trained on SST2: ```bash deepsparse.server \ - --task sentiment-analysis \ - --model_path "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" # or path/to/onnx - + --task text-classification \ + --model_path "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" # or path/to/onnx ``` -You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. -Here is an example client request, using the Python requests library for formatting the HTTP: +Making a request: ```python import requests @@ -356,9 +319,39 @@ resp = requests.post(url=url, json=obj) # recieve the post-processed output print(resp.text) -# {"labels":["entailment"],"scores":[0.5475465655326843]} +# {"labels":["positive"],"scores":[0.9330279231071472]} +``` + +## Multi-Input Usage +The CLI command below launches a single-input text classification pipeline with a 90% pruned-quantized oBERT model trained on MNLI: + +```bash +deepsparse.server \ + --task text-classification \ + --model_path "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" # or path/to/onnx +``` + +Making a request: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = { + "sequences": [[ + "The text classification pipeline is fast and easy to use!", + "The pipeline for text classification makes it simple to get started" +]]} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# {"labels":["entailment"],"scores":[0.6885718107223511]} ``` + ### Use Case Specific Arguments To use the `sequence_length` and `return_all_scores` arguments, create a Server configuration file for passing the arguments via kwargs. @@ -372,15 +365,14 @@ endpoints: sequence_length: 64 # uses sequence length 64 return_all_scores: True # returns all scores ``` -Spin up the server: +Spin up the server: ```bash +deepsparse.server --config-file config.yaml -deepsparse.server \ - --config-filetext-classification-config.yaml ``` -Making a request: +Making a request: ```python import requests @@ -398,4 +390,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. +Check out the Server User Guide for more details on configuring the Server. From 4ebdc59375e5028f075d868441d8bdbbbb59a399 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 17 Apr 2023 19:27:58 -0400 Subject: [PATCH 054/149] added zero shot text classification --- .../nlp/zero-shot-text-classification.md | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 use-cases/nlp/zero-shot-text-classification.md diff --git a/use-cases/nlp/zero-shot-text-classification.md b/use-cases/nlp/zero-shot-text-classification.md new file mode 100644 index 0000000000..3043a25dee --- /dev/null +++ b/use-cases/nlp/zero-shot-text-classification.md @@ -0,0 +1,239 @@ +# Deploying Zero Shot Text Classification Models + +This page explains how to benchmark and deploy a zero-shot text classification model with DeepSparse. + + +There are three interfaces for interacting with DeepSparse: +- **Engine** is the lowest-level API. It enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing +and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving +endpoint running DeepSparse with a single CLI. + +We will walk through an example of each. + +## Installation Requirements + +This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). + +## Task Overview + +Zero-shot text classification allows us to perform text classification over any set of potential labels without training a text classification model on those specific labels. + +We can accomplish this goal via two steps: +- Train a model to predict whether a given pair of sentences is an `entailment`, `neutral`, or `contradiction` (on a dataset like MNLI) +- For each sentence `S` and set of labels `L`, predict label `L_i` which has the highest entailment score between `S` and a hypothesis of the form `This text is related to {L_i}` as predicted by the model. + +## DeepSparse Pipelines + +Pipeline is the default interface for interacting with DeepSparse. + +Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. + +We will use the `Pipeline.create()` constructor to create an instance of a zero-shot text classification Pipeline with a 90% pruned-quantized version of oBERT trained on MNLI. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input and formatting the hypothesis) is handled by the `Pipeline`. + +Here's an example with a single input and single label prediction with a model trained on MNLI: + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +batch_size = 1 +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + labels=["poltics", "public health", "sports"] +) + +# run inference +prediction = pipeline("Who are you voting for in the upcoming election") +print(prediction) + +# sequences='Who are you voting for in the upcoming election' labels=['poltics', 'sports', 'public health'] scores=[0.5765101909637451, 0.23050746321678162, 0.19298239052295685] +``` + +We can also pass the labels at inference time: + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 +) + +# run inference +prediction = pipeline( + sequences="My favorite sports team is the Boston Red Sox", + labels=["sports", "politics", "public health"] +) +print(prediction) + +# sequences='My favorite sports team is the Boston Red Sox' labels=['sports', 'politics', 'public health'] scores=[0.9349604249000549, 0.048094600439071655, 0.016944952309131622] +``` + +### Use Case Specific Arguments +The Zero Shot Text Classification Pipeline contains additional arguments for configuring a `Pipeline`. + +#### Sequence Length +The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. The default sequence length for text classification is 128. + +The example below runs the zero-shot text classification at sequence length 64. + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + sequence_length=64 +) + +# run inference +prediction = pipeline( + sequences="My favorite sports team is the Boston Red Sox", + labels=["sports", "politics", "public health"] +) +print(prediction) + +# sequences='My favorite sports team is the Boston Red Sox' labels=['sports', 'politics', 'public health'] scores=[0.9349604249000549, 0.048094600439071655, 0.016944952309131622] +``` + +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the DeepSparse Pipeline will compile multiple versions of the model (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which an input fits. + +The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). +```python +from deepsparse import Pipeline, Context + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + sequence_length=[32,128], + context=Context(num_streams=1) +) + +# run inference +prediction = pipeline( + sequences="My favorite sports team is the Boston Red Sox", + labels=["sports", "politics", "public health"] +) +print(prediction) + +# sequences='My favorite sports team is the Boston Red Sox' labels=['sports', 'politics', 'public health'] scores=[0.9349604249000549, 0.048094600439071655, 0.016944952309131622] +``` + +### Model Config + +Additionally, we can pass a `model_config` to specify the form of the hypothesis passed to DeepSparse as part of the zero shot text classification scheme. + +For instance, rather than running the comparison with `"This text is related to {}"`, we can instead use `"This text is similiar to {}"` with the following: + +```python +from deepsparse import Pipeline, Context + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + model_config={"hypothesis_template": "This text is similar to {}"} +) + +# run inference +prediction = pipeline( + sequences="My favorite sports team is the Boston Red Sox", + labels=["sports", "politics", "public health"] +) +print(prediction) + +# sequences='My favorite sports team is the Boston Red Sox' labels=['sports', 'politics', 'public health'] scores=[0.5861895680427551, 0.32133620977401733, 0.0924743041396141] +``` + +### Cross Use Case Functionality +Check out the Pipeline User Guide for more details on configuring a Pipeline. + +## DeepSparse Server +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. DeepSparse Server wraps the Pipeline API, so it inherits all the utilities provided by Pipelines. + +The CLI command below launches a zero shot text classification pipeline with a 90% pruned-quantized oBERT model trained on MNLI: + +```bash +deepsparse.server \ + --task zero_shot_text_classification \ + --model_path "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" # or path/to/onnx +``` + +Making a request: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = { + "sequences": ["The Boston Red Sox are my favorite baseball team!"], + "labels": ["sports", "politics", "public health"] +} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# {"sequences":["The Boston Red Sox are my favorite baseball team!"],"labels":[["sports","politics","public health"]],"scores":[[0.9649990200996399,0.028026442974805832,0.006974523887038231]]} +``` + +### Use Case Specific Arguments +To use the `labels` and `model_config` arguments in the server constructor, create a Server configuration file for passing the arguments via kwargs. + +This configuration file sets the labels to `sports`, `politics` and `public health` and creates hypotheses of the form `"This sentence is similiar to {}"`. + +```yaml +# config.yaml +endpoints: + - task: zero_shot_text_classification + model: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none + kwargs: + labels: ["sports", "politics", "public health"] + model_config: {"hypothesis_template": "This text is similar to {}"} +``` + +Spin up the server: +```bash +deepsparse.server --config-file config.yaml +``` + +Making a request: +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"sequences": ["The Boston Red Sox are my favorite baseball team!"]} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# {"sequences":["The Boston Red Sox are my favorite baseball team!"],"labels":[["sports","politics","public health"]],"scores":[[0.7818478941917419,0.17189143598079681,0.04626065865159035]]} + +``` +### Cross Use Case Functionality + +Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file From 0a56876f120c606093ce8b12583aac2b23a93ca1 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 17 Apr 2023 20:02:49 -0400 Subject: [PATCH 055/149] RS edited token classification --- use-cases/nlp/token-classification.md | 189 +++++++++++------- .../nlp/transformers-embedding-extraction.md | 5 +- 2 files changed, 122 insertions(+), 72 deletions(-) diff --git a/use-cases/nlp/token-classification.md b/use-cases/nlp/token-classification.md index b1cadbafd6..e6c1ac4bfb 100644 --- a/use-cases/nlp/token-classification.md +++ b/use-cases/nlp/token-classification.md @@ -12,51 +12,51 @@ and post-processing steps, allowing you to make requests on raw data and receive endpoint running DeepSparse with a single CLI. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 4 core AWS `c6i.2xlarge` instance. ### ONNX Runtime Baseline As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you have ORT installed (`pip install onnxruntime`). ```bash deepsparse.benchmark \ - zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/base-none \ - -b 64 -s sync -nstreams 1 -i [64,384] \ + zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/base-none \ + -b 64 -s sync -nstreams 1 -i [64,128] \ -e onnxruntime -> Original Model Path: zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/base-none +> Original Model Path: zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 12.2691 -> Latency Mean (ms/batch): 5216.3342 -> Latency Median (ms/batch): 5216.3342 -> Latency Std (ms/batch): 27.7928 -> Iterations: 2 +> Throughput (items/sec): 19.96 ``` -ONNX Runtime achieves 12 items/second with batch 64 and sequence length 384. +ONNX Runtime achieves 20 items/second with batch 64 and sequence length 128. + ## DeepSparse Speedup -Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 80% pruned and quantized, while retaining >99% accuracy of the dense baseline on the [conll2003](https://huggingface.co/datasets/conll2003) dataset. + +Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the conll dataset. + ```bash deepsparse.benchmark \ - zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni \ - -b 64 -s sync -nstreams 1 -i [64,384] \ + zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none \ + -b 64 -s sync -nstreams 1 -i [64,128] \ -e deepsparse -> Original Model Path: zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni + +> Original Model Path: Original Model Path: zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 99.7367 -> Latency Mean (ms/batch): 641.6757 -> Latency Median (ms/batch): 641.0878 -> Latency Std (ms/batch): 4.0909 -> Iterations: 16 +> Throughput (items/sec): 126.5129 ``` -DeepSparse achieves 100 items/second, a 8x speed-up over ONNX Runtime! + +DeepSparse achieves 127 items/second, a 6.4x speed-up over ONNX Runtime! + ## DeepSparse Engine + Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. With Engine, we can compile an ONNX file and run inference on raw tensors. @@ -68,39 +68,44 @@ from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 -sparsezoo_stub = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" +sparsezoo_stub = "zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none" batch_size = 1 -bert_engine = Engine( +compiled_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) -output = bert_engine(inputs) +output = compiled_model(inputs) print(output) # array([[[ 2.0983224 , 1.2409506 , -1.7314302 , ..., -0.07210742, #... # -2.0502508 , -2.956191 ]]], dtype=float32)] ``` + ## DeepSparse Pipelines Pipeline is the default interface for interacting with DeepSparse. Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. -We will use the `Pipeline.create()` constructor to create an instance of a token classification Pipeline with a 80% pruned-quantized version of BERT trained on conll2003. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. +We will use the `Pipeline.create()` constructor to create an instance of a token classification Pipeline with a 90% pruned-quantized version of BERT trained on conll2003. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. + ```python from deepsparse import Pipeline -task = "ner" -model_path = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" +model_path = "zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none" pipeline = Pipeline.create( - task=task, + task="token_classification", model_path=model_path, ) output = pipeline("Mary is flying from Nairobi to New York") -print(output) -# predictions=[[TokenClassificationResult(entity='LABEL_1', score=0.9949890971183777, word='mary', start=0, end=4, index=1, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997545480728149, word='is', start=5, end=7, index=2, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997464418411255, word='flying', start=8, end=14, index=3, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997068643569946, word='from', start=15, end=19, index=4, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9992954730987549, word='nairobi', start=20, end=27, index=5, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9997722506523132, word='to', start=28, end=30, index=6, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9994122385978699, word='new', start=31, end=34, index=7, is_grouped=False), TokenClassificationResult(entity='LABEL_6', score=0.9990378022193909, word='york', start=35, end=39, index=8, is_grouped=False)]] +print(output.predictions) +# [[TokenClassificationResult(entity='B-PER', score=0.9971914291381836, word='mary', start=0, end=4, index=1, is_grouped=False), +# TokenClassificationResult(entity='B-LOC', score=0.9993892312049866, word='nairobi', start=20, end=27, index=5, is_grouped=False), +# TokenClassificationResult(entity='B-LOC', score=0.9993736147880554, word='new', start=31, end=34, index=7, is_grouped=False), +# TokenClassificationResult(entity='I-LOC', score=0.997299075126648, word='york', start=35, end=39, index=8, is_grouped=False)]] ``` + ### Use Case Specific Arguments The Token Classification Pipeline contains additional arguments for configuring a `Pipeline`. @@ -110,47 +115,93 @@ The `sequence_length` argument adjusts the ONNX graph to handle a specific seque The example below compiles the model and runs inference with sequence length of 64. ```python from deepsparse import Pipeline -task = "ner" -sequence_length = 64 -model_path = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" +model_path = "zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none" +pipeline = Pipeline.create( + task="token_classification", + model_path=model_path, + sequence_length=64 + ) +print(output.predictions) +# [[TokenClassificationResult(entity='B-PER', score=0.9971914291381836, word='mary', start=0, end=4, index=1, is_grouped=False), +# TokenClassificationResult(entity='B-LOC', score=0.9993892312049866, word='nairobi', start=20, end=27, index=5, is_grouped=False), +# TokenClassificationResult(entity='B-LOC', score=0.9993736147880554, word='new', start=31, end=34, index=7, is_grouped=False), +# TokenClassificationResult(entity='I-LOC', score=0.997299075126648, word='york', start=35, end=39, index=8, is_grouped=False)]] +``` + +Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the model (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. +The example below creates a bucket for smaller input lengths (64 tokens) and for larger input lengths (128 tokens). +```python +from deepsparse import Pipeline, Context +model_path = "zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none" pipeline = Pipeline.create( - task=task, + task="token_classification", model_path=model_path, - sequence_length = sequence_length, + sequence_length = [64,128], + context=Context(num_streams=1) ) -output = pipeline("Mary is flying from Nairobi to New York to attend a conference on generative AI") -print(output) -# predictions=[[TokenClassificationResult(entity='LABEL_1', score=0.9950078129768372, word='mary', start=0, end=4, index=1, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999826192855835, word='is', start=5, end=7, index=2, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998066425323486, word='flying', start=8, end=14, index=3, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998202323913574, word='from', start=15, end=19, index=4, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9993807077407837, word='nairobi', start=20, end=27, index=5, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999809205532074, word='to', start=28, end=30, index=6, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.999479353427887, word='new', start=31, end=34, index=7, is_grouped=False), TokenClassificationResult(entity='LABEL_6', score=0.9990516901016235, word='york', start=35, end=39, index=8, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998992085456848, word='to', start=40, end=42, index=9, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999877393245697, word='attend', start=43, end=49, index=10, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998823404312134, word='a', start=50, end=51, index=11, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999748945236206, word='conference', start=52, end=62, index=12, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999583899974823, word='on', start=63, end=65, index=13, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.5017374753952026, word='genera', start=66, end=72, index=14, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.892431378364563, word='##tive', start=72, end=76, index=15, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.9190302491188049, word='ai', start=77, end=79, index=16, is_grouped=False)]] +output = pipeline("Mary is flying from Nairobi to New York to attend a conference") +print(output.predictions) +# [[TokenClassificationResult(entity='B-PER', score=0.9971914291381836, word='mary', start=0, end=4, index=1, is_grouped=False), +# TokenClassificationResult(entity='B-LOC', score=0.9993892312049866, word='nairobi', start=20, end=27, index=5, is_grouped=False), +# TokenClassificationResult(entity='B-LOC', score=0.9993736147880554, word='new', start=31, end=34, index=7, is_grouped=False), +# TokenClassificationResult(entity='I-LOC', score=0.997299075126648, word='york', start=35, end=39, index=8, is_grouped=False)]] ``` -Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. -The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). +#### Aggregation Strategy + +`aggregation_strategy` specifies how to aggregate tokens in post-processing in a case where a single word is split into multiple tokens by the tokenizer. The default is to use `none`, which means that we perform no aggregation. + +Here is an example using `simple` aggregation strategy. + ```python from deepsparse import Pipeline -task = "ner" -buckets = [16, 128] -model_path = "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" +model_path = "zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none" +pipeline = Pipeline.create( + task="token_classification", + model_path=model_path, + aggregation_strategy="simple" +) + +output = pipeline("The Uzbekistani striker scored a goal in the final minute to defeat the Italian national team") +print(output.predictions) + +# [[TokenClassificationResult(entity='MISC', score=0.9935868382453918, word='uzbekistani', start=4, end=15, index=None, is_grouped=True), +# TokenClassificationResult(entity='MISC', score=0.9991180896759033, word='italian', start=72, end=79, index=None, is_grouped=True)]] +``` + +In comparison, here is the standard output withe no aggregation: +```python +from deepsparse import Pipeline +model_path = "zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none" pipeline = Pipeline.create( - task=task, - model_path=model_path, - sequence_length = buckets, - ) -output = pipeline("Mary is flying from Nairobi to New York to attend a conference on generative AI") -print(output) -# predictions=[[TokenClassificationResult(entity='LABEL_1', score=0.9950078129768372, word='mary', start=0, end=4, index=1, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999826192855835, word='is', start=5, end=7, index=2, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998066425323486, word='flying', start=8, end=14, index=3, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998202323913574, word='from', start=15, end=19, index=4, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.9993807077407837, word='nairobi', start=20, end=27, index=5, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999809205532074, word='to', start=28, end=30, index=6, is_grouped=False), TokenClassificationResult(entity='LABEL_5', score=0.999479353427887, word='new', start=31, end=34, index=7, is_grouped=False), TokenClassificationResult(entity='LABEL_6', score=0.9990516901016235, word='york', start=35, end=39, index=8, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998992085456848, word='to', start=40, end=42, index=9, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999877393245697, word='attend', start=43, end=49, index=10, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.9998823404312134, word='a', start=50, end=51, index=11, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999748945236206, word='conference', start=52, end=62, index=12, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.999583899974823, word='on', start=63, end=65, index=13, is_grouped=False), TokenClassificationResult(entity='LABEL_0', score=0.5017374753952026, word='genera', start=66, end=72, index=14, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.892431378364563, word='##tive', start=72, end=76, index=15, is_grouped=False), TokenClassificationResult(entity='LABEL_8', score=0.9190302491188049, word='ai', start=77, end=79, index=16, is_grouped=False)]] + task="token_classification", + model_path=model_path, + aggregation_strategy="none" +) + +output = pipeline("The Uzbekistani striker scored a goal in the final minute to defeat the Italian national team") +print(output.predictions) + +# [[[TokenClassificationResult(entity='B-MISC', score=0.9973152279853821, word='uzbekistan', start=4, end=14, index=2, is_grouped=False), +# TokenClassificationResult(entity='I-MISC', score=0.9898584485054016, word='##i', start=14, end=15, index=3, is_grouped=False), +# TokenClassificationResult(entity='B-MISC', score=0.9991180896759033, word='italian', start=72, end=79, index=15, is_grouped=False)]] ``` + ### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +Check out the Pipeline User Guide for more details on configuring a Pipeline. + ## DeepSparse Server + DeepSparse Server is built on top of FastAPI and Uvicorn, enabling you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. -The CLI command below launches a token classification pipeline with a 80% pruned-quantized BERT model: +The CLI command below launches a token classification pipeline with a 90% pruned-quantized BERT model trained on Conll2003: + ```bash -deepsparse.server ---task ner ---model_path "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" # or path/to/onnx +deepsparse.server \ + --task token_classification \ + --model_path "zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none" # or path/to/onnx ``` You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. @@ -161,25 +212,25 @@ import requests # Uvicorn is running on this port url = 'http://0.0.0.0:5543/predict' # send the data -obj = { - "inputs": "Mary is flying from Nairobi to New York to attend a conference on generative AI", -} +obj = {"inputs": "Mary is flying from Nairobi to New York to attend a conference"} resp = requests.post(url=url, json=obj) # receive the post-processed output print(resp.text) -# {"predictions":[[{"entity":"LABEL_1","score":0.9950078129768372,"index":1,"word":"mary","start":0,"end":4,"is_grouped":false},{"entity":"LABEL_0","score":0.999826192855835,"index":2,"word":"is","start":5,"end":7,"is_grouped":false},{"entity":"LABEL_0","score":0.9998066425323486,"index":3,"word":"flying","start":8,"end":14,"is_grouped":false},{"entity":"LABEL_0","score":0.9998202323913574,"index":4,"word":"from","start":15,"end":19,"is_grouped":false},{"entity":"LABEL_5","score":0.9993807077407837,"index":5,"word":"nairobi","start":20,"end":27,"is_grouped":false},{"entity":"LABEL_0","score":0.999809205532074,"index":6,"word":"to","start":28,"end":30,"is_grouped":false},{"entity":"LABEL_5","score":0.999479353427887,"index":7,"word":"new","start":31,"end":34,"is_grouped":false},{"entity":"LABEL_6","score":0.9990516901016235,"index":8,"word":"york","start":35,"end":39,"is_grouped":false},{"entity":"LABEL_0","score":0.9998992085456848,"index":9,"word":"to","start":40,"end":42,"is_grouped":false},{"entity":"LABEL_0","score":0.999877393245697,"index":10,"word":"attend","start":43,"end":49,"is_grouped":false},{"entity":"LABEL_0","score":0.9998823404312134,"index":11,"word":"a","start":50,"end":51,"is_grouped":false},{"entity":"LABEL_0","score":0.999748945236206,"index":12,"word":"conference","start":52,"end":62,"is_grouped":false},{"entity":"LABEL_0","score":0.999583899974823,"index":13,"word":"on","start":63,"end":65,"is_grouped":false},{"entity":"LABEL_0","score":0.5017374753952026,"index":14,"word":"genera","start":66,"end":72,"is_grouped":false},{"entity":"LABEL_8","score":0.892431378364563,"index":15,"word":"##tive","start":72,"end":76,"is_grouped":false},{"entity":"LABEL_8","score":0.9190302491188049,"index":16,"word":"ai","start":77,"end":79,"is_grouped":false}]]} +# {"predictions":[[{"entity":"B-PER","score":0.9966245293617249,"word":"mary","start":0,"end":4,"index":1,"is_grouped":false},{"entity":"B-LOC","score":0.999544084072113,"word":"nairobi","start":20,"end":27,"index":5,"is_grouped":false},{"entity":"B-LOC","score":0.9993794560432434,"word":"new","start":31,"end":34,"index":7,"is_grouped":false},{"entity":"I-LOC","score":0.9970214366912842,"word":"york","start":35,"end":39,"index":8,"is_grouped":false}]]} ``` + #### Use Case Specific Arguments -To use the `sequence_length` argument, create a server configuration file for passing the arguments via `kwargs`. +To use the `sequence_length` and `aggregation_strategy` arguments, create a server configuration file for passing the arguments via `kwargs`. -This configuration file sets sequence length to 64: +This configuration file sets sequence length to 64 with `simple` aggregation strategy: ```yaml # ner-config.yaml endpoints: - - task: ner - model: zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni + - task: token_classification + model: zoo:nlp/token_classification/obert-base/pytorch/huggingface/conll2003/pruned90_quant-none kwargs: sequence_length: 64 # uses sequence length 64 + aggregation_strategy: simple ``` Spin up the server: @@ -195,16 +246,14 @@ import requests url = 'http://0.0.0.0:5543/predict' # send the data -obj = { - "inputs": "Mary is flying from Nairobi to New York to attend a conference on generative AI", -} - +obj = {"inputs": "Mary is flying from Nairobi to New York to attend a conference",} resp = requests.post(url=url, json=obj) # recieve the post-processed output print(resp.text) -# {"predictions":[[{"entity":"LABEL_1","score":0.9950078129768372,"index":1,"word":"mary","start":0,"end":4,"is_grouped":false},{"entity":"LABEL_0","score":0.999826192855835,"index":2,"word":"is","start":5,"end":7,"is_grouped":false},{"entity":"LABEL_0","score":0.9998066425323486,"index":3,"word":"flying","start":8,"end":14,"is_grouped":false},{"entity":"LABEL_0","score":0.9998202323913574,"index":4,"word":"from","start":15,"end":19,"is_grouped":false},{"entity":"LABEL_5","score":0.9993807077407837,"index":5,"word":"nairobi","start":20,"end":27,"is_grouped":false},{"entity":"LABEL_0","score":0.999809205532074,"index":6,"word":"to","start":28,"end":30,"is_grouped":false},{"entity":"LABEL_5","score":0.999479353427887,"index":7,"word":"new","start":31,"end":34,"is_grouped":false},{"entity":"LABEL_6","score":0.9990516901016235,"index":8,"word":"york","start":35,"end":39,"is_grouped":false},{"entity":"LABEL_0","score":0.9998992085456848,"index":9,"word":"to","start":40,"end":42,"is_grouped":false},{"entity":"LABEL_0","score":0.999877393245697,"index":10,"word":"attend","start":43,"end":49,"is_grouped":false},{"entity":"LABEL_0","score":0.9998823404312134,"index":11,"word":"a","start":50,"end":51,"is_grouped":false},{"entity":"LABEL_0","score":0.999748945236206,"index":12,"word":"conference","start":52,"end":62,"is_grouped":false},{"entity":"LABEL_0","score":0.999583899974823,"index":13,"word":"on","start":63,"end":65,"is_grouped":false},{"entity":"LABEL_0","score":0.5017374753952026,"index":14,"word":"genera","start":66,"end":72,"is_grouped":false},{"entity":"LABEL_8","score":0.892431378364563,"index":15,"word":"##tive","start":72,"end":76,"is_grouped":false},{"entity":"LABEL_8","score":0.9190302491188049,"index":16,"word":"ai","start":77,"end":79,"is_grouped":false}]]} +# {"predictions":[[{"entity":"PER","score":0.9966245293617249,"word":"mary","start":0,"end":4,"index":null,"is_grouped":true},{"entity":"LOC","score":0.999544084072113,"word":"nairobi","start":20,"end":27,"index":null,"is_grouped":true},{"entity":"LOC","score":0.9982004165649414,"word":"new york","start":31,"end":39,"index":null,"is_grouped":true}]]} ``` + ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file +Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file diff --git a/use-cases/nlp/transformers-embedding-extraction.md b/use-cases/nlp/transformers-embedding-extraction.md index 88ac0133a2..902a0e4054 100644 --- a/use-cases/nlp/transformers-embedding-extraction.md +++ b/use-cases/nlp/transformers-embedding-extraction.md @@ -12,9 +12,10 @@ and post-processing steps, allowing you to make requests on raw data and receive endpoint running DeepSparse with a single CLI. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). + +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## DeepSparse Pipelines Pipeline is the default interface for interacting with DeepSparse. From e31c22efa3d39d76ba6ce1e5c435b5ba307899f5 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 17 Apr 2023 20:29:40 -0400 Subject: [PATCH 056/149] updated question answering example --- use-cases/nlp/question-answering.md | 159 +++++++++++++++++----------- 1 file changed, 97 insertions(+), 62 deletions(-) diff --git a/use-cases/nlp/question-answering.md b/use-cases/nlp/question-answering.md index e4290fc11e..d88edb5c9a 100644 --- a/use-cases/nlp/question-answering.md +++ b/use-cases/nlp/question-answering.md @@ -1,4 +1,5 @@ # Deploying Question Answering Models with DeepSparse + This page explains how to benchmark and deploy a question answering model with DeepSparse. There are three interfaces for interacting with DeepSparse: @@ -10,55 +11,54 @@ and post-processing steps, allowing you to make requests on raw data and receive - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. +We will walk through an example of each. + ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## Benchmarking -We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 12-core server. +We can use the benchmarking utility to demonstrate the DeepSparse's performance. We ran the numbers below on a 4 core AWS `c6i.2xlarge` instance. ### ONNX Runtime Baseline As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you have ORT installed (`pip install onnxruntime`). + ````bash deepsparse.benchmark \ - zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none \ + zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/base-none \ -b 64 -s sync -nstreams 1 -i [64,384] \ -e onnxruntime + > Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 13.1489 -> Latency Mean (ms/batch): 4867.3156 -> Latency Median (ms/batch): 4834.5695 -> Latency Std (ms/batch): 51.7144 -> Iterations: 3 +> Throughput (items/sec): 5.5482 ```` -ONNX Runtime achieves 13 items/second with batch 64 and sequence length 384. +ONNX Runtime achieves 5.5 items/second with batch 64 and sequence length 384. ## DeepSparse Engine Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the [SQuAD](https://huggingface.co/datasets/squad) dataset. ```bash deepsparse.benchmark \ - zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/base-none \ + zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none\ -b 64 -s sync -nstreams 1 -i [64,384] \ -e deepsparse -> Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none +> Original Model Path: zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 89.1442 -> Latency Mean (ms/batch): 717.9248 -> Latency Median (ms/batch): 717.2859 -> Latency Std (ms/batch): 4.5779 -> Iterations: 14 +> Throughput (items/sec): 31.6372 + ``` -DeepSparse achieves 89 items/second, an 7x speed-up over ONNX Runtime! +DeepSparse achieves 31.6 items/second, an 5.8x speed-up over ONNX Runtime! + ## DeepSparse Engine + Engine is the lowest-level API for interacting with DeepSparse. As much as possible, we recommended using the Pipeline API but Engine is available if you want to handle pre- or post-processing yourself. With Engine, we can compile an ONNX file and run inference on raw tensors. @@ -72,19 +72,21 @@ import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" batch_size = 1 -bert_engine = Engine( +complied_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=batch_size # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) -output = bert_engine(inputs) +output = complied_model(inputs) print(output) -#[array([[-6.904723 , -7.2960553, -6.903628 , -6.930577 , -6.899986 , + +# [array([[-6.904723 , -7.2960553, -6.903628 , -6.930577 , -6.899986 , # ..... # -6.555915 , -6.6454444, -6.4477777, -6.8030496]], dtype=float32)] ``` + ## DeepSparse Pipelines Pipeline is the default interface for interacting with DeepSparse. @@ -92,77 +94,108 @@ Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing We will use the `Pipeline.create()` constructor to create an instance of a question answering Pipeline with a 90% pruned-quantized version of BERT trained on SQuAD. We can then pass raw text to the `Pipeline` and receive the predictions. All of the pre-processing (such as tokenizing the input) is handled by the `Pipeline`. ```python - from deepsparse import Pipeline task = "question-answering" qa_pipeline = Pipeline.create( task=task, model_path="zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none", ) + q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" question = "What is DeepSparse?" output = qa_pipeline(question=question, context=q_context) -print(output) -# QuestionAnsweringOutput(score=23.620140075683594, answer='sparsity-aware inference runtime', start=14, end=46) +print(output.answer) +# sparsity-aware inference runtime ``` + ### Use Case Specific Arguments The Question Answering Pipeline contains additional arguments for configuring a `Pipeline`. -#### Sequence Length -The `sequence_length` argument adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. +#### Sequence Length, Question Length + +The `sequence_length` and `max_question_length` arguments adjusts the ONNX graph to handle a specific sequence length. In the DeepSparse Pipelines, the tokenizers pad the input. As such, using shorter sequence lengths will have better performance. + +The example below compiles the model and runs inference with sequence length 64 and truncates any question longer than 32 tokens. -The example below compiles the model and runs inference with sequence length 64. ```python from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" -batch_size = 1 -task = "question-answering" -sequence_length = 64 -pipeline = Pipeline.create( - task=task, - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - sequence_length=sequence_length, - batch_size =batch_size, # default batch size is 1 - ) +qa_pipeline = Pipeline.create( + task="question-answering", + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + sequence_length=64, + max_question_length=32, +) # run inference on image file q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" question = "What is DeepSparse?" output = qa_pipeline(question=question, context=q_context) -print(output) -# QuestionAnsweringOutput(score=23.620140075683594, answer='sparsity-aware inference runtime', start=14, end=46) +print(output.answer) + +# sparsity-aware inference runtime + ``` Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). ```python -from deepsparse import Pipeline +from deepsparse import Pipeline, Context # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" -batch_size = 1 task = "question-answering" -buckets = [16, 128] -pipeline = Pipeline.create( - task=task, - model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX - sequence_length=buckets, # creates bucketed pipeline - batch_size =batch_size, # default batch size is 1 - ) +qa_pipeline = Pipeline.create( + task=task, + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + sequence_length=[64, 128], # creates bucketed pipeline + max_question_length=32, + context=Context(num_streams=1) +) # run inference on image file q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" question = "What is DeepSparse?" output = qa_pipeline(question=question, context=q_context) -print(output) -# QuestionAnsweringOutput(score=23.620140075683594, answer='sparsity-aware inference runtime', start=14, end=46) +print(output.answer) +# sparsity-aware inference runtime +``` + +#### Document Stride + +If the context is too long to fit in the max sequence length of the model, the DeepSparse Pipeline splits the context into several overlapping chunks and runs the inference on each chunk. The `doc_stride` argument controls the number of token overlaps between the chunks. + +```python +from deepsparse import Pipeline, Context + +# download onnx from sparsezoo and compile with batch size 1 +sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +task = "question-answering" + +qa_pipeline = Pipeline.create( + task=task, + model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX + sequence_length=24, # creates bucketed pipeline + max_question_length=8, + doc_stride=4 +) + +# run inference on image file +q_context = "I have been trying to accelerate my inference workloads. DeepSparse is a CPU runtime that helps me." +question = "What is DeepSparse?" +output = qa_pipeline(question=question, context=q_context) +print(output.answer) +# CPU runtime ``` + ### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +Check out the Pipeline User Guide for more details on configuring a Pipeline. + ## DeepSparse Server + DeepSparse Server is built on top of FastAPI and Uvicorn, enabling you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. The CLI command below launches a question answering pipeline with a 90% pruned-quantized BERT model: @@ -170,7 +203,7 @@ The CLI command below launches a question answering pipeline with a 90% pruned-q ```bash deepsparse.server \ --task question-answering \ - --model_path "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" # or path/to/onnx + --model_path zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none # or path/to/onnx ``` You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. @@ -193,23 +226,25 @@ resp = requests.post(url=url, json=obj) print(resp.text) # {"score":23.620140075683594,"answer":"sparsity-aware inference runtime","start":14,"end":46} ``` + #### Use Case Specific Arguments -To use the `sequence_length` argument, create a server configuration file for passing the arguments via `kwargs`. +To use the task specific arguments, create a server configuration file for passing the arguments via `kwargs`. This configuration file sets sequence length to 64: ```yaml # question-answering-config.yaml endpoints: - task: question-answering - model: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none + model: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none kwargs: - sequence_length: 64 # uses sequence length 64 + sequence_length: 24 # uses sequence length 64 + max_question_length: 8 + doc_stride: 4 ``` Spin up the server: ```bash -deepsparse.server \ - --config-file question-answering-config.yaml +deepsparse.server --config-file question-answering-config.yaml ``` Making a request: ```python @@ -220,15 +255,15 @@ url = "http://localhost:5543/predict" # send the data obj = { - "question": "Who is Mark?", - "context": "Mark is batman." + "question": "What is DeepSparse?", + "context": "I have been trying to accelerate my inference workloads. DeepSparse is a CPU runtime that helps me." } -response = requests.post(url, json=obj) +resp = requests.post(url, json=obj) # receive the post-processed output -print(response.text) -# {"score":22.506305694580078,"answer":"batman","start":8,"end":14} +print(resp.text) +# {"score":19.74649429321289,"answer":"CPU runtime","start":73,"end":84} ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. +Check out the Server User Guide for more details on configuring the Server. From f260be733e5e3d9f99052931603de8d3d8c11843 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 17 Apr 2023 20:46:29 -0400 Subject: [PATCH 057/149] updated embedding extraction case --- .../nlp/transformers-embedding-extraction.md | 88 ++++++++++++++----- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/use-cases/nlp/transformers-embedding-extraction.md b/use-cases/nlp/transformers-embedding-extraction.md index 902a0e4054..7e0ae7bebc 100644 --- a/use-cases/nlp/transformers-embedding-extraction.md +++ b/use-cases/nlp/transformers-embedding-extraction.md @@ -11,6 +11,8 @@ and post-processing steps, allowing you to make requests on raw data and receive - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. +For the embedding extraction case, we will walk through an example of Pipeline and Server. + ## Installation Requirements This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). @@ -18,6 +20,7 @@ This use case requires the installation of [DeepSparse Server](https://docs.neur Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). ## DeepSparse Pipelines + Pipeline is the default interface for interacting with DeepSparse. Like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-processing around the inference performed by the Engine. This creates a clean API that allows you to pass raw text and images to DeepSparse and receive the post-processed predictions, making it easy to add DeepSparse to your application. @@ -42,7 +45,7 @@ from deepsparse import Pipeline bert_emb_pipeline = Pipeline.create( task="transformers_embedding_extraction", - model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", + model_path="zoo:nlp/masked_language_modeling/obert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", # emb_extraction_layer=-1, # (default: detect last layer) # extraction_strategy="per_token" # (default: concat embedding for each token) ) @@ -50,15 +53,16 @@ bert_emb_pipeline = Pipeline.create( input_sequence = "The generalized embedding extraction Pipeline is the best!" embedding = bert_emb_pipeline(input_sequence) print(len(embedding.embeddings[0])) -# 98304 +# 98304 << = 768*128 = hidden_dim * sequence_length>> ``` + An example returning the average embeddings of the tokens looks like this: ```python from deepsparse import Pipeline bert_emb_pipeline = Pipeline.create( task="transformers_embedding_extraction", - model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", + model_path="zoo:nlp/masked_language_modeling/obert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", # emb_extraction_layer=-1, # (default: detect last layer) extraction_strategy="reduce_mean" ) @@ -66,8 +70,9 @@ bert_emb_pipeline = Pipeline.create( input_sequence = "The generalized embedding extraction Pipeline is the best!" embedding = bert_emb_pipeline(input_sequence) print(len(embedding.embeddings[0])) -# 768 +# 768 <<=hidden dim>> ``` + ### Use Case Specific Arguments The Transformers Embedding Extraction Pipeline contains additional arguments for configuring a `Pipeline`. @@ -80,7 +85,7 @@ from deepsparse import Pipeline bert_emb_pipeline = Pipeline.create( task="transformers_embedding_extraction", - model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", + model_path="zoo:nlp/masked_language_modeling/obert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", # emb_extraction_layer=-1, # (default: detect last layer) extraction_strategy="reduce_mean", sequence_length = 64 @@ -91,19 +96,20 @@ embedding = bert_emb_pipeline(input_sequence) print(len(embedding.embeddings[0])) # 768 ``` + Alternatively, you can pass a list of sequence lengths, creating a "bucketable" pipeline. Under the hood, the DeepSparse Pipeline will compile multiple versions of the engine (utilizing a shared scheduler) and direct inputs towards the smallest bucket into which it fits. The example below creates a bucket for smaller input lengths (16 tokens) and for larger input lengths (128 tokens). + ```python from deepsparse import Pipeline -buckets = [16, 128] bert_emb_pipeline = Pipeline.create( task="transformers_embedding_extraction", - model_path="zoo:nlp/masked_language_modeling/bert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", + model_path="zoo:nlp/masked_language_modeling/obert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni", # emb_extraction_layer=-1, # (default: detect last layer) extraction_strategy="reduce_mean", - sequence_length = buckets + sequence_length = [16, 128] ) input_sequence = "The transformers embedding extraction Pipeline is the best!" @@ -112,40 +118,78 @@ print(len(embedding.embeddings[0])) # 768 ``` ### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +Check out the Pipeline User Guide for more details on configuring a Pipeline. + ## DeepSparse Server -DeepSparse Server is built on top of FastAPI and Uvicorn, enabling you to set up a REST endpoint for serving inferences over HTTP. +Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set-up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all of the utilities provided by Pipelines. + +The CLI command below launches an embedding extraction pipeline with an 80% pruned-quantized BERT model identifed by its SparseZoo stub: + +```bash +deepsparse.server \ + --task transformers_embedding_extraction \ + --model_path "zoo:nlp/masked_language_modeling/obert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni" # or path/to/onnx +``` + +You should see Uvicorn report that it is running on `http://0.0.0.0:5543`. Once launched, a `/docs` path is +created with full endpoint descriptions and support for making sample requests. + +Here is an example client request, using the Python `requests` library for formatting the HTTP: +```python +import requests -The HTTP Server is a wrapper around the Pipeline. Therefore, it inherits all of the functionality we described above. It can be launched from the command line with a configuration file. +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"inputs": "The transformers embedding extraction Pipeline is the best!"} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# >> {[[0.022315271198749542,0.02142658829689026, ... ,0.01950429379940033]]} +``` + +### Use Case Specific Arguments + +To use the `sequence_length` and `extraction_strategy` arguments, we can a Server configuration file, passing the arguments via `kwargs` + +This configuration file sets sequence length to 64 and returns all scores: -This configuration file sets sequence length to 64 and `emb_extraction_layer` to -3: ```yaml -# transformer-config.yaml +# config.yaml endpoints: - task: transformers_embedding_extraction - model: zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none + model: zoo:nlp/masked_language_modeling/obert-base/pytorch/huggingface/wikipedia_bookcorpus/pruned80_quant-none-vnni kwargs: - emb_extraction_layer: -3 # (default: detect last layer) sequence_length: 64 # uses sequence length 64 + extraction_strategy: reduce_mean ``` Spin up the server: ```bash -deepsparse.server --config_file transformer-config.yaml +deepsparse.server --config_file config.yaml ``` Making requests: ```python import requests, json -url = "http://0.0.0.0:5543/predict" -obj = { - "inputs": "Mary is flying from Nairobi to New York to attend a conference on generative AI", -} +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"inputs": "The transformers embedding extraction Pipeline is the best!"} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# >> {[[0.022315271198749542,0.02142658829689026, ... ,0.01950429379940033]]} resp = requests.post(url=url, json=obj) result = json.loads(resp.text) print(len(result["embeddings"][0])) -# 49152 +# 768 ``` + ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. \ No newline at end of file +Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file From 05a7f0773e9976a7021279ba23ae838392a49427 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Tue, 18 Apr 2023 13:19:14 -0400 Subject: [PATCH 058/149] updated directory structure --- .../use-cases}/cv/embedding-extraction.md | 0 .../use-cases}/cv/image-classification.md | 0 .../use-cases}/cv/image-segmentation-yolact.md | 0 .../use-cases}/cv/images/result-0.jpg | Bin {use-cases => docs/use-cases}/cv/images/result.jpg | Bin .../use-cases}/cv/object-detection-yolov5.md | 0 .../use-cases}/nlp/question-answering.md | 0 .../use-cases}/nlp/sentiment-analysis.md | 0 .../use-cases}/nlp/text-classification.md | 0 .../use-cases}/nlp/token-classification.md | 0 .../nlp/transformers-embedding-extraction.md | 0 .../use-cases}/nlp/zero-shot-text-classification.md | 0 {user-guide => docs/user-guide}/README.md | 0 .../user-guide}/deepsparse-benchmarking.md | 0 .../user-guide}/deepsparse-pipelines.md | 0 .../user-guide}/deepsparse-server.md | 0 {user-guide => docs/user-guide}/hardware-support.md | 0 {user-guide => docs/user-guide}/installation.md | 0 {user-guide => docs/user-guide}/scheduler.md | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename {use-cases => docs/use-cases}/cv/embedding-extraction.md (100%) rename {use-cases => docs/use-cases}/cv/image-classification.md (100%) rename {use-cases => docs/use-cases}/cv/image-segmentation-yolact.md (100%) rename {use-cases => docs/use-cases}/cv/images/result-0.jpg (100%) rename {use-cases => docs/use-cases}/cv/images/result.jpg (100%) rename {use-cases => docs/use-cases}/cv/object-detection-yolov5.md (100%) rename {use-cases => docs/use-cases}/nlp/question-answering.md (100%) rename {use-cases => docs/use-cases}/nlp/sentiment-analysis.md (100%) rename {use-cases => docs/use-cases}/nlp/text-classification.md (100%) rename {use-cases => docs/use-cases}/nlp/token-classification.md (100%) rename {use-cases => docs/use-cases}/nlp/transformers-embedding-extraction.md (100%) rename {use-cases => docs/use-cases}/nlp/zero-shot-text-classification.md (100%) rename {user-guide => docs/user-guide}/README.md (100%) rename {user-guide => docs/user-guide}/deepsparse-benchmarking.md (100%) rename {user-guide => docs/user-guide}/deepsparse-pipelines.md (100%) rename {user-guide => docs/user-guide}/deepsparse-server.md (100%) rename {user-guide => docs/user-guide}/hardware-support.md (100%) rename {user-guide => docs/user-guide}/installation.md (100%) rename {user-guide => docs/user-guide}/scheduler.md (100%) diff --git a/use-cases/cv/embedding-extraction.md b/docs/use-cases/cv/embedding-extraction.md similarity index 100% rename from use-cases/cv/embedding-extraction.md rename to docs/use-cases/cv/embedding-extraction.md diff --git a/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md similarity index 100% rename from use-cases/cv/image-classification.md rename to docs/use-cases/cv/image-classification.md diff --git a/use-cases/cv/image-segmentation-yolact.md b/docs/use-cases/cv/image-segmentation-yolact.md similarity index 100% rename from use-cases/cv/image-segmentation-yolact.md rename to docs/use-cases/cv/image-segmentation-yolact.md diff --git a/use-cases/cv/images/result-0.jpg b/docs/use-cases/cv/images/result-0.jpg similarity index 100% rename from use-cases/cv/images/result-0.jpg rename to docs/use-cases/cv/images/result-0.jpg diff --git a/use-cases/cv/images/result.jpg b/docs/use-cases/cv/images/result.jpg similarity index 100% rename from use-cases/cv/images/result.jpg rename to docs/use-cases/cv/images/result.jpg diff --git a/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md similarity index 100% rename from use-cases/cv/object-detection-yolov5.md rename to docs/use-cases/cv/object-detection-yolov5.md diff --git a/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md similarity index 100% rename from use-cases/nlp/question-answering.md rename to docs/use-cases/nlp/question-answering.md diff --git a/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md similarity index 100% rename from use-cases/nlp/sentiment-analysis.md rename to docs/use-cases/nlp/sentiment-analysis.md diff --git a/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md similarity index 100% rename from use-cases/nlp/text-classification.md rename to docs/use-cases/nlp/text-classification.md diff --git a/use-cases/nlp/token-classification.md b/docs/use-cases/nlp/token-classification.md similarity index 100% rename from use-cases/nlp/token-classification.md rename to docs/use-cases/nlp/token-classification.md diff --git a/use-cases/nlp/transformers-embedding-extraction.md b/docs/use-cases/nlp/transformers-embedding-extraction.md similarity index 100% rename from use-cases/nlp/transformers-embedding-extraction.md rename to docs/use-cases/nlp/transformers-embedding-extraction.md diff --git a/use-cases/nlp/zero-shot-text-classification.md b/docs/use-cases/nlp/zero-shot-text-classification.md similarity index 100% rename from use-cases/nlp/zero-shot-text-classification.md rename to docs/use-cases/nlp/zero-shot-text-classification.md diff --git a/user-guide/README.md b/docs/user-guide/README.md similarity index 100% rename from user-guide/README.md rename to docs/user-guide/README.md diff --git a/user-guide/deepsparse-benchmarking.md b/docs/user-guide/deepsparse-benchmarking.md similarity index 100% rename from user-guide/deepsparse-benchmarking.md rename to docs/user-guide/deepsparse-benchmarking.md diff --git a/user-guide/deepsparse-pipelines.md b/docs/user-guide/deepsparse-pipelines.md similarity index 100% rename from user-guide/deepsparse-pipelines.md rename to docs/user-guide/deepsparse-pipelines.md diff --git a/user-guide/deepsparse-server.md b/docs/user-guide/deepsparse-server.md similarity index 100% rename from user-guide/deepsparse-server.md rename to docs/user-guide/deepsparse-server.md diff --git a/user-guide/hardware-support.md b/docs/user-guide/hardware-support.md similarity index 100% rename from user-guide/hardware-support.md rename to docs/user-guide/hardware-support.md diff --git a/user-guide/installation.md b/docs/user-guide/installation.md similarity index 100% rename from user-guide/installation.md rename to docs/user-guide/installation.md diff --git a/user-guide/scheduler.md b/docs/user-guide/scheduler.md similarity index 100% rename from user-guide/scheduler.md rename to docs/user-guide/scheduler.md From 588b65813a6ad12a92761ca5f4e54823612fb19b Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Tue, 18 Apr 2023 16:38:51 -0400 Subject: [PATCH 059/149] updated dir structure --- docs/_static/css/nm-theme-adjustment.css | 156 ------------ docs/_templates/versions.html | 27 --- docs/api/.gitkeep | 0 docs/api/deepsparse.rst | 93 -------- docs/api/deepsparse.transformers.rst | 36 --- docs/api/deepsparse.utils.rst | 52 ---- docs/api/modules.rst | 22 -- docs/conf.py | 216 ----------------- .../diagnostics-debugging.md | 191 --------------- docs/debugging-optimizing/example-log.md | 113 --------- docs/debugging-optimizing/index.rst | 25 -- docs/debugging-optimizing/numactl-utility.md | 88 ------- docs/favicon.ico | Bin 15406 -> 0 bytes docs/index.rst | 45 ---- docs/source/c++api-overview.md | 223 ------------------ docs/source/hardware.md | 28 --- docs/source/icon-deepsparse.png | Bin 2029 -> 0 bytes docs/source/multi-stream.png | Bin 31497 -> 0 bytes docs/source/scheduler.md | 61 ----- docs/source/single-stream.png | Bin 17490 -> 0 bytes 20 files changed, 1376 deletions(-) delete mode 100644 docs/_static/css/nm-theme-adjustment.css delete mode 100644 docs/_templates/versions.html delete mode 100644 docs/api/.gitkeep delete mode 100644 docs/api/deepsparse.rst delete mode 100644 docs/api/deepsparse.transformers.rst delete mode 100644 docs/api/deepsparse.utils.rst delete mode 100644 docs/api/modules.rst delete mode 100644 docs/conf.py delete mode 100644 docs/debugging-optimizing/diagnostics-debugging.md delete mode 100644 docs/debugging-optimizing/example-log.md delete mode 100644 docs/debugging-optimizing/index.rst delete mode 100644 docs/debugging-optimizing/numactl-utility.md delete mode 100644 docs/favicon.ico delete mode 100644 docs/index.rst delete mode 100644 docs/source/c++api-overview.md delete mode 100644 docs/source/hardware.md delete mode 100644 docs/source/icon-deepsparse.png delete mode 100644 docs/source/multi-stream.png delete mode 100644 docs/source/scheduler.md delete mode 100644 docs/source/single-stream.png diff --git a/docs/_static/css/nm-theme-adjustment.css b/docs/_static/css/nm-theme-adjustment.css deleted file mode 100644 index ac3c8cacf8..0000000000 --- a/docs/_static/css/nm-theme-adjustment.css +++ /dev/null @@ -1,156 +0,0 @@ -body, -.wy-side-nav-search, -.wy-nav-side, -.wy-nav-content-wrap, -.wy-nav-content, -.wy-nav-shift { - background: #ffffff; - color: rgb(40, 68, 41); - font-family: "Helvetica Neue"; -} - -.wy-nav-content { - max-width: 1280px; - min-height: 100vh; - border-left: solid 1px #000000; -} - -.highlight { - background: #f5f5f5; -} - -a { - color: #062EF5; - font-family: "Helvetica Neue"; -} - -a:visited { - color: #062EF5; - font-family: "Helvetica Neue"; -} - -.wy-side-nav-search { - margin: 0; -} - -#rtd-search-form { - margin-left: 10px; - margin-right: 10px; -} - -.wy-side-nav-search .wy-dropdown>a, -.wy-side-nav-search>a { - color: rgb(40, 68, 41); - font-family: "Helvetica Neue"; -} - -.rst-content .toctree-wrapper>p.caption, h1, h2, h3, h4, h5, h6, legend { - color: rgb(40, 68, 41); - font-family: "Helvetica Neue"; -} - -.wy-side-nav-search .icon-home { - display: flex; - justify-content: center; - flex-direction: row-reverse; - align-items: center; - font-size: 1.4rem; -} - -.wy-side-nav-search>a.icon img.logo { - padding: 0; - margin: 0 8px 0 0; - width: 28px; -} - -.wy-side-nav-search .icon-home::before { - content: ""; -} - -.wy-side-nav-search>div.version { - display: none; -} - -.wy-side-nav-search input[type=text] { - border-radius: 4px; - border: solid 1px #000000; - box-shadow: none; - font-family: "Helvetica Neue"; -} - -.wy-menu-vertical { - border-top: solid 1px #000000; - margin-top: 10px; -} - -.wy-menu-vertical header, -.wy-menu-vertical p.caption { - color: #062EF5; - font-family: "Helvetica Neue"; -} - -.wy-menu-vertical a { - color: rgb(40, 68, 41); - font-family: "Helvetica Neue"; -} - -.wy-menu-vertical a { - background-color: #ffffff !important; -} - -.wy-menu-vertical a:hover { - color: #f6f7fe !important; - background-color: #062EF5 !important; -} - -.wy-menu-vertical li.toctree-l1.current>a { - border-bottom: none; - border-top: none; -} - -.rst-versions, -.rst-versions .rst-current-version .fa-book, -.rst-versions .rst-current-version .fa-caret-down, -.rst-versions .rst-other-versions dt, -.rst-versions .rst-other-versions dd { - background: #ffffff; - color: rgb(40, 68, 41); - font-family: "Helvetica Neue"; -} - -.rst-versions .rst-other-versions dd a { - background: #ffffff; - color: #062EF5; - font-family: "Helvetica Neue"; -} - -.rst-versions { - border-top: solid 1px #000000; -} - -.rst-versions .rst-current-version { - background: #ffffff; - color: #062EF5; - font-family: "Helvetica Neue"; -} - -.btn, .btn.btn-neutral { - background: #ffffff !important; - color: #062EF5 !important; - box-shadow: none; - border: solid 1px #062EF5; - border-radius: 4px; -} - -.btn .fa, .btn.btn-neutral .fa { - color: #062EF5 !important; -} - -.btn:hover, .btn.btn-neutral:hover { - background: #062EF5 !important; - color: #f6f7fe !important; -} - -.btn:hover .fa, .btn.btn-neutral:hover .fa { - color: #f6f7fe !important; -} diff --git a/docs/_templates/versions.html b/docs/_templates/versions.html deleted file mode 100644 index 476c8d1954..0000000000 --- a/docs/_templates/versions.html +++ /dev/null @@ -1,27 +0,0 @@ -{%- if current_version %} -

- - Other Versions - v: {{ current_version.name }} - - -
- {%- if versions.tags %} -
-
Tags
- {%- for item in versions.tags %} -
{{ item.name }}
- {%- endfor %} -
- {%- endif %} - {%- if versions.branches %} -
-
Branches
- {%- for item in versions.branches %} -
{{ item.name }}
- {%- endfor %} -
- {%- endif %} -
-
-{%- endif %} \ No newline at end of file diff --git a/docs/api/.gitkeep b/docs/api/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/api/deepsparse.rst b/docs/api/deepsparse.rst deleted file mode 100644 index aefb9cb97a..0000000000 --- a/docs/api/deepsparse.rst +++ /dev/null @@ -1,93 +0,0 @@ -.. - Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -deepsparse package -================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - deepsparse.transformers - deepsparse.utils - -Submodules ----------- - -deepsparse.benchmark module ---------------------------- - -.. automodule:: deepsparse.benchmark - :members: - :undoc-members: - :show-inheritance: - -deepsparse.cpu module ---------------------- - -.. automodule:: deepsparse.cpu - :members: - :undoc-members: - :show-inheritance: - -deepsparse.engine module ------------------------- - -.. automodule:: deepsparse.engine - :members: - :undoc-members: - :show-inheritance: - -deepsparse.generated\-version module ------------------------------------- - -.. automodule:: deepsparse.generated-version - :members: - :undoc-members: - :show-inheritance: - -deepsparse.generated\_version module ------------------------------------- - -.. automodule:: deepsparse.generated_version - :members: - :undoc-members: - :show-inheritance: - -deepsparse.lib module ---------------------- - -.. automodule:: deepsparse.lib - :members: - :undoc-members: - :show-inheritance: - -deepsparse.version module -------------------------- - -.. automodule:: deepsparse.version - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: deepsparse - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/deepsparse.transformers.rst b/docs/api/deepsparse.transformers.rst deleted file mode 100644 index ff75736e79..0000000000 --- a/docs/api/deepsparse.transformers.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. - Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -deepsparse.transformers package -=============================== - -Submodules ----------- - -deepsparse.transformers.pipelines module ----------------------------------------- - -.. automodule:: deepsparse.transformers.pipelines - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: deepsparse.transformers - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/deepsparse.utils.rst b/docs/api/deepsparse.utils.rst deleted file mode 100644 index 73ddc7e5d5..0000000000 --- a/docs/api/deepsparse.utils.rst +++ /dev/null @@ -1,52 +0,0 @@ -.. - Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -deepsparse.utils package -======================== - -Submodules ----------- - -deepsparse.utils.data module ----------------------------- - -.. automodule:: deepsparse.utils.data - :members: - :undoc-members: - :show-inheritance: - -deepsparse.utils.log module ---------------------------- - -.. automodule:: deepsparse.utils.log - :members: - :undoc-members: - :show-inheritance: - -deepsparse.utils.onnx module ----------------------------- - -.. automodule:: deepsparse.utils.onnx - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: deepsparse.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/modules.rst b/docs/api/modules.rst deleted file mode 100644 index 1871f62e5b..0000000000 --- a/docs/api/modules.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. - Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -deepsparse -========== - -.. toctree:: - :maxdepth: 4 - - deepsparse diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 9e2bf07dfc..0000000000 --- a/docs/conf.py +++ /dev/null @@ -1,216 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - -import os -import sys - -sys.path.insert(0, os.path.abspath("../..")) - - -# -- Project information ----------------------------------------------------- - -project = "DeepSparse" -copyright = ( - "2021 - present / Neuralmagic, Inc. All Rights Reserved. " - "Licensed under the Neural Magic Engine License and Apache License, " - "Version 2.0 as noted." -) -author = "Neural Magic" - -# The full version, including alpha/beta/rc tags -version = "unknown" -version_major_minor = version - -# load and overwrite version and release info from deepsparse package -version_path = os.path.join(os.pardir, "src", "deepsparse", "generated_version.py") -if not os.path.exists(version_path): - version_path = os.path.join(os.pardir, "src", "deepsparse", "version.py") -exec(open(version_path).read()) -release = version -version = version_major_minor -print(f"loaded version {release} from {version_path}") - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.coverage", - "sphinx.ext.extlinks", - "sphinx.ext.githubpages", - "sphinx.ext.ifconfig", - "sphinx.ext.intersphinx", - "sphinx.ext.napoleon", - "sphinx.ext.viewcode", - "sphinx_copybutton", - "sphinx_markdown_tables", - "sphinx_multiversion", - "sphinx_rtd_theme", - "m2r2", -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# Whitelist pattern for tags (set to None to ignore all tags) -smv_tag_whitelist = r'^v.*$' - -# Whitelist pattern for branches (set to None to ignore all branches) -smv_branch_whitelist = r'^main$' - -# Whitelist pattern for remotes (set to None to use local branches only) -smv_remote_whitelist = r'^.*$' - -# Pattern for released versions -smv_released_pattern = r'^tags/v.*$' - -# Format for versioned output directories inside the build directory -smv_outputdir_format = '{ref.name}' - -# Determines whether remote or local git branches/tags are preferred if their output dirs conflict -smv_prefer_remote_refs = False - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -source_suffix = [".rst", ".md"] -# source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" -html_logo = "source/icon-deepsparse.png" - -html_theme_options = { - 'analytics_id': 'UA-128364174-1', # Provided by Google in your dashboard - 'analytics_anonymize_ip': False, -} - - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -html_css_files = ["css/nm-theme-adjustment.css"] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - -html_favicon = "favicon.ico" - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "deepsparsedoc" - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # "papersize": "letterpaper", - # "pointsize": "10pt", - # "preamble": "", - # "figure_align": "htbp", -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, "deepsparse.tex", "DeepSparse Documentation", [author], "manual",), -] - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "deepsparse", "DeepSparse Documentation", [author], 1)] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "deepsparse", - "DeepSparse Documentation", - author, - "deepsparse", - ( - "CPU inference engine that delivers unprecedented " - "performance for sparse models" - ), - "Miscellaneous", - ), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = [] - - -# -- Extension configuration ------------------------------------------------- - -# -- Options for intersphinx extension --------------------------------------- diff --git a/docs/debugging-optimizing/diagnostics-debugging.md b/docs/debugging-optimizing/diagnostics-debugging.md deleted file mode 100644 index 9ea259d1a3..0000000000 --- a/docs/debugging-optimizing/diagnostics-debugging.md +++ /dev/null @@ -1,191 +0,0 @@ - - -# Logging Guidance for Diagnostics and Debugging - -Unlike traditional software, debugging utilities available to the machine learning community are scarce. Complicated with deployment pipeline design issues, model weights, model architecture, and unoptimized models, debugging performance issues can be very dynamic in your data science ecosystem. Reviewing a log file can be your first line of defense in pinpointing performance issues with optimizing your inference. - -The DeepSparse Engine ships with diagnostic logging so you can capture real-time monitoring information at model runtime and self-diagnose issues. If you are seeking technical support, we recommend capturing log information first, as described below. You can decide what to share, whether certain parts of the log or the entire content. - -**Note:** Our logs may reveal your inference network’s macro-architecture, including a general list of operators (such as convolution and pooling) and connections between them. Weights, trained parameters, or dataset parameters will not be captured. Consult Neural Magic’s various legal policies at [https://neuralmagic.com/legal/](https://neuralmagic.com/legal/) which include our privacy statement and software agreements. Your use of the software serves as your consent to these practices. - -## Performance Tuning - -An initial decision point to make in troubleshooting performance issues before enabling logs is whether to prevent threads from migrating from their cores. The default behavior is to disable thread binding (or pinning), allowing your OS to manage the allocation of threads to cores. There is a performance hit associated with this if the DeepSparseEngine is the main process running on your machine. If you want to enable thread binding for the possible performance benefit, set: - -```bash - NM_BIND_THREADS_TO_CORES=1 -``` - -**Note 1:** If the DeepSparse Engine is not the only major process running on your machine, binding threads may hurt performance of the other major process(es) by monopolizing system resources. - -**Note 2:** If you use OpenMP or TBB (Thread Building Blocks) in your application, then enabling thread binding may result in severe performance degradation due to conflicts between Neural Magic thread pool and OpenMP/TBB thread pools. - -## Enabling Logs and Controlling the Amount of Logs Produced by the DeepSparse Engine - -Logs are controlled by setting the `NM_LOGGING_LEVEL` environment variable. - -Specify within your shell one of the following verbosity levels (in increasing order of verbosity: - -`fatal, error, warn,` and `diagnose` with `diagnose` as a common default for all logs that will output to stderr: - -```bash - NM_LOGGING_LEVEL=diagnose - export NM_LOGGING_LEVEL -``` - -Alternatively, you can output the logging level by - -```bash - NM_LOGGING_LEVEL=diagnose -``` - -To enable diagnostic logs on a per-run basis, specify it manually before each script execution. For example, if you normally run: - -```bash - python run_model.py -``` - -Then, to enable diagnostic logs, run: - -```bash - NM_LOGGING_LEVEL=diagnose python run_model.py -``` - -To enable logging for your entire shell instance, execute within your shell: - -```bash - export NM_LOGGING_LEVEL=diagnose -``` - -By default, logs will print out to the stderr of your process. If you would like to output to a file, add `2> .txt` to the end of your command. - -## Parsing an Example Log - -If you want to see an example log with `NM_LOGGING_LEVEL=diagnose`, a [truncated sample output](example-log.md) is provided at the end of this guide. It will show a super_resolution network, where Neural Magic only supports running 70% of it. - -_Different portions of the log are explained below._ - -### Viewing the Whole Graph - -Once a model is in our system, it is parsed to determine what operations it contains. Each operation is made a node and assigned a unique number Its operation type is displayed: - -```bash - Printing GraphViewer torch-jit-export: - Node 0: Conv - Node 1: Relu - Node 2: Conv - Node 3: Relu - Node 4: Conv - Node 5: Relu - Node 6: Conv - Node 7: Reshape - Node 8: Transpose - Node 9: Reshape -``` - -### Finding Supported Nodes for Our Optimized Engine - -After the whole graph is loaded in, nodes are analyzed to determine whether they are supported by our optimized runtime engine. Notable "unsupported" operators are indicated by looking for `Unsupported [type of node]` in the log. For example, this is an unsupported Reshape node that produces a 6D tensor: - -```bash - [nm_ort 7f4fbbd3f740 >DIAGNOSE< unsupported /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/ops.cc:60] Unsupported Reshape , const shape greater than 5D -``` - -### Compiling Each Subgraph - -Once all the nodes are located that are supported within the optimized engine, the graphs are split into maximal subgraphs and each one is compiled. ​To find the start of each subgraph compilation, look for `== Beginning new subgraph ==`. First, the nodes are displayed in the subgraph: ​ - -```bash - Printing subgraph: - Node 0: Conv - Node 1: Relu - Node 2: Conv - Node 3: Relu - Node 4: Conv - Node 5: Relu - Node 6: Conv -``` - -Simplifications are then performed on the graph to get it in an ideal state for complex optimizations, which are logged: - -```bash -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:706] == Translating subgraph NM_Subgraph_1 to NM intake graph. -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:715] ( L1 graph - ( values: - (10 float [ 1, 64, 224, 224 ]) - (11 float [ 1, 64, 224, 224 ]) - (12 float [ 1, 64, 224, 224 ]) - (13 float [ 1, 32, 224, 224 ]) - (14 float [ 1, 32, 224, 224 ]) - (15 float [ 1, 9, 224, 224 ]) - (9 float [ 1, 64, 224, 224 ]) - (conv1.bias float [ 64 ]) - (conv1.weight float [ 64, 1, 5, 5 ]) - (conv2.bias float [ 64 ]) - (conv2.weight float [ 64, 64, 3, 3 ]) - (conv3.bias float [ 32 ]) - (conv3.weight float [ 32, 64, 3, 3 ]) - (conv4.bias float [ 9 ]) - (conv4.weight float [ 9, 32, 3, 3 ]) - (input float [ 1, 1, 224, 224 ]) - ) - ( operations: - (Constant conv1.bias (constant float [ 64 ])) - (Constant conv1.weight (constant float [ 64, 1, 5, 5 ])) - (Constant conv2.bias (constant float [ 64 ])) - (Constant conv2.weight (constant float [ 64, 64, 3, 3 ])) - (Constant conv3.bias (constant float [ 32 ])) - (Constant conv3.weight (constant float [ 32, 64, 3, 3 ])) - (Constant conv4.bias (constant float [ 9 ])) - (Constant conv4.weight (constant float [ 9, 32, 3, 3 ])) - (Input input (io 0)) - (Conv input -> 9 (conv kernel = [ 64, 1, 5, 5 ] bias = [ 64 ] padding = {{2, 2}, {2, 2}} strides = {1, 1})) - (Elementwise 9 -> 10 (calc Relu)) - (Conv 10 -> 11 (conv kernel = [ 64, 64, 3, 3 ] bias = [ 64 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) - (Elementwise 11 -> 12 (calc Relu)) - (Conv 12 -> 13 (conv kernel = [ 32, 64, 3, 3 ] bias = [ 32 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) - (Elementwise 13 -> 14 (calc Relu)) - (Conv 14 -> 15 (conv kernel = [ 9, 32, 3, 3 ] bias = [ 9 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) - (Output 15 (io 0)) - ) -) -``` - -### Determining the Number of Cores and Batch Size - -This log detail describes the batch size and number of cores that Neural Magic is optimizing against. Look for `== Compiling NM_Subgraph` as in this example: - -```bash -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:723] == Compiling NM_Subgraph_1 with batch size 1 using 18 cores. -``` - -### Obtaining Subgraph Statistics - -Locating `== NM Execution Provider supports` shows how many subgraphs we compiled and what percentage of the network we managed to support running: - -```bash -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:122] Created 1 compiled subgraphs. -[nm_ort 7f4fbbd3f740 >DIAGNOSE< validate_minimum_supported_fraction /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/utility/graph_util.cc:321] == NM Execution Provider supports 70% of the network -``` - -### Viewing Runtime Execution Times - -​For each subgraph Neural Magic optimizes, the execution time is reported by `ORT NM EP compute_func:` for each run as follows: - -```bash -​[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:265] ORT NM EP compute_func: 6.478 ms -``` diff --git a/docs/debugging-optimizing/example-log.md b/docs/debugging-optimizing/example-log.md deleted file mode 100644 index 6905766f96..0000000000 --- a/docs/debugging-optimizing/example-log.md +++ /dev/null @@ -1,113 +0,0 @@ - - -# Example Log, Verbose Level = diagnose - -The following is an example log with `NM_LOGGING_LEVEL=diagnose` running a super_resolution network, where we only support running 70% of it. Different portions of the log are explained in [Parsing an Example Log.](diagnostics-debugging.md#parsing-an-example-log) - -```bash -onnx_filename : test-models/cv-resolution/super_resolution/none-bsd300-onnx-repo/model.onnx -[ INFO neuralmagic.py: 112 - neuralmagic_create() ] Construct network from ONNX = test-models/cv-resolution/super_resolution/none-bsd300-onnx-repo/model.onnx -NeuralMagic WAND version: 1.0.0.96ce2f6cb23b8ab377012ed9ef38d3da3b9f5313 (optimized) (system=avx512, binary=avx512) -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:104] == NMExecutionProvider::GetCapability == -Printing GraphViewer torch-jit-export: -Node 0: Conv -Node 1: Relu -Node 2: Conv -Node 3: Relu -Node 4: Conv -Node 5: Relu -Node 6: Conv -Node 7: Reshape -Node 8: Transpose -Node 9: Reshape -​ -[nm_ort 7f4fbbd3f740 >DIAGNOSE< unsupported /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/ops.cc:60] Unsupported Reshape , const shape greater than 5D -[nm_ort 7f4fbbd3f740 >DIAGNOSE< construct_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:595] == Constructing subgraphs from graph info -[nm_ort 7f4fbbd3f740 >WARN< construct_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:604] Cannot support patterns, defaulting to non-pattern-matched subgraphs -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:644] == Beginning new subgraph == -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:667] Runtime inputs for subgraph: -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:679] input (required) -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:684] Printing subgraph: -Node 0: Conv -Node 1: Relu -Node 2: Conv -Node 3: Relu -Node 4: Conv -Node 5: Relu -Node 6: Conv -​ -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:706] == Translating subgraph NM_Subgraph_1 to NM intake graph. -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:715] ( L1 graph - ( values: - (10 float [ 1, 64, 224, 224 ]) - (11 float [ 1, 64, 224, 224 ]) - (12 float [ 1, 64, 224, 224 ]) - (13 float [ 1, 32, 224, 224 ]) - (14 float [ 1, 32, 224, 224 ]) - (15 float [ 1, 9, 224, 224 ]) - (9 float [ 1, 64, 224, 224 ]) - (conv1.bias float [ 64 ]) - (conv1.weight float [ 64, 1, 5, 5 ]) - (conv2.bias float [ 64 ]) - (conv2.weight float [ 64, 64, 3, 3 ]) - (conv3.bias float [ 32 ]) - (conv3.weight float [ 32, 64, 3, 3 ]) - (conv4.bias float [ 9 ]) - (conv4.weight float [ 9, 32, 3, 3 ]) - (input float [ 1, 1, 224, 224 ]) - ) - ( operations: - (Constant conv1.bias (constant float [ 64 ])) - (Constant conv1.weight (constant float [ 64, 1, 5, 5 ])) - (Constant conv2.bias (constant float [ 64 ])) - (Constant conv2.weight (constant float [ 64, 64, 3, 3 ])) - (Constant conv3.bias (constant float [ 32 ])) - (Constant conv3.weight (constant float [ 32, 64, 3, 3 ])) - (Constant conv4.bias (constant float [ 9 ])) - (Constant conv4.weight (constant float [ 9, 32, 3, 3 ])) - (Input input (io 0)) - (Conv input -> 9 (conv kernel = [ 64, 1, 5, 5 ] bias = [ 64 ] padding = {{2, 2}, {2, 2}} strides = {1, 1})) - (Elementwise 9 -> 10 (calc Relu)) - (Conv 10 -> 11 (conv kernel = [ 64, 64, 3, 3 ] bias = [ 64 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) - (Elementwise 11 -> 12 (calc Relu)) - (Conv 12 -> 13 (conv kernel = [ 32, 64, 3, 3 ] bias = [ 32 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) - (Elementwise 13 -> 14 (calc Relu)) - (Conv 14 -> 15 (conv kernel = [ 9, 32, 3, 3 ] bias = [ 9 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) - (Output 15 (io 0)) - ) -) -​ -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:723] == Compiling NM_Subgraph_1 with batch size 1 using 18 cores. -[7f4fbbd3f740 >DIAGNOSE< allocate_buffers_pass ./src/include/wand/engine/units/planner.hpp:49] compiler: total buffer size = 25690112/33918976, ratio = 0.757396 -[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:644] == Beginning new subgraph == -[nm_ort 7f4fbbd3f740 >WARN< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:652] Filtered subgraph was empty, ignoring subgraph. -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:122] Created 1 compiled subgraphs. -[nm_ort 7f4fbbd3f740 >DIAGNOSE< validate_minimum_supported_fraction /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/utility/graph_util.cc:321] == NM Execution Provider supports 70% of the network -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:129] == End NMExecutionProvider::GetCapability == -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:140] == NMExecutionProvider::Compile == -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:157] Graph #0: 1 inputs and 1 outputs -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:276] == End NMExecutionProvider::Compile == -Generating 1 random inputs - -- 1 random input of shape = [1, 1, 224, 224] -[ INFO execute.py: 242 - nm_exec_test_iters() ] Starting tests -[ INFO neuralmagic.py: 121 - neuralmagic_execute() ] Executing TEST_1 -[ INFO neuralmagic.py: 124 - neuralmagic_execute() ] [1] input_data.shape = (1, 1, 224, 224) -[ INFO neuralmagic.py: 126 - neuralmagic_execute() ] -- START -[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:265] ORT NM EP compute_func: 6.478 ms -[ INFO neuralmagic.py: 130 - neuralmagic_execute() ] -- FINISH -[ INFO neuralmagic.py: 132 - neuralmagic_execute() ] [output] output_data.shape = (1, 1, 672, 672) -``` diff --git a/docs/debugging-optimizing/index.rst b/docs/debugging-optimizing/index.rst deleted file mode 100644 index 1491e91049..0000000000 --- a/docs/debugging-optimizing/index.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. - Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -======================== -Debugging and Optimizing -======================== - -.. toctree:: - :maxdepth: 3 - - numactl-utility - diagnostics-debugging - example-log diff --git a/docs/debugging-optimizing/numactl-utility.md b/docs/debugging-optimizing/numactl-utility.md deleted file mode 100644 index b5f711cc88..0000000000 --- a/docs/debugging-optimizing/numactl-utility.md +++ /dev/null @@ -1,88 +0,0 @@ - - -# Using the numactl Utility to Control Resource Utilization with the DeepSparse Engine - -The DeepSparse Engine achieves better performance on multiple-socket systems as well as with hyperthreading disabled; models with larger batch sizes are likely to see an improvement. One standard way of controlling compute/memory resources when running processes is to use the **numactl** utility. **numactl** can be used when multiple processes need to run on the same hardware but require their own CPU/memory resources to run optimally. - -To run the DeepSparse Engine on a single socket (N) of a multi-socket system, you would start the DeepSparse Engine using **numactl**. For example: - -```bash - numactl --cpunodebind N -``` - -To run the DeepSparse Engine on multiple sockets (N,M), run: - -```bash - numactl --cpunodebind N,M -``` - -It is advised to also allocate memory from the same socket on which the engine is running. So, `--membind` or `--preferred` should be used when using `--cpunodebind.` For example: - -```bash - numactl --cpunodebind N --preferred N - or - numactl --cpunodebind N --membind N -``` - -The difference between `--membind` and `--preferred` is that `--preferred` allows memory from other sockets to be allocated if the current socket is out of memory. `--membind` does not allow memory to be allocated outside the specified socket. - -For more fine-grained control, **numactl** can be used to bind the process running the DeepSparse Engine to a set of specific CPUs using `--physcpubind`. CPUs are numbered from 0-N, where N is the maximum number of logical cores available on the system. On systems with hyper-threading (or SMT), there may be more than one logical thread per physical CPU. Usually, the logical CPUs/threads are numbered after all the physical CPUs/threads. For example, in a system with two threads per CPU and N physical CPUs, the threads for a particular CPU (K) will be K and K+N for all 0<=K<N. The DeepSparse Engine currently works best with hyper-threading/SMT disabled, so only one set of threads should be selected using **numactl**, i.e., 0 through (N-1) or N through (N-1). - -Similarly, for a multi-socket system with N sockets and C physical CPUs per socket, the CPUs located on a single socket will range from K*C to ((K+1)*C)-1 where 0<=K<N. For multi-socket, multi-thread systems, the logical threads are separated by N*C. For example, for a two socket, two thread per CPU system with 8 cores per CPU, the logical threads for socket 0 would be numbered 0-7 and 16-23, and the threads for socket 1 would be numbered 8-15 and 24-31. - -Given the architecture above, to run the DeepSparse Engine on the first four CPUs on the second socket, you would use the following: - -```bash - numactl --physcpubind 8-11 --preferred 1 -``` - -Appending `--preferred 1` is needed here since the DeepSparse Engine is being bound to CPUs on the second socket. - -Note: When running on multiple sockets using a batch size that is evenly divisible by the number of sockets will yield the best performance. - - -## DeepSparse Engine and Thread Pinning - -When using **numactl** to specify which CPUs/sockets the engine is allowed to run on, there is no restriction as to which CPU a particular computation thread is executed on. A single thread of computation may run on one or more CPUs during the course of execution. This is desirable if the system is being shared between multiple processes so that idle CPU threads are not prevented from doing other work. - -However, the engine works best when threads are pinned (i.e., not allowed to migrate from one CPU to another). Thread pinning can be enabled using the `NM_BIND_THREADS_TO_CORES` environment variable. For example: - -```bash - NM_BIND_THREADS_TO_CORES=1 - or - export NM_BIND_THREADS_TO_CORES=1 -``` - -`NM_BIND_THREADS_TO_CORES` should be used with care since it forces the DeepSparse Engine to run on only the threads it has been allocated at startup. If any other process ends up running on the same threads, it could result in a major degradation of performance. - -**Note:** The threads-to-cores mappings described above are specific to Intel only. AMD has a different mapping. For AMD, all the threads for a single core are consecutive, i.e., if each core has two threads and there are N cores, the threads for a particular core K are 2*K and 2*K+1. The mapping of cores to sockets is also straightforward, for a N socket system with C cores per socket, the cores for a particular socket S are numbered S*C to ((S+1)*C)-1. - -## Additional Notes - -`numactl --hardware` - -Displays the inventory of available sockets/CPUs on a system. - -`numactl --show` - -Displays the resources available to the current process. - -For further details about these and other parameters, see the man page on **numactl**: - -```bash - man numactl -``` diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index 784590876f3eda97e984282a4bf2e688030c6a99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeI0d9apM8NfflaK%s&H}GC%k<0~S5g6p6s3dBoW{OOBHB-yfG(<&eM5fVjEXpON zV1Ge>iSKtM2Y2^B1X1hV@5Oki}ybQlujVR%_`BKbzR3D&_0ke9~N@d4Zbo8bcZ3;Y)xZ+=Z{VS|JCsdq~o36 zdEY7bEh7H{{Ht7lom~5EcWgXfA6^FQ%kA3E&H5w_wD5DMTseY=1hk84f?n4um< zgYl`#vdJ>#2byBv;y6>txv`hDZm``;4(@5uNjNYD5`X*&hR!CG(+ybEoiUtP}S8TZ|A z?wB{I9|;%2#;C7W{m$$CxdHs}$hxLc@D9ZD zSbhk4!TlM1o{dZ4M2O>XOY{$C?08rQv2ORnGhPQzC4I_mfb-xA_$IhF`pLdL{dbo8 zgC2EX4gL-aZRVcw@4%rDzpFvU_ih~A4B9Dk;2>}x9h>Fo<;rWyQ+WF{wHV~3Vv_*huM&xN9*^&0}zk*{-^7Y z`@u&x?1-JM$QXc|z`9-C1t;jGqP#;5ToO3HdVGoF^-7N8{y?#)~laarzDc zV}Le=_Mm?iZNtEGvp(t%a!=B>FH}m}_h$&>;@mxOF9yT&Wf}Dw7y80fNYA+A?}u(s zZQ23B>%emo>$mJ4Jp|rU&+k!SGG3?`nEM`1cMeqYJ>IFFcvz4Nt>^Q@Lrf- z2%hm;l771dQorWy=MZEcgA<@F)90D&b)x8gPr1pn)zay>$*Wi1!9D?72jP{=ovHkWrzuy$$jiOj{s$+%+VG{)X z=6UwpHxKrNMu^9Ra-RJgk)H%@VZVECm~`(`|5dVo0b_bXBLth{-1DF1|F*LKQ_O!V zEX>irE!*$joeQfV%kRODMdxP$W4DI?yQ6ms#P>AVo95e^{l;!%uy0CgYgWA)`_IXl z%k%GdSsH&@d;WVd*Bq$E?_n+5g8jaALm+)$f{o^RcVKJV?>#*Uo&(>$*yd2)O#9=I z?z8>1*nbw|YdJs0N8j(aAg6zc;qbgbyILH&~9c91!{W5$74ul`UdPr@yuI#IDHfXzX@?FT1EwcYH#?(X6 z{jKKwXwRQuF!Y9BL(zB4Qu>brzcufGqIbaG82uqD{YfkAm(Rc-A>6&xpMDFt z&cmP&{1)TiC~OLZ4Pz!<6h_oL*NhKdP0Ai`x|@` z41#(%9iD{vZr5VJwtIH{pOJH~gw#L5CgZs4+#8Mo&#P^rKh58!?=)zDXJ7%0g#E!- z^&vQ>8vDOimQ8hNe>b=UitefB*86EZ8V}l*&K2se$@jMbjw+W!pE1~Y-4oQk2SJHPb>;awyV{0lC>Yj^7paCXA)_LDR`+YD12EzGJ z2U+%e4~y(yLBI0_{b|08z6S8j+z#s?l?iqIei!TmLt!z5zBE^MDd^W7pck~p{&e0@ z-^_fj{SfF4&9DhVf0}!ieJc(C&xQF&@UHs(pUQ{2dYWJ~xSr4+@=E>>a&hjQvq9U9 zF%N-lu}oa{&6*6_doO73eE1T$j%n}~#AD*Jd$9<@eUAHDYX3*fGXwU7G4L{YrsFx| z@(TJ+0nhO~2?}oy@ za0e8{nDu3u9^`3q-+l&bq0&0s+jC$q@LgF3d3%v(zw0(uxc`H|7*lk&-lTsr`2OZ4 zzd097cWR8bU(30f;d;F$a2}QnfJsZHgZ~*w`couB4 zFSdUvb=PWq{1$j7Vt>SCePfJqe_ESd^KkGj^zOv-r)Bj$31>iGxB`|#u-~_6EO>su z4{IQuBh-yazX$!H4Yef4_kfGxJqSATa%EH$(P=*v&VscN_TnAN-WAtaDRn`> qGdTz>Z;j;naNj-eFO|#H*pBEnlk{C5=m%TNkK2qh{%=Uzd*FZjq!cdz diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 1df368ea01..0000000000 --- a/docs/index.rst +++ /dev/null @@ -1,45 +0,0 @@ -.. - Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -.. mdinclude:: ../README.md - -.. toctree:: - :maxdepth: 3 - :caption: General - - source/hardware - -.. toctree:: - :maxdepth: 3 - :caption: Performance - - debugging-optimizing/index - source/scheduler - -.. toctree:: - :maxdepth: 2 - :caption: API - - source/c++api-overview - api/deepsparse - -.. toctree:: - :maxdepth: 3 - :caption: Connect Online - - Bugs, Feature Requests - Deep Sparse Community Slack - Neural Magic GitHub - Neural Magic Docs diff --git a/docs/source/c++api-overview.md b/docs/source/c++api-overview.md deleted file mode 100644 index 2143124a53..0000000000 --- a/docs/source/c++api-overview.md +++ /dev/null @@ -1,223 +0,0 @@ - - -# C++ API Overview - -You can use a C++ API in `libdeepsparse.so` as the interface between your application and the [Neural Magic DeepSparse Engine.](https://docs.neuralmagic.com/deepsparse/) You would start with a model that has been exported in ONNX format, client code on your server. Your application will write data to input tensors, execute the DeepSparse Engine for inference results, and read output tensors for result data. - -A simple demo with code is provided to invoke the DeepSparse Engine using the C++ API. Once you have installed the DeepSparse Engine, you will be ready to use the C++ API and take advantage of the library `libdeepsparse`. With `libdeepsparse`, you can run the demo by building and running `demo.bin`. - -You can find more information about the engine at [https://docs.neuralmagic.com/deepsparse/.](https://docs.neuralmagic.com/deepsparse/) - - -## Prerequisites - -The following is required to build and run the demo. - -**OS** - Our engine binary is manylinux2014-compatible and built on CentOS 7. Linux distributions such as Ubuntu 20.04, which are compatible with manylinux2014, should support it. - - -**Hardware** - The utility arch.bin is included in deepsparse_api_demo.tar.gz. Run `arch.bin` to check hardware support. The “isa” field in the output states whether you are running on a machine that supports the avx512 or avx2 instruction set. - -``` -./arch.bin -``` - -**Installed Tools** - These tools are assumed to be installed to build the demo: - -* clang++ 11 or g++ 10 -* C++17 standard libraries - - -## Demo: Obtaining, Building, and Running - -To download a demo file, use the curl command: - - -``` -curl https://github.com/neuralmagic/deepsparse/releases/download/v0.8.0/deepsparse_api_demo.tar.gz --output deepsparse_api_demo.tar.gz -``` -or visit our [DeepSparse Engine Release page](https://github.com/neuralmagic/deepsparse/releases/tag/v0.8.0) and download `deepsparse_api_demo.tar.gz` from there. - - -Once you obtain the file deepsparse-api.tar.gz, follow these steps to unpack and build the demo: - should be either avx2 or avx512 based on the result of arch.bin. - -``` -tar xzvf deepsparse_api_demo.tar.gz -cd _deepsarse_api_demo -make -./bin//demo.bin ./data/model.onnx -``` - -**Note**. The makefile defaults to avx512 support. For tar files and machines with avx2 instruction set, to build, you must set the ARCH flag on the command line when you make the demo. - -``` -make ARCH=avx2 -``` - -## C++ API - -This document discusses the high-level overview of the API. For the exact signatures and classes of the API, review the header files under - -``` -deepsparse_api_demo/include/libdeepsparse/ -``` - -The API consists of five C++ header files: - -``` -compiler.hpp -config.hpp -dimensions.hpp -tensor.hpp -engine.hpp -``` - -### Compiler - -Helper header to export the API to a shared object. - -### Config - -This file contains a structure that is used in the call to create the engine. The **engine_config_t** fields are: - -**model_file_path** - This should be a file path to the model in the ONNX file format. See [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) on proper ONNX model files. - -**batch_size** - The batch size refers to the process of concatenating input and output tensors into a contiguous batched tensor. See [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) about the performance trade-offs of batching. - -**num_threads** - The number of worker threads for the engine to use while executing a model. If left as 0, the engine will select a default number of worker threads. - - -### Dimensions - -Review the [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) about expected input and output tensors. The **dimensions_t** object describes the extent or count of elements along each dimension of a tensor_t. - - -### Tensor - -A tensor is an n-dimensional array of data elements and metadata. An element is a concrete value of a supported primitive type (for example, an element of type float or uint8). - -**tensor_t** - members. - -**element_type()** - Return an enumeration value of the concrete type of a tensor element. - -**dims()** - Return a dimension_t that specifies the extents of the tensor. - -**data()** - Pointer to the first element of the tensor data memory. - -The data that tensor_t points to may have either of two different lifetime models for the memory. - -1. The tensor owns the data memory. -2. The tensor aliases a pointer to the memory location. The lifetime of the data memory is delegated to a lambda passed to the tensor. For externally-owned data pointers, the lambda can be a no-op. - -_Code Example: Memory lifetime allocated and owned by tensor_t_ - - -``` -#include "deepsparse/engine.hpp" - -void my_engine_inference() -{ - // ... - { - // create a float tensor with y and x 10 by 10 - auto t = deepsparse::create_tensor( - deepsparse::element_type_t::float32, - deepsparse::dimensions_t{1, 1, 1, 10, 10}); - - float* p_raw = t.data(); // 100 floats - // read and write values to p_raw and send tensor to the engine. - - - } - // data memory of t deallocated when t goes out of scope - // ... -} -``` - -_Code Example: Tensor data memory lifetime is delegated to lambda_ - -``` -#include -#include "deepsparse/engine.hpp" - -void my_engine_inference() -{ - // tensor data memory MUST aligned. - // dims must match total number of elements below - float* p_raw = static_cast(aligned_alloc( - deepsparse::minimum_required_alignment(), - sizeof(float) * 100)); - { - - // policy - tensor is just an alias to memory - auto alias_dealloc = [](void* p) {}; - - // policy - tensor destructor calls dealloc - auto owned_dealloc = [](void* p) { - free(p); - // or cast p to pointer to well known type - // and manually call your own delete - }; - - // create a alias float tensor with y and x 10 by 10 - auto t = deepsparse::tensor_t( - deepsparse::element_type_t::float32, - deepsparse::dimensions_t{1, 1, 1, 10, 10}, - p_raw, - alias_dealloc // lambda invoked in object destructor - ); - - // read and write p_raw and send to the engine. - } - -} -``` - -### Engine - -The engine API is the primary interface for external code to load and run the [Neural Magic DeepSparse Engine](https://docs.neuralmagic.com/deepsparse/). - -**engine_t()** - Construct an instance of the engine with the configuration struct. The config file path to the model is used to load and compile the model during the constructor. On error, exceptions will be thrown to the calling code. - -**execute()** - This is the primary method to run the inference specified in the ONNX model. The engine executes the model with the input tensors and returns output tensors. - -**Input and output getters** - these methods are used to get metadata on the input and output tensors of the loaded model. - - -#### Utility Functions - -**generate_random_inputs()** - Once the engine is instantiated and has a model loaded, this function can use the model’s definition of input tensors to generate a set of tensors with random values with the correct type and shape. Random input can be used to test the engine or its performance. - -**load_inputs()** - Creates a collection of tensor_t from the model input files. The input files are expected to be in the NumPy array format. See the [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) for more on input file support. - -**max_diff()** - Returns the largest absolute elementwise difference between two tensors. This function is used to compare the output result with the expected output when testing the engine. - -**allclose()** - Returns true if two tensors are less elementwise different than the specified absolute and relative tolerances. - - -### User Calling Code - -The expected workflow is that the calling code will create one engine per model. The model path will specify an ONNX file. During creation, the engine will load and compile the model from the file. - -The input tensors will be created by the calling code. The tensor dimensions and types must match the corresponding dimensions and types of the model inputs. And, the number of tensors must be the same as the number of model inputs. - -Call engine execute() with the collection of input tensors. During the execute() call, the engine will do the inference over the ONNX model with the input tensors and return the output tensors. - -The output tensors’ data members can then be read to extract values and results. - -For a detailed example of creating, loading, and running an engine, see the code in `deepsparse_api_demo/src/demo.cpp` diff --git a/docs/source/hardware.md b/docs/source/hardware.md deleted file mode 100644 index 7d79a3af1a..0000000000 --- a/docs/source/hardware.md +++ /dev/null @@ -1,28 +0,0 @@ - - -## Hardware Support - -With support for AVX2, AVX-512, and VNNI instruction sets, the DeepSparse Engine is validated to work on x86 Intel (Haswell generation and later) and AMD CPUs running Linux. Mac and Windows require running Linux in a Docker or virtual machine. - -Here is a table detailing specific support for some algorithms over different microarchitectures: - -| x86 Extension | Microarchitectures | Activation Sparsity | Kernel Sparsity | Sparse Quantization | -|:----------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------------:|:---------------:|:-------------------:| -| [AMD AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX2) | [Zen 2,](https://en.wikipedia.org/wiki/Zen_2) [Zen 3](https://en.wikipedia.org/wiki/Zen_3) | not supported | optimized | not supported | -| [Intel AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX2) | [Haswell,](https://en.wikipedia.org/wiki/Haswell_%28microarchitecture%29) [Broadwell,](https://en.wikipedia.org/wiki/Broadwell_%28microarchitecture%29) and newer | not supported | optimized | not supported | -| [Intel AVX-512](https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512) | [Skylake](https://en.wikipedia.org/wiki/Skylake_%28microarchitecture%29) [Cannon Lake,](https://en.wikipedia.org/wiki/Cannon_Lake_%28microarchitecture%29) and newer | optimized | optimized | emulated | -| [Intel AVX-512](https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512) VNNI (DL Boost) | [Cascade Lake.](https://en.wikipedia.org/wiki/Cascade_Lake_%28microarchitecture%29) [Ice Lake,](https://en.wikipedia.org/wiki/Ice_Lake_%28microprocessor%29) [Cooper Lake,](https://en.wikipedia.org/wiki/Cooper_Lake_%28microarchitecture%29) [Tiger Lake](https://en.wikipedia.org/wiki/Tiger_Lake_%28microprocessor%29) | optimized | optimized | optimized | diff --git a/docs/source/icon-deepsparse.png b/docs/source/icon-deepsparse.png deleted file mode 100644 index 5e0580f1e5423a969364204df0f0323de3f04531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2029 zcmY*adpwl+8h>X7<1#~5Xh_RgMr1B-X%3?qQsgr0)*yDq{W>#i24j(2!fHra6|u(3 zB^9kgp~BQA#w9BumYg=1(?*%*xH~g)>^Z;p^SsaZ_q^Zl_xU})f8HO-F2~{W8u9=D zz#UH5xrwu$ICN!Y#BY^n`~%_yV!0hB0d;-*=fs=V0rhkH=i6lW)VAR`)03t|KWqdCz`i3+gd;KeXHm`&5> zL`TtCc#bu4n}HW&Ng0FG-lni4t&tRGvbHTFCRp1XZHC4o3G&+7+Ey_^A$T`C`yIJ> zXN?SFvzd4dCO$qM9dC+e#DrpSM~)o9U`;S4CdOihF)M-2rg4nvEZr|A|Mjs8W(CHC zGuhz`y0*lZ7Ql#QTO*N@M!W0FIoaVMe|Ms@cE%D9h>?83;LupiuD4ifC8^?FW5R>Q zohAMRoYgk-|7tsZtT2-1|8tlxlWteVqY~t;FuT`Akay}tc>{oSse>KSgCf^hn8@{W z-<##@krknAM)R3|;Z{{%VRv)Pu{w;~e4RU+&oR%xN&1d9)jN^>NIA`nepqf zK++cUG(^3FFrxJOji#kyUe?P2kmF=Z>OX5QrP>`;3s*TU|2s{c;)@qecrx__sc{tO z)hr%)fiDi@gNuDs}=y2^cs=+*u@ki!Jm4S#Ei^_dn;V!hgwyA(sen&DAq*Z7K zF>$9x3<=PWaX79^1rpmzFT-p3pW`3(PQg7l7`tn(51&b}Ig|?Jsgcf{!m+A+CLq_^ z-Pr~d8=XiJ%C{q00~ASo@I&)+9DEVMKHDhLnO$(Y4~GdA2AVfdlcR9GA7WG)Z=ICG z`i2q=Z36lB2T|t^`#oOaO+HB?y}Ehk_`n%L=y~^RnwQLb0|b9QYy#`2ch~7a-}kiW ziOffrlp^)l@~itou3w?QVcdbwU+Vp}GDyqER%Qb-E=zB%W*>%`87L&~tDz9fPp!TJ zdo9smcKKtEq)Ls>Tb7LquCCz_^qKytQqdk*rnL<1qUI24%Hc=Bi$g}G4-7HB&Cyy0 z3pG7Ft!ZIl&pz7*?pzNSLDo&8R{4~JhS0OlO@g$D^c&tc$%~rdnzs-hgStF zCwA{VJOXmH(z4HflaaGF7bsIa;gcQh=e$ND=iR(_F7Nx)RdNWYmUttScR)Wa1%k^C z@qXG|W{Pa6?6vuPkqHxesXbbi&76Bu5O`0iGZ_np2@)#?4Fe+uPU{bs7V+z%fFAayV#&AI(9f!e<4j@+0S&spwt>|my3+4j;vaJChqdsSoH1ch zQSVgT^2~hck}*a7z?uQQxCm)rY6@!N+b^2Yr5RCMDC;6Lz9~CI36Zt5eBnj@g#l9I zWp<3IS-Nj+$p{ZRaA_I!m*%qK9lLL3SY2G|Fpm8PX+`j$b7oq)zsB}_ z9O6lQrHPhB`YvWtYwp5W@jr2l;dwa`kxnW|MNvNYHe?w{T6Fm{w#c`+6CTk058MvuL{wR6~3R}s8e09V;yVi zdG0wo0{E?Ha1=86x3SV}g1iO)hsEnYtnTVqOGPhYP|uVVkj1|{^@{~DSH;t)^j$sl z!lP7k5|mm)hpio=YIy17ZNsFAvno2f(1 z4AwgYMVT*!bZL|-)bqhz;OS9RL*EdE^TZwj?;h=~CHj{{q3^f`>ywPl^egjXz1mcU z-|WA$=qmMc2BbQ!CO^^iH0m#>I|V_ReOV})vKg_fJ+X$PXD#DF%Z5x@^hudgH#n|DuvCmc`cHEp2IO0yE+mz?LR4|NaFwi diff --git a/docs/source/multi-stream.png b/docs/source/multi-stream.png deleted file mode 100644 index 769133fb0d5d903506488ec6fbb584813cf27bc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31497 zcmZ^~19&FS(l;F2wry?9jcwbuZQIGlwz1jRwz0A8Y@F}j|2gM*&-+~8d-pXnb@z02 zcU5=KR9F2bQbA4}9tH;n2nYyXQbI%t2naXbXA7>GtS9J8#nia;m`BM41;oRJYIZUk0R^k%sA zW+bhL)d#I+OI>YD+u(evAd3}&S!-05oO2lqQG&K?!fn3e-_8O78z^Wc2T(w!n(E1O zOagg&J2^8B22gfzy`&L8P^8HA@}H*#_V*Fk)9l}Embc&HcBhZnkn3*;l?w~c1}6pw zXxsZef>(>?l?FRvU@7vLr!#cymy45-_q2`lQbOo%aU0}Q7Fl{5Ubbwwq`5h48z(i4 zvMtZ)n;n0tpx5xNA?k8@+NCWmV2hwJPZ9wWV5=yKKy$yRm@2W6o$^;7tGO=YRZv-5 zH4vTpMBt6zGHc@9-}VVQ;2HRRJvEf5A_R`P_v%?&X5#M{p`Q3#SZ;ylr+0GqsDPU%|4o7)sY4`<611j?ca|w;fyT4LoJ#v@K|HW zT?H<3BlUnhJq6Bzbp3jW7vGePrz7NE*5LO8wqAB$1eZ?81dLLnsfMJPtSk@>fQABs z1V#Y@1yI0%1O$u&1pW^V1Y`t^`#-c5Fx9`yfB?!c0|CxJ@PNY!nB^ZD4L}EkTLOXo zn>Ph;{ri&u9DrK?brqMCRUl$vU}j=q<^%#_VPawCW@6)J;Ur@I#?8jW&B6`@1dTdjJFd$j181XQ`s$q9H58ZESDLU}$1* zWXj-S>+qKgh}VM~K-!wR7!rBd+SoaBd+?F`Q-T{n|IKD3A^N9?i!~pK2B3?=_D-fm z-x$~!m`M0xh=_=IolMNQl|;n;%?|j-M`GdP;=s+w=$;Lgfm?_|!%%*DmU$i%|P z!a@%yLGSEo=VItVZ|6+19C(cgLvjqF`r_((|pHuOKgfA!PF z((M1VWas>Eu>b-x{=LJ<%)rF>|Ho$PVfp`H`+MjA$p+BT|A_T}HRfUH@Gqu+l<}`V z^8W1sx4e_3DL~4@u78WG+F82rv;3Roe`Ws{*WV6v z3)|b+J1ILD8k_!Ylm!u*p9m@)h?0M7r+K7N?EAGPy9 zK!QM$B0?%2z!$oZ1}dVc1QLk!4>Y2JUe}_6mP%+yDuQT8B12)l@`}i4Drkx}`BV`k zMDwCZU=<{?38fEPJjBI`f}bx(ri_f~2UognWbTHT4A))sO&v|wQy0^d(`J$Tg4`Bx z|4D5Tfz=F0D{J2%*#4csKz{_DU zdhv4OC$!fSon|BFMCHOI6Hw>N^YiM^nRqO4EU>!)P_txs^N`?jb=(S;W;43lIzm8L zEpt8~C1^75xPUvo+?jH_oG`t-K95V0HIek^eDqsgF4t(M@QJh2=y&sS?eASxYY)t0 zwP`asoGnswdmo|I-L<$3<`^TqqN6%T!=TZLkV~gIL9J7RYBrisKVH1B@h#M0=J2?0 zA&rqWxq@X$UcDNc3>s_Z&}%d4w!0*%eRQWy=LyJz!=O2ELUAhsgza^Oi}Ce`^X0m= zKAUgh*o1;Dseb>!&RSnwW>+9{y{38Rp+2|L%}o%1t)h;g?EXouUi-V*;l;~pb4Tw7 zt35F|7+C45U>O|Q!@BF)o0U5byAM&Q-oDVCVWx)F2-%RCNgPtMY~AXr#&{-ch!%fm>K6X0 z3>kGC&P7Tf7&wH|sgh|q6Qg-ha8O3ziI5a>0WT1et|55;d$!eLb?J~gHHkE(1>A3J z2p9~S@1jv?1^uDH)GAKfpQcTJ@Z88k4hM=qcQBc z%4R9Ws}<;Xg52IuOnbnWjow&K#^XDil)-^b1)^|TtBsDL_<7|gA*A#pnlxx)nRj=8 zO0u3}{hcQqh5&Ntta)vnP()J@M?CkArv3tKKgUWZjW z9&PTIXU%EmOWgQ!{p{M<=&%ohU>S zyX~UFf7VkcCSW;vT(6#{S^u-%+x*MKzTy7Y^aDpma3O~{$sR6Mcbebr$MSH4# z(kmfX8soLBmVhRT2_bG|_w!)Rww{+!_jW0k-#@ah71iFe${-dRe%lhbJ{RHwShcanl&xt^>`#mpzh-!d>@Q394{y z&s;F8V6TRF4{4#6QFV2M{u>ZAO3l#q$o8}J!YBn6u|m=rtrG2ooDs9QDha7Qp6^I# zo1l+ihY~8ezA%NncOV2#t3TmS`8macVR21k*>P+^FInc~Qp~+zI0M3+B*gJ@75TsQ zVIOj69iTNnj-!%N(}IJ8M+FOcc)~%!)iX!$><}3uDJm)|c_Z;ysg(WMje6L)2!}mY zipvBpl7JF`=@pNzgjF|I2ppsXfkc4c9XU69ki{Mqv1( zWzfd@^mNnhaT>ESG;m0W2yjsc!6MSC3cH>><)+Y(IJyBv|xe^FE+}=dTt&)Q{^_4vX;>0fh>X z)RV2CRLk+=Yw(I+-<{@LYk0~nbCy+FCBD%l)`8UMHwj@y?X3$;Wl$a_e7-!_ot!ev z%n!qBQ;APsJZzQ6TbwdZK+6C}fU}&dSc!!r;O({tmykE@Poz`pq!L)oC(7mV4uG06 zRoovlC|uXhAkW{>sy9>EZg$2Z28_#LGU}CzZ@88D@#zua?bA$JL!p=&kRIQqN*Y2A+~mGX6J?MAw?v36J{3+%dq zD`rucR%lrYyqbQ}Ma4!UdwIOM;n8Qa=2)db=X3wAJI=KdE|bSk_M4Cz1ot5~U^wLY z$%@o0PoHJ>#Tk!4$Svh9Z&OC@KUuPK(bunoaKR&TNpK-!AMcx7JDgqWDQz~pAI~fM z_QfOkB78Tu7btO?|7vaIbmwup zC;BvwP5#?`EeZW$-1)1BPyFZn80}lRZTRw}!4Ih0@X@nq`R*fNEm=&`__4V zkn@fD>a+A_BA)k01r2$f!ZT0TqBR>W`7PS%V|5k{B=0CgPy_j3fLZ zQFSbL-?7>8kLs(*g~Jb4XBO03y6>Rv%{4+r!+yfv^22japou9Q`hM$wt_51r``z{9 zi$LGp7W8=-7xq-WPlMedW|Kl$J-=tBB3x}tZ}PC`&rmQXH11P=%4~8k78QjfC_Y_? ztkmihY6{2{?~5Z!Z4&yiN5B5D6USuGIS9`6vG_Ax^RSx{*z7eyz212Y=gVUZUZc~U z(C^t8eFu9aV_f>oR`c;jcPW4>J`}Yr9sDXFguI*BDAn$YJCAlQv$9sMInvE4L6bwy zjwICg$kU&|p;uHm1f!galUku2KN2F%kZ}+Dc(R#_7-UIxm4VA%A+K7gS~!LKRa#$; z`+zs|+4HgsTd7+|!5Tv3RVZ!_t_yLx)mY-8dBixuFH(*PIx%{419kT2Vd7z--%_Pb zLWm(fG}mLEs^@z9!%0m%^X&@V?87{Bct76u)qBntXB)x!-97UF?|WFI}_lW_TJ!tR-F-7K0`K z0()fms+ilIaR=KqO^aW8`!I!hy}&wySQ1WnNmW=Dr%O;&_Sz|y7wbQ9BJ3mn`b0SD z!Wx3ZX2&4G?f--pT}HG7m3VBc{b}NbE{@o0C6wp)Eei&HY$kklGoP?bE?>U;hjeH( zMv_n0E4)!@Qz+#zk7p>U;`@lNk!$*tM~= z+|PNvg7r9&v5~2~x;#&XIfJGd}O!#{NCx@ZkCMW zPA9So$D#L_qB;<_8Af4A*!ES)0dBt!ykODkB=TgRb*4oHoDQH;X;jB0C3C9(juQ&R z4g6F#SF%_PmJs~eN@4Eih9aDQ-goC5|1=Pc_PdNMj>B$^TcyrIB8oxeLeR*_M{^2C z)%o1>H8})jn6aGrRvVq6M2hGb#bOw`{F%G}NAyvf5IQIQ+e|8ND7|V2J|C z)@q3o8+V5zBFp9Gw?U_Dw;lSKt;px@ngSCql`B@gRUspqXadRSlTxacEBlh1d5$Ou zX5r|d6vbq=^xCy3Q+l1LxgKo@@O~Ave4Fk>d~5fjH0rl{?T-80tcl7G>-V}R$Jq3z zym^@ZpT8}s5!>M)!k5x{f+yfYEL)vF(FHPCP&p36;HKk|-5$qPaXxmT&fdN=V#_e0 zxxlq2)2i1>evx>P13v+kD5#*tgXJ>$?r?$)g06XZ+DJqmZ%KUZ*Xer_(JVa9p7Yjs zH`09nFkG>}p(DX1?V{!T-9Uf&ZqC6sf9Hsi^&z%Bl}$z)Yf&pds4#g4j4QAR4*`Lt z7R1;C-1E^I)BSElQddi}(Di4lYDWBL5ND3KaW4X>`AlxfFX!WV+PY2>YCjpBaCrY@ zS4iB9avZq9b@j0!S?p9txPZM5Ghg-v95V_y|KUynYW1t^?~Lys@Z=nKq9VUbc|C05}K48j{S4L6ZV>h%iUpD)(Y?=_*0#Lo{xUn`uyNiT%qr)&I33P${bEzyi-SKfqh z)tvMfKA}GoT`QN!9b{FgVuSmZHEYT?cva`UhD>viR;Z-IsM}`5hEa1IfDVMDQz0da z!)($FC-=$w;`uE>Sq+B6E=0NSJ9xQi7Nq6!!>>ZD@nllO%dOkG6iAW`bOl;D{l_;P zy~y8tcGZ9rXsr}p%x=gy1WVX<2PmXvrOGR^pheq&Nm|`3~FL@3kS{z zS>LS1wtep5(GSp}*9-pIYyr-2I^8~*-Cv*HT&)eoh)WLpc zEOU}NMxGmd&f0gK|anT?KT zy^J>9tR)Q9i!NP(d@Oe7e^$$YPdR8j+i}Bo8MMeZV*k3_8jgDH(L@)dBcK&E=Kn#0 z|DL{ok2xx2I-yz$Z=HT$oLMf|chN6c_{^pmv8ZcG5XqeWo;*RDaL`8i^1tl@9fz0yC5z6wAbhMeQovut5T~;aw}C)8?lD_ zJ%t6duhD9Q{dh7bJPR}od_JBrx4DD>|92-MXv{9*pPu?YZc87z1 z5Btm2>-h2b^xhsbGH#dR8dun2qnR%D<3XIe-kn4C;|x@*(M({0)o6Vwc8lN>PuuZj zEgF?UL``gA*qa7OjFg8P?)egmQ z(-db6d2_iF(kj^9eBP#bBWY@z$+xEw#?zS=T+5gF)OVJH(o{MpnyK+iYjxqw=(N(B zubQGYfoaq8kfMj(5ayW0qQdQf64fzp>DhJqiIX>K*0m?DIJ4=mIJq27dMRfOt5j3+ zXMwk`LVe5+u^z^cV5^lHp#d>Cv`uI=D#&TOqF8@BvxVKBkb+6A8~i1E74--{Gm^y4 z;t(bOvC3aYIjYH-l1)dk)KuCDHgqn-ylBG%wfgeSM%Y|VWcUJpIvLPrYXA7N$cB0k z#*$ATSM;xs$c z^EoBbiL-#X$6KKC%M0JjdXntb_SOc&d%Fm>crimC(-u9O)Y~(Aj<5RPP}eqBR7>GBKR=Qofap^^K%dxM<56fIIEly zuf&D@F-LAbE6A7x1Pszf!g>}LN#UXxlYYnw>@+M|X28^sgbuTa8g4lP-nm+AJ|S+G zzlUOK>U$OZalToJOled|89zkf88k*JBRM^Wt3DEoIqnBSYX z8%HE^rjZocLHi0qTwLyTg-zo1`aF+6P+WMCq23a0<+Wz$oegY%%xSQH29Dy<5}c@I zxH*{3^4LJY!LX~zm9ww}nSRSgetc_c%N|2L*NRDnd-4N=cxBNw&V*iIpB?BWidfq} zKc>wfV*h4@4z2~;9(rjm69tBWA&L}R;RnNz^b+jSOsX!yzTRR?DeH4BsnzJix-!z( zh88r09wAJS4ApFeq7f?S18x+X5202fOo-x}ijCrq^POQZ%lwIHL{zXW013=yii55` z;xc!@(4X8iv2-U0m?!{S2sz$-s29yya8v!4U9Xs+X7R4(AvkFpGKO7nJe#T>4A%#! z23^s&0=X8)$FrsW-%)7P-x<4~3za3fgNJ)J!veRuyeEpU4%78ZOJ%bfL(S~go6He? z-)~1KRBlF0KYL}U$F`jGd`2=%#6&kSyn@Yr(g7yX0go9J^4L7&L9iS!MYEX~5I*Qv zJjbo!>tJCGU5Ydm&$xshog$}T44SX%DOtX{DI@OfWrWF(P&b2PZV-yz6=Ba2?i1AG zU@F=RZi@c=SP0vRam3**p5Bla%!O{KBHyUo%nh1=&4&>ZP-du_CGI7`=Vvl~Ie09r zu*7tA6gZusA_5mI;E2%Fv*0}w^v?W8K;ZKiJFZ<;M%rAtj*y2$#er53Pf5Og7&ZW`do zD&N@0bQ^ZD>X;fM*}fmDnv%0Fpn~UBAg0Ua%Nl?MSWY9-D)9zO5|=kd4Dgl5aP|C> zljP{b1hRWKP7G{o5JRQXE(^p{=C!l6^od^*yhRzJN~_x$6>MuVV6g92Kb&+t@=lGj z{@xi1GX;Lg|H6W$)A{_%=@6yFYpzg~T0WO|u~0Oc49O(~FuW0gu$T-AktpOApYJb= z70Q&Ffq-+kp6{b-uE|n#wkr&zl*yK>%x61&rWBL*r;BEk|A2-5AXX z(eqdf(xhOj%#l^Pg?`@4nok{3=Kka#n$6UzA~TRUQ9w-AxZAyix?%zrEMUx-l@LPG z@qJO~K1Rua5Ph1S1FtnAa3D^l^?UpzYpxSYe24$Y=+T)N*uv+B{rD_1xfd+ZjOkT z41mL7kCR;+5e)Muq|63)@00)xbi6>%#VU3A3`waM*Un)5>sx;tMHq@XC-Y}23f-EFK!3M*H*6Zv;b6vSgL;;7A?$AD^H5gJ`))q6@+va_< z#@KY8;k%eVjMB&_AO?i9Y7S2p;fDPI1N@} zdQdg)O=dPQBVd}t=l9{fo@l2@R#9K~J>B!nb;v1;#w_%-7qRH{tm^wd88gTXEu%r2 zAJf!X=}Ud6dV5xRS7j}AOS9?Il7#7rRNc%&?g4$90d)V_d4iNVO7QzIr*u6 z8EwLa56F)CeoxuzO|{F_yXVo}t)EH?RkU_)sCL3^#Pb#r$*+Li%1P?ONXD{8a!3lr z>>x`J`qNU6Q~|CGn(1z?qrEl`NNsF62?^FGPlF-1NcnHV3Os95ra7C&VowLj@=85^ zAA``OcUc15=7+2__;y+jVZ0a1x|xz@D&DNZT^@```J}yPm{Df(BVAV8fOT6gpT}=B z9ml6w8)igaHEaDrP&(57rv!IWkO&|7BlsiS266S>uq@?>D&7~{ov))wPTgSWc&Kd$ ze;hq2>*%93_s5FWFlTJpAL@s>3fkmo-emszuK=5#^z@~*onnQ#O|%*akjbA)B~h)h z0%{KVSl|#SX`1-lE(!OD3Th>qfyv08XojN^?XEcx5C#yih)cNy%XRwlvOm}o2bPnx z)HSU?p!sJ{7spU#vNWn0TqIE`tuvfcCRS+zoz-@j8rgW#UC^>wkiwbl&;nPHxf z$ES}~{5~F68gYbdY3OaOx>?I-cPDfC-lhXi=G{Yp@i2k56?5_2F4skuDpiZFp@5s3%b&p~xs2 z7+KP`?;^)icunYl!q052l5-`>Y(3UnZ+N=gSZKCf7))lIMhn#T@Tsm)T=u4hacSbi z1I<`0|AQ>PuxEpqZ5o%K86J+98$GoEHz*q7`TZ%E&l~0Gc(GbD`RQtt7E;BMVh@_M zXd{16t#`02&rj;Q=c~KTVuaZ0ho?#RAK~cwP8XTo2DB`=4wq9BzTOx_84<|Xz?gm@ ztPm9KsSM`e9irRcznoiRW3`SG6-krb9<3mf+}dQFM>(%h5ilL?*g!HOmLrR~);HAx zQSRH|4In^R>KFj}7vuAK$Pb1@9N$&IUZvA@#W5CSO<)sq&0h>vb54=|+y-W}1ZI_MY5kPZe`$JVmj($*dx>1;n4Cax793V%#HH3`o zaoDfB1L3y&3D_>Yp|F?~8Tzzh4Q`iOj;i;o#_^ImfVcP%Yfa69oXp^ylh) z6mqHApWA-YIoz&EX~u9}w;%T)&E9ILFXXetsD=TivknsIe`W-}BFB2Fb8>9xV+6#G)GvPi>hW zSY2IZySB8#jv$G1KAw(^j*60M$hiQei#t4LKve}7=&CjAM+C{j6PBq+#u@*FVqxq7Ca)p=8p&pp^7zzZ0?Uj(s7 z)c>8SOf#_L-UI>80wRfPm|2l=l8}|BKH8Wv#?gj`#&bR?Qp1$U#>>VS$p7=jys%Z< zQ}AojE79lWZf)n6l*4A~_T;FCvH9-?H>_))nm7ms&)*e+hj7hF5IT zhm5j1faQ!$NT!&p^_yWMcYb*mj` z5)@?d+`x@_9ovm;E@zwiRo}MDE#$tgWVREKdi9|tgDXs^?yjXJfFoqzV7{t9gV~tw z4>>&+)MQ^EWlSmp4yYNN1u}*|w!1WyLViFS$lAsPR|L7VnG{Jz=Y zURIXtB_-GkO+4-H2^jAojggg_QWcYJyF#9_pH)*-!p*2>D3SnFyBec{kvnY08)%Ug?8 z3BUd383gJ<;{F|q74SlL0!%Bix$Ip8_WIu+0lH7Th?5_)Nzu@)^=|xOgVWwkyL#Bj^{kOLqwx9C|Nek#>4PDS@Xdx zdXdBZ8$>h00RzFOzW;|R6l#`9J2B!U*rA&Wn_%wr3tmqb!F55 z9(lM*-5a+zfDw(vyQBBZN%TkMJWJyyQr*Yfbx$jWpie(8$&ksiYT`0!Jn#1ox68&p zr$dNvm%h$6%_g&2H0dOFfI*>BEP-rf+E&~b^3op)``3fLfm|;MV`>2A5Gy?Hpn(mt z1+9L|0KV`Xe_QKy#E~|vD-U^tamtSvgy2pLG07^Gv9MgOIB?`PkbMMW^}7W`(bcHz zo^^)TSX-#sew%;Eh|H~o08%{m{gCUUPZW}NUzJtK7w|^6_zlb0nNZGOMZ$HDReTHs zX13Kk2UrZn+edE2ILPe=r9%M=pd|?S+#=`8wPic`Hm#(b3xOY=uIQ`fn5ekIfN)5m zT8;Ktg;(w}@g&ttO4398OIduwQI6{Qx|lMUZ_d9c+8& zPavXv+y+Q+N#^7p{fZS`-Qk|>TiXDd)C4jf{35sT_)-{jIt9D=`FSwu&!;$_yoO>c1j5Nux3HuV`J!h90^I7QQX~*5W?=_al266 zMhz;CEPR7!kTO_?QC5oZL8=GEFXS+7F>Mjz^zQWk`c!E&k>si#*FgwL=D6ETbVc8} zTx$x4fWs05ETixM`;Aum21B4!dhKPMoRQ6-`l6NB&D4Rq~ROVEw z&ptd+9X0;cdq5McI}!f;2Wuh1UGPzn0kcA$+#WXudz*7KYsR&!q=B;GXBpa5CAe%B zg4Nazzhcp8)zAh4k8{1{#?z>k#e8^w!9~E@tT#($V5u=dSrBULjzvu`WJ2bJNVHfj zkz~*4wOFZI_WXPw`;khm$Lkvm1-qT?XzGX|qIKJ|krd{#;!m9lZqwT&uYP-GB{$;h zbX%!=)Opl)u&2tsL!Ci`pe}BzB*$?>$qD{(XZUWUc0t6c8_0y`csD%G);_g|y%A7> z3yuexsiYe>uk!NH{$sABhe(RI7&-?mHS?JP~y|o|e`3eMATBk7Px(H4-QD z1h(C3e>QdZJHZzUTx6>U%KBBT;cMAaYgqaYiOD`4l4oH%@^}0M4@TNDdNrA)`h0zM zK0rj-E=H&|41mTG(fEEWbkrAOH((rMY{IP@NVWpw;1vkFdKMmYw)J^NHN|hQC6M)x z0F|8gs+$|fea=2O3PyzWQn#h~8Bl1`NQezf$w+4M=b-D6IcETazwsNqS0Z5Rv-NZ_ z4zDeRObALRO5ppT==JsSC&%m0As=2Lt->VemdEb=rgjEoC+k2F!=N2tqwN5$o147y zJ~zhaZ-4fKgt?!i!e7!M0m|MgB4>f|9Bz<{PW4>ZU$q=X?A&x!K?Y1p|0zz|2TPSi zUp03TpMOg&rF)zNR!yD>#9(HCfdiEr$d!HUysk$M;DjQ74s1lsXelB%F#(J5iXVs`p%Gu@tZ*cE)6ep8M!L6g?-^t>FeC(2Erl#aXd@^Uy#O zC@Eqe!?2i;LwQ?i{gvd{Mq;xaanY)^RFBWa{ajAIrYPLK?=OK>cSd`Ja_)xje+^bWhD zf{$3xK6tHJ>3!dTQbVNz6;{`h)SxfM^K7v~soLxD3Cw``Wgt;n13YOK|!??3IGG0VjA9G!uA4y+njN8ZpoatI$pN0&$=Kv02`n} zz-wiE65R{g^lCgDmi$aatXgmq-e6nS2)ShM>R47G3b~xKN*&34>l?$GAxlhb093tF z@K(M`h0@){I3XS=?4QdTt(GJs2l8N9?Njt#lU+^s)A^E9)skAuWB>-u-u{R73xe%{8Vk*Ka#*x)%+7iY;xG0bQi0LoR&}`5S=|2vrpJi!;=nr&Y%WMCfpX z&Ny}2N&Hv=P_QA`SuX}#(aycJD&<5+d1m6FS43fE{b4!B4q;H=&!qx#L2T8gVI3)W zr}3E29jp6;QAM(qrx{K{Gy7t&hQZYax_#dUSzu`q!8nx$X@G#Aogk;dGJ*vhjv}0o z*9ftML?H3X#JdoF6eqJlL(s7x(YH!1d3fI%$x^!(h+O^t)#O50_TCpnNkr63;S~s? z4elOcjAj@Nn><3`Eu%)fDhQQlQybFTE4bH0XR+1ksRXx2dg7S>;P%a6I8L~>R|3hw z0^LWK3&y|;;H^g!i{_(&(ol@WV*Z}NoEsbzg_LMs zL5nma-q%&UXKC&y(I*qFUXxkugh4~yfQ@7*yz_X}xT*os;P@1;HvvYnAzqg?p+O(G zW4hCN#1bqVf`MC1{-7n&SEjnolf%^mwrwZ@l=!hxnQtlF)?1`$)NcK#;8o0&@vw33 zOl1VU@Xa_JMg}{F=Rs37c-ahos4S1$j)jpB%?) zR@N2spQ!RaW}-OzP&h&<)9j+prwx15-_E@-(6>ti`~Jd^u{K5BtL!fDr50h#nV z(dEXP4e_9*?NJFGAc))v_s28QStuEBm~}dV(*uX~o)gJX;hM2b~ z;Q~=yw7eTAzdfAy2BDER`XotoQaz*tD%f0X)rKjgm%-K()<4HM?8S}Ist@KTllx5; zE)B>#M=YvPxv5N1UsJI7N6Mf!EEU_*QL7D`+NHugwNw-!)Ze;cP3+DgERgJ$00< z%tIRJRYA8~avVi6Iaw73nuB}i2R3&IEg0ldsO_N52SDC?t1;9Jpb`!TLNxFLOiTEY ztWNBD;a)mnsX;Kw9F~%B1hfEO3?#5lQp)}yt77H-dRUOZ=oa{CYY&cvpGQrC%dA+C zsY!L4&owP2P<;@1g1}945&r^+w29c8mSmS508bkL0hfLaVd{@kEFY+OTGYv_5hj49 z2gf5?C=)GUeR;fsaid#k3k8eqYFdCTK>L3Im4epWvOPR zPXf(MGkxyvnuR_uyizmdH7|uBRWvu~uWF$`?{Mv-U7{Ogu@f-5ChLdC$M((f8)m&X z@)o#1S*s8iM8j&jt)-!&^;tQ!xr>dWVu+=&>2RRG`|+{GaEK2xxCA)}!36~q}f}jo5-wwar!8+XTnYIr) zBw#w+VUAKV-8K^Jdm&!%NQg$G7c5ZD%pOXoi;^YgoC6~8;=#RC379+zdSEMmpgDtb zaxK@I%rVVF6PEU?p(2a`jyc9@kX>%DqfP}XW8oht8*NR#R)>)1LgGtGuJfDbeL4*m%y|2K-;|_aLxlzpPw?KS(q- z0~$#3^$v#QLz@loVAuyEu@JG4dEajaXx6Q34SbM(3h9GlzCk&(1VzGbbtREYuRENM zh@=tfoZ&`%d+=DP-O9(~5Cm&DmL0SN8%WOV4F^;|RvQqJ6tTb9Qgy@6|5vzW#R2zus=i!}sMxi1V~ebmao{ z!D~#=wdzjf>UCkL#bnOh#o>qMdi}x)ZVqck7B-jhlRw{bdoq7~iAD*4A+>m2)MRr# zB0GB*f%IygN`8wRpRuBuxiSPw_eS`BA> z`OW%gQuoK_F{4V07P+B}6}stp^Co;zfG`G)LOoElJG;V|(vYcI#A-7cL4bc5FeAr0 zlr?_a%kT)sF?y~&^Zm$34A39^S=nA*v@M-@d>)G1>Caxo8sAwLkI9!drvK__2_Hp1&3+# zIT_;5xdc5AujF6+e9p$F(T~L-{ue6*LlSRV_PC>&Tq%3_Q|WK#>;;$F%X1m=-9_Sr z*P{HqD>_W({IPhibX}iM#}8W)!GWLGQ@4w6zXB&a-%nxkSlON~S2A*I)7+-sPPNbO z`S9MAPshR;g499QMEE&*65ALcr(zo-t(qXAIqbz_)!UOu~LAqQey*J zsZfE}a(G`a@a2)j6u&9ns&ER3?eql=^$P+q(lU~hYBmR9B1nIKi$`OPx$emk3V$Lj zv}P`&?&Sy=B7{BN#fYDqgZ0h;JcphG9`8F((I&psz8c~Hh+M%r89G?BN%?KLywS;H z?_6NKBKP6A1Z;3MdIOc|49Fdv=g8!CF$6+c@(J`^^flM0L+k*OsHomm{n)$6uS#AJ zYkSX0T!G+tcdRcj6SOdWE#js@LAA8i^N8N_!g2p zlT$XK9XU{KYQ95B+A|d>EY%6?=bxUhxfpC#6J-JsNnekia9VDx6Uur;q$-+-ru_ZD zX~;ruCV6#`2cldQvzc7_&l;ld!4ueE)x!-=YR@YBHMOI`-sm?3dI^jXFPO7u$_(ZuLz1?corN@7L5wXFHb^lLYXJ00YP|#OcT@~FVJA1=%IHoa&-=p?09rm7v?7gB zQwX2?XYzxqf8Ak;3Bs@KZdb^q3iasWnCAoxx-8M5E5m+V0x^0m0isif)HrmFE8b2W z6F!J0M2aMz*lRkIAOJXa!MljRa7@5QE*udb-MXOj*=Db6p;-OJm!;%#(W}2=+-{E;CwNH> z5I#+!O|!|)BraVV9yg(FjSeX!xH>gt4(DU)O~#Ht^6n_a{w$|3!xEN(st@WH0md+a zQms@$K0*+NsTS;0$Q9^JrZoMjW5qKQZ*my)eZMC|RP=gmXqbh0@#fQv&Wwa&m=;{` zv=QBdJZMRQh&7n|Vafb@miEg#J<`Dq2LRlR#kzw#Jm3>rvSv4+mcslr>kY}&Y+8ld zy`{`EJ`z)@S74IO`$NfEFf4N+J_lI@F&p7dsTjMCt|@~d-g2f|QUGUvQYAlEx+!iG z(j|P1(4Qd1Vl33=^g^=mYfq)q1l#j-Z$ELd#9E(5wH@zqUzM`|G&$XJ?zd%QBe&}w zTV1Uk%jGZid4!9NDWg8f5It^F0;T?S!f3cbndiwrlC;PSh-^&k(P-3zO|KSFchO1M zHh2?H`2iq8T5R@_R8@cx(qL37^P>lR;8Ci<4@{#5l5s08Cc+Xt#(+K)IOGgv9E3mt zE+V*9ZgMCQtb=5@`&<7s{;*2a9R#cE?a=_+Tn@ufRJn?MO7!>RxO4F7(s8)LdFzzr zV1}U&gab8kN&>SUr+=bpxM$nuTtA#5wC2>)j-5Y1J8Yv9xr!?k!q%= zo4Jb*u#;FuqVG~^bkuraOb8mD{kTC~BBJfBeQ2hhtDYz!&I}QcI9#5G0q9z2tN|Kn z7GSqc!1raR$su7vA#S_x*cgVgskb3GyA|eorXLoPUN38LAZ0VK!WH87{0N!k@JLmM zQ(5x0iiu2Tx9BAtEZ->`?62i0&Pq!3c;7@J6z2-bPbMphe*b?Ronv?%T^EL9+h$`M zjcwajgA+EkZQE+xhK+4Dwrw_PzInf&b6p2>X3y-|v(|IphuM+zvu3ac_l8`H&~+V& z!@0}6E3tHs>Sgo&FQej{TT5*!*mKQwKuNj-NishqMDyHxL3tC@kuiV3Rm+JI|93wv zHvw9wS7IQD6kjFiZB3U6Pjf{a0!!!+w>wVM5+P0HXUbZ71OyWAfU#hv@6zkg)!bPfV25IR4voa>@=vPlPL}{>kavu2 ztOX4njw%jLpuZ!Sq`NPG^c`B7g?}C^=xU|0nw)TvWqHaiZNL z%i~D}M_=_^7D8U+iW%ThAbzvaG7LT_9O>wpw;*4tI>7ixW3xzLcE2TSvm4LfQMsPJ z<~!J&u7zln+Bow&Ux4{_xqanwG7Pb$!}PquJlfo4M1x>b+eR_rN4ov8t?-&M;iR&D zpoTFN^hFPGIL0&mVi63CtNcF~0D^_|Tv3R0+pPgk$uO`8g{hG|hqYb2`ED!7km)3` zfmZe$?oQZ#Pm`s7R0jTeuAB#uktrj}@UTY16f96(GB3-G@;(IbcZ8D`)3?dD)Jz|i z?WH4wgLm`XnQz^*DMsENWW;;{3WLf3-d*=-)68+;vb7xPRnV&V#|q z_*3VvEMn_gXt=!CnBiFM1A~p@lKL|$x&x}~d-S`d3iy0dR+(JzPt{lxy>i#Up~>(;VDdhcl`& zXH*#u^vPjgmuf20uZCc_oiw)oKZ@VNP7Qc*r}$Hz9xmja8sbMHXr@z6H(QNL1v^oi z#qt2x%-_?J@30FM+wb&~j1Oy;Yg2MwP>G6E7+lougKif*e<~D8n=49I+rlc124JbK zp^Y34#*UQ@J08u~-iU}dNU0gpL;fny0nn_aCG=Vv5ec*i{iluv+4PIb-dZ20NGKw1 zuuIaA!YvXM%QfZ6|D|#xF+Op^W;r6?es|FUX38bthHhR zBsa4hJ#9;;#|2fok+R^Qj2|5x3#s^+s~dHRnWW}akiX%?K7}AF^((d*G4vT9%?lzm zVJKKUeYc@AGd_Y`@W$PE!9p#Ox<IOPQ41=8lfAsAPcoNw4+ZB>O)Y|uM~us{PUlz zkzB%kMKRnV8%=nv4Tkm(wFZ$7N=isHmuZ5(nNIWP3p`cyv3O9P5RmvcKoASvu;4)3 zADzn+ej3p2l{jDjOUS>RixaXAzv~Y~?>Zy1q7T2qJsggEC3Uyg)&25C@NYLYQrYeY z)5$8XaRs;H`0UGbg!*6DiG}<6*g_a0)KmP)?+lt%K;GD4q@+1ns23`=U1YtrDfA0ts{dhELvrv7{r7xddz?;DHze4n%VImO^JcXV zOt#msH|)J?y~mu{Hf3wz{_*AXndcj&&t`8S*%|@sfuY@B8SO16k3gKd6>)%qN{(`{ z&|v&&zn(?2;-E{=-<#QNT0ib|j&du(%+e@`o`WJR`bZ!+%8`;i1_AQbGh96hk`7Cy=A~=YbRLOVp74g6tv0O}m zOhsiA!>m+DDKffYwZSDVTH~iTwi4uCiKJ<~XuJtbDDlbV*I(!`Q0EM+1et~8#H{xX zR9edC!-A$gr@AZ0zflxLj zacr?oW3fiZ#qT?$@{?qwIlgQGe%PB>FzImC)i8I|NH#b+td*ODFfe0z`Zl=-O}_62 zm%>OdXTkH>{w=*wg%=UQtGX&5QbMf6siZ=_Z5L({M$kB7Kz?)r)|khGY3l2FRQiWA%}=uXE7F&gy+aZ1L(k0nIt;(w8$dN}t> z!jJFa^W&|U?5~<2)e7#qLDdEW@&L0%WHTxX9tb zoQq{Ph$l0L6YvNQODJd*I)M<>9=VhvbEE`ztOd1`x?qqFh?pG_o97!S;dvs*O)lFM zbjexKjh&k|GYoUEkZEmL)`sbRNHgSjQeuFoenHXO(@ z*TGMT&cNP><9n-AEs-z?TY~ddB^RMFL#f*xF_{1TjOXNA3THw>9|*pOH3a0XA|ctRNF0bg=Y@^*!aIn+)gREc!;9uM^R7DdG>UU-#9bL- zXmm9zkPf~~*Y1%ibqs|feO>wZKxTkQ3)iajF@RRTRaC!)_u9i$IIRv$Rm^2nMhNeq zPN7K^NsiacpD2Bq+KB2lB-Fa&(x_xy;NfEr9vb^>R&@3MEwCJ z8JL9Y*-I^sht->l@yGb)SBwvK;`TzY9j1a=<9FC}w-uHU5RK2(skhtU46i1aM!t;p#XGae~@JldMt*`-j~?w9rMO z3u8Ce?3(B3lXg9nS9N+sG1TD*uubGHIDOk@d=fl5H-we2guoBVvrth1{h$cnI;ijc zgCvM;o)Iy=kuS46X#B)iNRSFo&>0|4JZ#Ggu1=1~E!8yw$VSC#khzb{GKtu!+qt05`dcj>|hw^kn&gWC4X-UZ~24bBU8Jf!fWn~})HnP?; zTLO+;r09Ei%h`kJVaWYoG>42p(CDgr1PI_)rhM~Oe zzv!;JWgsDg)-iOn+bNPERU^bmVl^B}p}I=1Y0V5#qw#j_aFW_l=w2~yDv_3H8YY89 z5e9vZZk4-JBZlsH%DXinucK%j&L*H&*^=r9H6j-hXL23Sspb{jWrUPczrBY zz(oG4a&!NAD8CEyil)&6a$^40ElmK?LowF6E%WPx>sTHoe$h@G3Ixx8&8q{%UTsGc z3}H(TBQD#j@(U0)_9-=95X=c(oZ#`ahtT|Ac43{DP#{urgYj6>JK@S`cC4qexth-L ztW+yN0?k8BD|h@4VRkz+p%R0^bwAFjTKa^%hUHG8HyW zOa=~QyZL%OaJM$slvu#Sod{YGNz4xHL|aZ%gLX=LeJ_X@wN;cvLo)mCdinMhjDk5@ zxiOKTj>cA3NCcuU+s^;g9Ak&#kmi}e$)_^{JmW|_ams3yKKcO!>kI;+u%%2{c~TWt{KC~xJu(dXHb4}*(R zKP#U@mK8Pflyx1JJgf|3GTo5x81fD3B1ZMn*wjTczU~6tBy;3?`)s*GjGk2@ruAq#> z1a*Q2$FH9AKJ80hW3>2};(&;o<#=kjFzjU!9khS)!e0k_z#Ap+(;4-xd_bruc8}oYL9Rk%)@EG*HTigEURx>D1!54 zEK|9+_<3KUM#8#Ib=V0)#~%3PYTp!6!pbQ3*-TD&LV}x{zkWb64wFneR$%Dst(A<^ zLE3((eB+;hrNk$7^F|HvM!v|o9B&px`fJV5V88;&CSsxzIJGw-$8?$}9Ys%87wkpv zE4aI&2bDlgavDgR8v>EL6UbOQUb`c)%R!ud{6r5TAR|%^4%*?>rCnXfiRxUd!Zr5I z%J)8_%ZUH!{xiOlT{z`+y>+G0CZ5R5`4kRT2_Mc%GYkRO1xmcaiyWr#%Rn+|^qpFn zT-1o+A?++?ChXb{JU9(YzXC3^Lhh;L2Hmgm$Zm0q2o6{C<^7gB)kmT8JmPufZDS4c|4Qk zokHzX&y3(49{49h3Sk`zLarCHqB23->|2r-{6!J^$=EgGX)M5G<9$FN=t69)v>KwR zO`ZRAcJfpT1DPpXZ8)WpYQ~LR-vpQKr87VqNk6?J(hHlVn+5l7Df$^Ql?IZDdLG8i ziu|d%&_0g!L{6J!np!$FP$zKf(mai{L5*Z z8Q}@&+Loh4;->b~rLO!y$zbADhq{iPFY*$-cgI}7gYvp12tPaId>@3P_KQblyh-otQ>{v&w3j&=2Z*`Lu-@LM*2+Y{} zkgZF({{5Vv`9#n2WI}-!a|ZhUAH&dT^1hp00(g`tT!P{Pq+s0@Sl5y;8Wrh;%6{cU z=}Ms)M0$hv$ls3Q->{LCRSiN+eE}jX6geFrpMsDe2aBz`Dmc0Mh3?IO51ah@Yn~(1 z^bdfF?l|MnoOp@#CN)f3T|@`!IXIzgan%HWh-%Xd{irt|iN}tJ?QO5=>+XqWEnyj_ zZ1N+~K5tuiUul7zs5Ko3kJ^{)g~W7DTiv$(>gF}m2aqxO95{|n@TvK%0|sp%axRCp z#mk#(C^D-H@o=P~hK2?#>kBP-D+`M+KMdOE|Kzr~1UT08_96S)fFIXr*Lx&RcqOxY z_m&Ig=0@(ohY~m{?Reqwqjzb;X^qITp9^Op0Gd6E>RkmgcCzB3XzY|Mvj`O8(u{nO zz{!*0B27>R_-_T5J_OT#N82_Nulp&U6v!k~XAm-KQi7grrgQyUSk3F!hK?tEuN-nd zL<8zT3WNaa^=4-Vzx&yj2xl*~bvOO_|6G2hEZqw&+-P)Yr6(sg&R}p!a@SkG%aqd6 z%aLWEZxk=$JMCASTFi!`CCbOd$Ontv&&yJ*U+>l1qtoEKRljo2{k>A7zt~`1k61fuopZk%=AB z8{3N8NmFIm$&F0Rui;%WFVJ426J?SRO;M^s!ti`Tuk@32uloJ<9`nh}(_5tmE>o$d z5NjV)Ql~|0^2LOcSG^P%BNytJ@#`w>lRv#LjdJ|n50#t?{FFOpc{u{5WKJ##K zRSsQNV6Pl!L*X$?6}NyE_g#ZDGy`>Q;m)hp50O-iS0K^JX){Y^x7t)jzlW`Tf&>~y zP^dx&UBN)$m98b+0}*LN^#@MXT+YS50vsm2S%6=ISHc74@9QEf3KX>TvE&D(2L;Z; z{gDKkTx)+IXLOpz!-mR*1j+-4fs@i{1DQslAS0)R)a?|EH7JypWxDlGDw!W zYc&YFQ6i0+&1N3HejBp6B`2}xv6F>F_U>9XBsG;fYvDfw`f>%ZEpRn-wRMxWbZN`C zTXEn_{dZTlDS!6Ah~`ehG;hY5k=RB_O$p3nbbg0fXKd>CqGOHpJT1$Bs^q(zwwo&x z#QsnGcs8nIk2}o@M67@0u7f>ac>l3RcjnJoXEm|HN}*OPHusfa&x8-=n{vRm6Cnu! z{{eZMSp@n`?(=j(egeH?v6VtCq}_jYlT62KiC&Xy@^ zTl&6b0}u1J#ck-Hug3-OR;j=nJP}~9FoU3p1(15hCN8Xo)z_-lcS!)|XpcmooDi$6 zEzo9Xf~6-l%dkVUmSG;RArk!OTHx_9@mG)0ZsJH?_fg;f=#H3SPHyh>o#nGQo-MLm z+d9CQQ(;~RxH$G$Y8*E^%uaSsFPAP|k%1%B7G94u6_>XbhJcMhs)E0UX4pO)h=|u4 zq|f^s{a>?*U7h$*U!Yd8&zMd;0he6u4>Amq*`p34c2GxFa8Nw)*XZwNo{`-yhtUo^ z-BJnvO^awO0R0ag@b|Knj-Fml4-wXPyrhPPi&Q}oHV)hesd7vSAaqZF>%)Z`xr=o_ zke;}U5K=GnaCe_4;Gb_}S&#Q4{jM9ovL%T~$sPOoF74Ff)V%~XN?FGpKN9NrO6Pwh z(?>VZhE?T2LnV>OwOej?wGPHzOH4#dup$xJ?+bwy#LM|Ey?|Bp)UstZRJ#_wd3Li3 zV~+OUHz`T#9ZBG^=TP zD5IlxqW3|K#l!wd+QxodnsB-eCPE+Wj@Z}UGfJ(m?|6zCYGK1)!hC%{XgN7!tCuUq zm7hGvcfTGk5?<4gDi;QY7?s#I_Y_amzTb_VmB7-&k847upS99PT6ShI8x^C6jLcT0 zR8#^bCi;-F^B_pr|JH7M{avCVFnXqr@Zb1TT1~);m2!t8HQ@}_0l+zLXKXbMY<*5lIgHEIhL_&I33|-#+D8IrvR;97W<&as?Bdk?A^m~8=|f^XuzN7fAe|>b zlhD);LutEQmv@M=lT)cv#s1U37M06UEw8^0lY>ezjggWyD|Uyku`6-k+^$lrtQyPT zk!)oK z*^(Zg@U?UHiV15F)X`V|tKxMPnudf4Mup_M1wkv0hv@R5NM}W*Y0~aI*1S|-P?<43`_%g zzQYz~|KY+eJHYuvol;!_xDRMx3RPeQgQRV9Eh3xuox^LY#RtpIFy_N1(Tq<+Cg+2+l#7u@ zAQMtc<9_*rtoL`AoiKjBO!&6aJ$zS#HPOxXpP}j)aE95w-0a#1<4N(3DRLxqnyt9g zsP_hXe;WJ0THa=aIYCPAD`l}uY8}5QO(G4ZJa?7O4k_R|D2Aq;cI1%|%T+&cEWZJB z@PAC^AP*WhL3P4NuJkrwv{J+iq($5Y?;lN~v`mlz3bHmQvqgJJyE#tXN#fO5N=*&_ z23L`E50{o({=385hG++6CnG-sce(=u)ZCTufg-e2A{~+k(zu{bEXcF3y>;Hg!orQS z|G|FtZwUa5vchVv6KaQJCN4v;vEoKS*ZT+Q3-sWI7pHyGOF<_07m<*!PXJUk4=iuQ zc^m(mZsURm@m79*!#x~Ji2#LyEBH`SP*_+7@8?tiOmdez6-GU(M*%Q0Z*-4%pkcl} za-dwU6Po*0ZtW?{F~_srxz=ER0nz&&3{;>q?G`IVzx}B#I$NsgL{K_j#%p6>1%oN> z2Nv#w);I<|`>kQmG4>lT8FEVOqRmt%Ic|M{fT_fafSDP83TttrEOl?n>uP69^Cnt# z2Z67)r>CH+;mA`vJLK(Sg_8%T8=MzdoCW~`BJmg;rey3$h6Xny;9$T*-o!v>2&K>{ zmq%kW%mWy^We9mL3t&hnAZBC6a66dN<~91B$$v50{X+v=F&>6GUfpc~?3$Lz>(+hm zZkiwquX|T+Ko_6p^odM;)G-P8iRJX79{++a2XB?s!;s3z1~~R4or$i$NnY_89ctDU zG`sN67wU^=%SmgxkZyBw*lW~r-;yV&ZmHPS+#S7v0&;jhsyHvkcmu8|99AEAHenWE278AF4Y`{z($@ae>Zqp8dyU&veo7E=JTlGC~dYy>!q z0HBo7?`VLFOC2d=f*8*lVc^V8{&0VvM>{G=s>AGk$A~_7qQv8Yft59LB4pN;|31)0 z$OfwgHN|pPB30Um)RFKka`z)8nj3-!Jjg#8);2SMLwElHFN-q-9L5^8B@H@vl=f+6 zq($9^1EHO*G&rbGHl6_d2wh|(p2Qk8i^r2v1!b+#FN3!KY_VRFN)G4vo~@}7vV|2e z4|{uj_s@1Y9#f3Fs-Ia#6Z*xKy$K7uP=#4xdq4cc?b42x%SI!ke}IxUAjlU8)H$#H zIwXY%p))K`Wu;=J?xi=bB08MPklx@g5ONQnL0FM!#p`^=Vv%|z_(Lwtiq(?$`T5h4 z<8r;Tz&KR|&|{DRvez^hkbb>UJM|-T_Ge9@*Ibt|m2#S0S|w;sOkf3Gfw{~Xta)Hv z9ROA7eT?z{1FdXl3q|QHmv8@bzkA@R_y{as;O`mxif5*N+)Ypj#hxivYX((Hr5Gus zwzG~VFM!Rtff=^@{-=~4-*$E8_qAamUTMt%jCqoW3|b}jt1fWv>%U!TK3aXghD?bq zed8(gnMgHVuS5=oud(2nZ4mrG@mW+ndW5^7i&ahrs)da`ZE*k@7> zM6lF5DkNmfiPiK14qzkkL80 z6NmRlEM%Bgxl7KWsE+HuEPsOv0IXYeiQtjAL`nla^qZ88x?>MA&J8^^DZEpqMpO0YSj*7uj@@5HFz0&WVxj!;E zB&2dPB=5hdm~Q(K%T4c6!O3jOL|$sz2#^o2vU(kMySQOxa}@gCixXUFo7F^y}#U2;}eZ zv_nWEeA-T%ALc5@i+~AE1y7~f>T$DA`wu|3iR!o7m&-@6iAQ)wxayO8=8nt=BN}$Q z<#)K(Du)>P2LF5Yfy@)|$b=Ev&SEy?9Z8WX*6d*^o<`No7(32d7^(VzJhZ*e&#T3J zOnLLqyrY}S|A&ovPL}^ALTy?43{a8>+ze)%-1G%hx#pZbxNJ+G^uKa2j=jVIuG*p4 zLo^OZ;c`lJ&i9`cr)5G3H`V0PfZkcz3FV4mlAM;8LD@c_g4yBQY^-g;!YT)it;*-f z=?~Knoi~h$hlzr9@5nXoPUTGHrwcxt9mItBos15-uP$LPbA2B^Rm#h-r(_(G!omS( zbM0(Qf8Br8F8cbuTR@kB&Ts6Z$T7>zp!XW|WJ5FIxHsVHQM`jl?3Rt(xodvR zQ(zUbpm%CG3L%C2sqlXnYSlpCTQ{7{E<8s(a#~0z`Ai~Kzo@8o>h@flrFQ=O4LUZRN~g2Z_oBJ zd#`5?ZL3wA$$r`qO0Q?Z2WRzZu4otZ$mivsh>LF%j2~m5r!ng%b+}hbbDeXRCzr`J z9Yjdl-ae>-l|9gq6ivTG{>6ubIM02|C!5e ze;8v+Aw&7@YTGApI#*a;*#8;xqx&B&N$iE&=^J*nR!MZ}cCHAtx0r4=_r0{ya9E5Ek{1d`3hfw03$Br0y2sT^28qLRUWePI%rlRJ%#=PC z<}J3-d@ZAI=%rVFSG(6AbP1mRe&|y>oYFJ-(`X#gMrfWTJGy)|BjS2M25o{283kV; zx?Ag?QVm0`NTJp5x?EvoqRKlrgs7^K{A|(a7WGf>Gr>ZzEdW)X6Xmv=0``Y)d!{zo z`s(4VJG}}kjbeUx?tjYRc#_qTl= zA7^a{xOeE@Zkn?+9`ezQBsa|Fw2TM@9qEz1%DxKDl@KoHg-PCHS7~jIE3`(Ieeo(2 z-1U8emvDbh19B&iUUAur^b$_-`7&`iKg+|PdCcDAHb{g99d8x#-@9d8tv>n{n4*>a zx+o3-a7m_2E~8~wK!qZ|mfw7Gx?(}?_oJ}XZv&>!2uJW}TiQ-Gp9GU~2qk2gh1<_d zLqs=?C2YZWGjTF_*%$64VRDgD>}<0jFlef%qW1u3Dw8r92IwNvPMsK!RSa86geJIR zLo-!e2VE7$#cHycrMqw6u&QWVpzYubLPG<^F(?wR{I)hGp^~$B$jK6YQnI{Uw?D3U zoCQCeYuT7kz~0s}UV8fVN7b zC}CH1huLPO)WR7=b|uN1JwEJKj4=)%pv1RReJmFU>&JGAAgO%jIFc8;6S}&cYY3LJ zGR~?gR>X8*9oRQQMp7s4`+_08A@Q~lSzVo_Wcuz_?s33lL}=k=Z=sP0?Z8A;6{mWS zlV$_`PUo%hY|{6P${OE;^GnF{eAN>JFgQZ4$G-)vAR^1)c`6*7v!3*He-yf%EGk+l z%4ZzJ$9;LeZdYmj3OSPApsgaPsi%fAy-&7Wq{|P^HhU$?rw-$|ZykdZp9so^e&q3t zxGhClQ^C`LX>PUOL})IcYVw_;u6%fwS?is?vwu=%+i~aZF<-VQ4f5geyfL`d#oHLL ze7z@JQgUr%ym^w-o6~eM;&aigz5bRrBO;Rzm7lNVu=Z08l`ftL4s8Fm3*@afmzn=K zt6>}56Z2wD`&*-$1CE6B?ZucCUy#On#`1k?QgPUS0QNXe3MzP4f?^%*k;;^z%qli_26ZN7wp~d)$^ucfO zPSaB5S$yd)R(x*vF-Qriooix=m8=X4LN=D7+n~mmC`Jf!auFl&vQVtd zEwm04gucZ0>86tpENtSE!!pbG5hZlf;KphMnAy1G4g`WkZ)1T3!7}4yU+b;s!>Cmb z3)`mzi;tt$ML1)X#QJ=LMrTw08ajcLdP^4-pMYWBNta);Z;_@A*aPZV?pK?S@ksK7 zKOP&1%aI>u*&VnNFjxkahS=Dg+1=l0@p-&beQ&lXWR))|AR{bf?lQU|)<Z z2u(UOlV8GIQeX)9DK(R^L+^Uj^w2)UF5sSOzx`Wsg7>b#{BACSbO41z*IHaRrJE@v zqD<~jYk6hwZixkce^FhBjuuLlA8r>e|HP!NYwnXNx68$7fTia!tAgf$yOtSqtyS&Q zRAWdR`;}(Swv-+)0zqghyXf|j7usM^n2V*=#8QMA0Ya@6#iGzKQ53&TaZgi{>6@EK zQitS?gUuCryZE*Tvs1L#q4cYA`E_Z<|5A;zXxBx2KT#n@E1=^o+frgok%z_m(>E(* zwTCnb5q;2Di!^5R3*`)yr0a@P9n{SuP;sS*Pxja2GH~3#-G1w-2w`BMV#lxJFR?X0 zxFy2+Xs<3t*c2&GH${&B-{e|P=M-+5luAX7rm zFFhl}PBD~#hdN)#wV2wh`?Q_Yw4t3K#LM6j8gUW(jCSG4^cHl?LN0XK;2O~M+K;9S zT;XA}F(|sgia%!=+lhl$f*Qz6Ol-PVlW#!;+p;+=#1#pdrHn zUu4@Zutn8I&J;q|qZg=H+HCzcid-$zzI4RF&T$KA{k*y1+S`_^-K-y z4FO@EQSjW79R8k$4m#k#s2NbiJKEc#AvJ_TJ@j+YaKOLZTQ~O-Nj)lZD0SWs$6LbP zQ$_Nk5ABA#W=i&ukbr!>|0CRoc}C}Dx~Xp`Os?&5ky`oNA}?;I5EJK*Ou=_GC_-C% z0|rA_Z6lORB#omCwuQo}jCYyrLwe6O)jaQ_*k_l6s4nHgtLMP=wZhmx%uX;6?JXM0 zg>dD$glAY*dT{Zvpx^_MG|KMo?%Mzv80X5pz>=|nr%VV&SP+K~DE7d>kOcGm7=Mi2siCLlNs)eWLbXGtLW8>GdH+T@9VkZ>hyySe$W0^JHhy~8(@dX& z0=KOq(CON5!c!S*I)l?G5zYBDhPVb03(oMFj-c{Bw?mWS?t;GBUed?)m8R$%M?0Y> zl&{ti95@0C&8Ow&9&MdvY@G{B$Xf0t1y6XGY9*J1&mP)xy$-P>%!H%T`+{MLzG4Dew<9G6cU9;l7jg1NW@@-aI1qz|lrp{8*bjj&faw^K z1kBWGiD?s~b=1Q}S}AUxWzy{HUC+(r$lJ>pDeS~f zqwIjIz)_4HheIScQeCG#&DkCwW-KmVPcu>XMuf_I2g_1<3veXrP{A?Sq#uV_9b|r< z{WgXW?f;vfYZALUEvQI-vs$6;ipVF=x_(mkgo5Gc%w~m|%1j&jZWbCk;3xEtEqBOh z?lB!{5PHTSKuxvpvOouSBckhFQron`L9=}-b*4Kl<1eR>(AAyu`Xl+tgdrmMULAV3 z)L;4lkBdu%TV7V=^bNXXv_epBcgll ztgEwns3dK7Md<8tucL-O3KR4;Of*m%IL;|pA38G%ImncAYy%DI{@zq8_Wn3)J~`$F zO>ut6=YYUYh@e_;YD6$Rgn;|YBZcrko%kr41XTsLX(-AQ1pJT{MDtUHra^`K&>S-1tUFG1^w_G%EXFroP$E%LsfAZPK zO7>EKzLA3{oeYdu!)rhOUXdQpP!Ywj&wA{VUJt(Yd_DY|u^+%a>%1h;?V(jbv+{c8 znzdrmZis-!La%4nK%uzg{&(!R32oOTboKhYlZ0&nsuE8z%`b(R-XBq5o~JmSQ+2&K zebml4Abr@3whm0PG@YA3mW;Ax^5E75Xy=_hihjABGc=dg9 z$A?5M*B{9|jW^)Yv@U2=ooLv<2kCB7fuBSp_l-<=CN3F5|N0UWDA`Na>!av}f{sG7 z+Zn|gby4911!qJRIGFsD)!UZ_R)n6zGIALA_S<9#rtU#WP`;O;5yB`R@%VbNZp~j8 zO6WV3jJN?ukiO#eSME_4^-GqRU6zi2{JzThsEkP=2xwPKLrl{p4K!FW?D^_ip)br> z&Z?EpB0H1~H{m4s8!l`3_^gzqqb^B$$YsqzG z=D1QVi(hSnr?I+>C!OOyShKnc%862pCZ4s#P8}eYGgD1PRbnz}v%Wgz3sKGGHW&Q& zB(O==U^;MZc^uz*I~gm3woO&v)w^lyID1|->|?y#0r&a-Z=??aiHd0gfy>YRu^9LX>6;aF0Vn5RvOXrXd#EW5o2jld0VMS(Qd+ z5Z5gBxl#+M@LSy}pBu|G`2MrqB+2QRtXTJTTzJhm5Ab#HuQPYXAvf}w79K`G3*fwH z1WqOs^ai*#slqcU&AR5*P+)(7GlfEB8@&OdwEGjn{*wvfzu&<0-*$TMs{Q|8P}ogr z0xjeB3BB@9A6RPO1nE%#+e|^ttH1{EYCq{-gv7Uhhz%dDzzoC)@lyiW)`PHa{%>J| g6Z96}`w1SZfS4{Z*uVt*oiLcJq>@CPm`U*e06ryfDF6Tf diff --git a/docs/source/scheduler.md b/docs/source/scheduler.md deleted file mode 100644 index c856b037c5..0000000000 --- a/docs/source/scheduler.md +++ /dev/null @@ -1,61 +0,0 @@ - - -## Serial or Concurrent Inferences - -Schedulers are special system software which handle the distribution of work across cores in parallel computation. The goal of a good scheduler is to ensure that while work is available, cores aren’t sitting idle. On the contrary, as long as parallel tasks are available, all cores should be kept busy. - -In most use cases, the default scheduler is the preferred choice when running inferences with the DeepSparse Engine. It's highly optimized for minimum per-request latency, using all of the system's resources provided to it on every request it gets. Often, particularly when working with large batch sizes, the scheduler is able to distribute the workload of a single request across as many cores as it's provided. - -single stream diagram - -_Single stream scheduling; requests execute serially by default_ - -However, there are circumstances in which more cores does not imply better performance. If the computation can't be divided up to produce enough parallelism (while maximizing use of the CPU cache), then adding more cores simply adds more compute power with little to apply it to. - -An alternative, "multi-stream" scheduler is provided with the software. In cases where parallelism is low, sending multiple requests simultaneously can more adequately saturate the available cores. In other words, if speedup can't be achieved by adding more cores, then perhaps speedup can be achieved by adding more work. - -If increasing core count doesn't decrease latency, that's a strong indicator that parallelism is low in your particular model/batch-size combination. It may be that total throughput can be increased by making more requests simultaneously. Using the [deepsparse.engine.Scheduler API,](https://docs.neuralmagic.com/deepsparse/api/deepsparse.html) the multi-stream scheduler can be selected, and requests made by multiple Python threads will be handled concurrently. - -multi stream diagram - -_Multi-stream scheduling; requests execute in parallel and may utilize hardware resources better_ - -Whereas the default scheduler will queue up requests made simultaneously and handle them serially, the multi-stream scheduler allows multiple requests to be run in parallel. The num_streams argument to the Engine/Context classes controls how the multi-streams scheduler partitions up the machine. Each stream maps to a contiguous set of hardware threads. By default, only one hyperthread per core is used. There is no sharing amongst the partitions and it is generally good practice make sure that the num_streams value evenly divides into your number of cores. By default num_streams is set to multiplex requests across L3 caches. - -Here's an example: Consider a machine with 2 sockets, each with 8 cores. In this case the multi-stream scheduler will create two streams, one per socket by default. The first stream will contain cores 0-7 and the second stream will contain cores 8-15. - -Manually increasing num_streams to 3 will result in the following stream breakdown: threads 0-5 in the first stream, 6-10 in the second, and 11-15 in the last. This is problematic for our two socket system. The second stream (threads 6-10) is straddling both sockets, meaning that each request being serviced by that stream is going to incur a performance penalty each time one of its threads makes a remote memory access. The impact of this penalty will depend on the workload, but it will likely be significant. - -Manually increasing num_streams to 4 is interesting. Here's the stream breakdown: threads 0-3 in the first stream, 4-7 in the second, 8-11 in the third, and 12-15 in the fourth. Each stream is only making memory accesses that are local to its socket which is good. However, the first two and last two streams are sharing the same L3 cache which can result in worse performance due to cache thrashing. Depending on the workload, the performance gain from the increased parallelism may negate this penalty, though. - -The most common use cases for the multi-stream scheduler are where parallelism is low with respect to core count, and where requests need to be made asynchronously without time to batch them. Implementing a model server may fit such a scenario and be ideal for using multi-stream scheduling. - -Depending on your engine execution strategy, enable one of these options by running: - -```python -engine = compile_model(model_path, scheduler="single_stream") -``` - -or - -```python -engine = compile_model(model_path, scheduler="multi_stream", num_streams=None) # None is the default -``` - -or pass in the enum value directly, since` "multi_stream" == Scheduler.multi_stream` - -By default, the scheduler will map to a single stream. diff --git a/docs/source/single-stream.png b/docs/source/single-stream.png deleted file mode 100644 index c324a2045d7103401bc87db2258e3937ce28982e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17490 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 S0nct=VDNPHb6Mw<&;$THg2_Sv From 7cb56711815cabcb41406fa27547eacaae4c0ec3 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Tue, 18 Apr 2023 16:39:02 -0400 Subject: [PATCH 060/149] updated dir structure --- docs/old/_static/css/nm-theme-adjustment.css | 156 ++++++++++++ docs/old/_templates/versions.html | 27 +++ docs/old/api/.gitkeep | 0 docs/old/api/deepsparse.rst | 93 ++++++++ docs/old/api/deepsparse.transformers.rst | 36 +++ docs/old/api/deepsparse.utils.rst | 52 ++++ docs/old/api/modules.rst | 22 ++ docs/old/conf.py | 216 +++++++++++++++++ .../diagnostics-debugging.md | 191 +++++++++++++++ docs/old/debugging-optimizing/example-log.md | 113 +++++++++ docs/old/debugging-optimizing/index.rst | 25 ++ .../debugging-optimizing/numactl-utility.md | 88 +++++++ docs/old/favicon.ico | Bin 0 -> 15406 bytes docs/old/index.rst | 45 ++++ docs/old/source/c++api-overview.md | 223 ++++++++++++++++++ docs/old/source/hardware.md | 28 +++ docs/old/source/icon-deepsparse.png | Bin 0 -> 2029 bytes docs/old/source/multi-stream.png | Bin 0 -> 31497 bytes docs/old/source/scheduler.md | 61 +++++ docs/old/source/single-stream.png | Bin 0 -> 17490 bytes 20 files changed, 1376 insertions(+) create mode 100644 docs/old/_static/css/nm-theme-adjustment.css create mode 100644 docs/old/_templates/versions.html create mode 100644 docs/old/api/.gitkeep create mode 100644 docs/old/api/deepsparse.rst create mode 100644 docs/old/api/deepsparse.transformers.rst create mode 100644 docs/old/api/deepsparse.utils.rst create mode 100644 docs/old/api/modules.rst create mode 100644 docs/old/conf.py create mode 100644 docs/old/debugging-optimizing/diagnostics-debugging.md create mode 100644 docs/old/debugging-optimizing/example-log.md create mode 100644 docs/old/debugging-optimizing/index.rst create mode 100644 docs/old/debugging-optimizing/numactl-utility.md create mode 100644 docs/old/favicon.ico create mode 100644 docs/old/index.rst create mode 100644 docs/old/source/c++api-overview.md create mode 100644 docs/old/source/hardware.md create mode 100644 docs/old/source/icon-deepsparse.png create mode 100644 docs/old/source/multi-stream.png create mode 100644 docs/old/source/scheduler.md create mode 100644 docs/old/source/single-stream.png diff --git a/docs/old/_static/css/nm-theme-adjustment.css b/docs/old/_static/css/nm-theme-adjustment.css new file mode 100644 index 0000000000..ac3c8cacf8 --- /dev/null +++ b/docs/old/_static/css/nm-theme-adjustment.css @@ -0,0 +1,156 @@ +body, +.wy-side-nav-search, +.wy-nav-side, +.wy-nav-content-wrap, +.wy-nav-content, +.wy-nav-shift { + background: #ffffff; + color: rgb(40, 68, 41); + font-family: "Helvetica Neue"; +} + +.wy-nav-content { + max-width: 1280px; + min-height: 100vh; + border-left: solid 1px #000000; +} + +.highlight { + background: #f5f5f5; +} + +a { + color: #062EF5; + font-family: "Helvetica Neue"; +} + +a:visited { + color: #062EF5; + font-family: "Helvetica Neue"; +} + +.wy-side-nav-search { + margin: 0; +} + +#rtd-search-form { + margin-left: 10px; + margin-right: 10px; +} + +.wy-side-nav-search .wy-dropdown>a, +.wy-side-nav-search>a { + color: rgb(40, 68, 41); + font-family: "Helvetica Neue"; +} + +.rst-content .toctree-wrapper>p.caption, h1, h2, h3, h4, h5, h6, legend { + color: rgb(40, 68, 41); + font-family: "Helvetica Neue"; +} + +.wy-side-nav-search .icon-home { + display: flex; + justify-content: center; + flex-direction: row-reverse; + align-items: center; + font-size: 1.4rem; +} + +.wy-side-nav-search>a.icon img.logo { + padding: 0; + margin: 0 8px 0 0; + width: 28px; +} + +.wy-side-nav-search .icon-home::before { + content: ""; +} + +.wy-side-nav-search>div.version { + display: none; +} + +.wy-side-nav-search input[type=text] { + border-radius: 4px; + border: solid 1px #000000; + box-shadow: none; + font-family: "Helvetica Neue"; +} + +.wy-menu-vertical { + border-top: solid 1px #000000; + margin-top: 10px; +} + +.wy-menu-vertical header, +.wy-menu-vertical p.caption { + color: #062EF5; + font-family: "Helvetica Neue"; +} + +.wy-menu-vertical a { + color: rgb(40, 68, 41); + font-family: "Helvetica Neue"; +} + +.wy-menu-vertical a { + background-color: #ffffff !important; +} + +.wy-menu-vertical a:hover { + color: #f6f7fe !important; + background-color: #062EF5 !important; +} + +.wy-menu-vertical li.toctree-l1.current>a { + border-bottom: none; + border-top: none; +} + +.rst-versions, +.rst-versions .rst-current-version .fa-book, +.rst-versions .rst-current-version .fa-caret-down, +.rst-versions .rst-other-versions dt, +.rst-versions .rst-other-versions dd { + background: #ffffff; + color: rgb(40, 68, 41); + font-family: "Helvetica Neue"; +} + +.rst-versions .rst-other-versions dd a { + background: #ffffff; + color: #062EF5; + font-family: "Helvetica Neue"; +} + +.rst-versions { + border-top: solid 1px #000000; +} + +.rst-versions .rst-current-version { + background: #ffffff; + color: #062EF5; + font-family: "Helvetica Neue"; +} + +.btn, .btn.btn-neutral { + background: #ffffff !important; + color: #062EF5 !important; + box-shadow: none; + border: solid 1px #062EF5; + border-radius: 4px; +} + +.btn .fa, .btn.btn-neutral .fa { + color: #062EF5 !important; +} + +.btn:hover, .btn.btn-neutral:hover { + background: #062EF5 !important; + color: #f6f7fe !important; +} + +.btn:hover .fa, .btn.btn-neutral:hover .fa { + color: #f6f7fe !important; +} diff --git a/docs/old/_templates/versions.html b/docs/old/_templates/versions.html new file mode 100644 index 0000000000..476c8d1954 --- /dev/null +++ b/docs/old/_templates/versions.html @@ -0,0 +1,27 @@ +{%- if current_version %} +
+ + Other Versions + v: {{ current_version.name }} + + +
+ {%- if versions.tags %} +
+
Tags
+ {%- for item in versions.tags %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} + {%- if versions.branches %} +
+
Branches
+ {%- for item in versions.branches %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} +
+
+{%- endif %} \ No newline at end of file diff --git a/docs/old/api/.gitkeep b/docs/old/api/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/old/api/deepsparse.rst b/docs/old/api/deepsparse.rst new file mode 100644 index 0000000000..aefb9cb97a --- /dev/null +++ b/docs/old/api/deepsparse.rst @@ -0,0 +1,93 @@ +.. + Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +deepsparse package +================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + deepsparse.transformers + deepsparse.utils + +Submodules +---------- + +deepsparse.benchmark module +--------------------------- + +.. automodule:: deepsparse.benchmark + :members: + :undoc-members: + :show-inheritance: + +deepsparse.cpu module +--------------------- + +.. automodule:: deepsparse.cpu + :members: + :undoc-members: + :show-inheritance: + +deepsparse.engine module +------------------------ + +.. automodule:: deepsparse.engine + :members: + :undoc-members: + :show-inheritance: + +deepsparse.generated\-version module +------------------------------------ + +.. automodule:: deepsparse.generated-version + :members: + :undoc-members: + :show-inheritance: + +deepsparse.generated\_version module +------------------------------------ + +.. automodule:: deepsparse.generated_version + :members: + :undoc-members: + :show-inheritance: + +deepsparse.lib module +--------------------- + +.. automodule:: deepsparse.lib + :members: + :undoc-members: + :show-inheritance: + +deepsparse.version module +------------------------- + +.. automodule:: deepsparse.version + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: deepsparse + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/old/api/deepsparse.transformers.rst b/docs/old/api/deepsparse.transformers.rst new file mode 100644 index 0000000000..ff75736e79 --- /dev/null +++ b/docs/old/api/deepsparse.transformers.rst @@ -0,0 +1,36 @@ +.. + Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +deepsparse.transformers package +=============================== + +Submodules +---------- + +deepsparse.transformers.pipelines module +---------------------------------------- + +.. automodule:: deepsparse.transformers.pipelines + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: deepsparse.transformers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/old/api/deepsparse.utils.rst b/docs/old/api/deepsparse.utils.rst new file mode 100644 index 0000000000..73ddc7e5d5 --- /dev/null +++ b/docs/old/api/deepsparse.utils.rst @@ -0,0 +1,52 @@ +.. + Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +deepsparse.utils package +======================== + +Submodules +---------- + +deepsparse.utils.data module +---------------------------- + +.. automodule:: deepsparse.utils.data + :members: + :undoc-members: + :show-inheritance: + +deepsparse.utils.log module +--------------------------- + +.. automodule:: deepsparse.utils.log + :members: + :undoc-members: + :show-inheritance: + +deepsparse.utils.onnx module +---------------------------- + +.. automodule:: deepsparse.utils.onnx + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: deepsparse.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/old/api/modules.rst b/docs/old/api/modules.rst new file mode 100644 index 0000000000..1871f62e5b --- /dev/null +++ b/docs/old/api/modules.rst @@ -0,0 +1,22 @@ +.. + Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +deepsparse +========== + +.. toctree:: + :maxdepth: 4 + + deepsparse diff --git a/docs/old/conf.py b/docs/old/conf.py new file mode 100644 index 0000000000..9e2bf07dfc --- /dev/null +++ b/docs/old/conf.py @@ -0,0 +1,216 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +import os +import sys + +sys.path.insert(0, os.path.abspath("../..")) + + +# -- Project information ----------------------------------------------------- + +project = "DeepSparse" +copyright = ( + "2021 - present / Neuralmagic, Inc. All Rights Reserved. " + "Licensed under the Neural Magic Engine License and Apache License, " + "Version 2.0 as noted." +) +author = "Neural Magic" + +# The full version, including alpha/beta/rc tags +version = "unknown" +version_major_minor = version + +# load and overwrite version and release info from deepsparse package +version_path = os.path.join(os.pardir, "src", "deepsparse", "generated_version.py") +if not os.path.exists(version_path): + version_path = os.path.join(os.pardir, "src", "deepsparse", "version.py") +exec(open(version_path).read()) +release = version +version = version_major_minor +print(f"loaded version {release} from {version_path}") + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.extlinks", + "sphinx.ext.githubpages", + "sphinx.ext.ifconfig", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx_copybutton", + "sphinx_markdown_tables", + "sphinx_multiversion", + "sphinx_rtd_theme", + "m2r2", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# Whitelist pattern for tags (set to None to ignore all tags) +smv_tag_whitelist = r'^v.*$' + +# Whitelist pattern for branches (set to None to ignore all branches) +smv_branch_whitelist = r'^main$' + +# Whitelist pattern for remotes (set to None to use local branches only) +smv_remote_whitelist = r'^.*$' + +# Pattern for released versions +smv_released_pattern = r'^tags/v.*$' + +# Format for versioned output directories inside the build directory +smv_outputdir_format = '{ref.name}' + +# Determines whether remote or local git branches/tags are preferred if their output dirs conflict +smv_prefer_remote_refs = False + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] +# source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" +html_logo = "source/icon-deepsparse.png" + +html_theme_options = { + 'analytics_id': 'UA-128364174-1', # Provided by Google in your dashboard + 'analytics_anonymize_ip': False, +} + + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +html_css_files = ["css/nm-theme-adjustment.css"] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + +html_favicon = "favicon.ico" + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "deepsparsedoc" + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # "papersize": "letterpaper", + # "pointsize": "10pt", + # "preamble": "", + # "figure_align": "htbp", +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "deepsparse.tex", "DeepSparse Documentation", [author], "manual",), +] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "deepsparse", "DeepSparse Documentation", [author], 1)] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "deepsparse", + "DeepSparse Documentation", + author, + "deepsparse", + ( + "CPU inference engine that delivers unprecedented " + "performance for sparse models" + ), + "Miscellaneous", + ), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = [] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- diff --git a/docs/old/debugging-optimizing/diagnostics-debugging.md b/docs/old/debugging-optimizing/diagnostics-debugging.md new file mode 100644 index 0000000000..9ea259d1a3 --- /dev/null +++ b/docs/old/debugging-optimizing/diagnostics-debugging.md @@ -0,0 +1,191 @@ + + +# Logging Guidance for Diagnostics and Debugging + +Unlike traditional software, debugging utilities available to the machine learning community are scarce. Complicated with deployment pipeline design issues, model weights, model architecture, and unoptimized models, debugging performance issues can be very dynamic in your data science ecosystem. Reviewing a log file can be your first line of defense in pinpointing performance issues with optimizing your inference. + +The DeepSparse Engine ships with diagnostic logging so you can capture real-time monitoring information at model runtime and self-diagnose issues. If you are seeking technical support, we recommend capturing log information first, as described below. You can decide what to share, whether certain parts of the log or the entire content. + +**Note:** Our logs may reveal your inference network’s macro-architecture, including a general list of operators (such as convolution and pooling) and connections between them. Weights, trained parameters, or dataset parameters will not be captured. Consult Neural Magic’s various legal policies at [https://neuralmagic.com/legal/](https://neuralmagic.com/legal/) which include our privacy statement and software agreements. Your use of the software serves as your consent to these practices. + +## Performance Tuning + +An initial decision point to make in troubleshooting performance issues before enabling logs is whether to prevent threads from migrating from their cores. The default behavior is to disable thread binding (or pinning), allowing your OS to manage the allocation of threads to cores. There is a performance hit associated with this if the DeepSparseEngine is the main process running on your machine. If you want to enable thread binding for the possible performance benefit, set: + +```bash + NM_BIND_THREADS_TO_CORES=1 +``` + +**Note 1:** If the DeepSparse Engine is not the only major process running on your machine, binding threads may hurt performance of the other major process(es) by monopolizing system resources. + +**Note 2:** If you use OpenMP or TBB (Thread Building Blocks) in your application, then enabling thread binding may result in severe performance degradation due to conflicts between Neural Magic thread pool and OpenMP/TBB thread pools. + +## Enabling Logs and Controlling the Amount of Logs Produced by the DeepSparse Engine + +Logs are controlled by setting the `NM_LOGGING_LEVEL` environment variable. + +Specify within your shell one of the following verbosity levels (in increasing order of verbosity: + +`fatal, error, warn,` and `diagnose` with `diagnose` as a common default for all logs that will output to stderr: + +```bash + NM_LOGGING_LEVEL=diagnose + export NM_LOGGING_LEVEL +``` + +Alternatively, you can output the logging level by + +```bash + NM_LOGGING_LEVEL=diagnose +``` + +To enable diagnostic logs on a per-run basis, specify it manually before each script execution. For example, if you normally run: + +```bash + python run_model.py +``` + +Then, to enable diagnostic logs, run: + +```bash + NM_LOGGING_LEVEL=diagnose python run_model.py +``` + +To enable logging for your entire shell instance, execute within your shell: + +```bash + export NM_LOGGING_LEVEL=diagnose +``` + +By default, logs will print out to the stderr of your process. If you would like to output to a file, add `2> .txt` to the end of your command. + +## Parsing an Example Log + +If you want to see an example log with `NM_LOGGING_LEVEL=diagnose`, a [truncated sample output](example-log.md) is provided at the end of this guide. It will show a super_resolution network, where Neural Magic only supports running 70% of it. + +_Different portions of the log are explained below._ + +### Viewing the Whole Graph + +Once a model is in our system, it is parsed to determine what operations it contains. Each operation is made a node and assigned a unique number Its operation type is displayed: + +```bash + Printing GraphViewer torch-jit-export: + Node 0: Conv + Node 1: Relu + Node 2: Conv + Node 3: Relu + Node 4: Conv + Node 5: Relu + Node 6: Conv + Node 7: Reshape + Node 8: Transpose + Node 9: Reshape +``` + +### Finding Supported Nodes for Our Optimized Engine + +After the whole graph is loaded in, nodes are analyzed to determine whether they are supported by our optimized runtime engine. Notable "unsupported" operators are indicated by looking for `Unsupported [type of node]` in the log. For example, this is an unsupported Reshape node that produces a 6D tensor: + +```bash + [nm_ort 7f4fbbd3f740 >DIAGNOSE< unsupported /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/ops.cc:60] Unsupported Reshape , const shape greater than 5D +``` + +### Compiling Each Subgraph + +Once all the nodes are located that are supported within the optimized engine, the graphs are split into maximal subgraphs and each one is compiled. ​To find the start of each subgraph compilation, look for `== Beginning new subgraph ==`. First, the nodes are displayed in the subgraph: ​ + +```bash + Printing subgraph: + Node 0: Conv + Node 1: Relu + Node 2: Conv + Node 3: Relu + Node 4: Conv + Node 5: Relu + Node 6: Conv +``` + +Simplifications are then performed on the graph to get it in an ideal state for complex optimizations, which are logged: + +```bash +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:706] == Translating subgraph NM_Subgraph_1 to NM intake graph. +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:715] ( L1 graph + ( values: + (10 float [ 1, 64, 224, 224 ]) + (11 float [ 1, 64, 224, 224 ]) + (12 float [ 1, 64, 224, 224 ]) + (13 float [ 1, 32, 224, 224 ]) + (14 float [ 1, 32, 224, 224 ]) + (15 float [ 1, 9, 224, 224 ]) + (9 float [ 1, 64, 224, 224 ]) + (conv1.bias float [ 64 ]) + (conv1.weight float [ 64, 1, 5, 5 ]) + (conv2.bias float [ 64 ]) + (conv2.weight float [ 64, 64, 3, 3 ]) + (conv3.bias float [ 32 ]) + (conv3.weight float [ 32, 64, 3, 3 ]) + (conv4.bias float [ 9 ]) + (conv4.weight float [ 9, 32, 3, 3 ]) + (input float [ 1, 1, 224, 224 ]) + ) + ( operations: + (Constant conv1.bias (constant float [ 64 ])) + (Constant conv1.weight (constant float [ 64, 1, 5, 5 ])) + (Constant conv2.bias (constant float [ 64 ])) + (Constant conv2.weight (constant float [ 64, 64, 3, 3 ])) + (Constant conv3.bias (constant float [ 32 ])) + (Constant conv3.weight (constant float [ 32, 64, 3, 3 ])) + (Constant conv4.bias (constant float [ 9 ])) + (Constant conv4.weight (constant float [ 9, 32, 3, 3 ])) + (Input input (io 0)) + (Conv input -> 9 (conv kernel = [ 64, 1, 5, 5 ] bias = [ 64 ] padding = {{2, 2}, {2, 2}} strides = {1, 1})) + (Elementwise 9 -> 10 (calc Relu)) + (Conv 10 -> 11 (conv kernel = [ 64, 64, 3, 3 ] bias = [ 64 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) + (Elementwise 11 -> 12 (calc Relu)) + (Conv 12 -> 13 (conv kernel = [ 32, 64, 3, 3 ] bias = [ 32 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) + (Elementwise 13 -> 14 (calc Relu)) + (Conv 14 -> 15 (conv kernel = [ 9, 32, 3, 3 ] bias = [ 9 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) + (Output 15 (io 0)) + ) +) +``` + +### Determining the Number of Cores and Batch Size + +This log detail describes the batch size and number of cores that Neural Magic is optimizing against. Look for `== Compiling NM_Subgraph` as in this example: + +```bash +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:723] == Compiling NM_Subgraph_1 with batch size 1 using 18 cores. +``` + +### Obtaining Subgraph Statistics + +Locating `== NM Execution Provider supports` shows how many subgraphs we compiled and what percentage of the network we managed to support running: + +```bash +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:122] Created 1 compiled subgraphs. +[nm_ort 7f4fbbd3f740 >DIAGNOSE< validate_minimum_supported_fraction /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/utility/graph_util.cc:321] == NM Execution Provider supports 70% of the network +``` + +### Viewing Runtime Execution Times + +​For each subgraph Neural Magic optimizes, the execution time is reported by `ORT NM EP compute_func:` for each run as follows: + +```bash +​[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:265] ORT NM EP compute_func: 6.478 ms +``` diff --git a/docs/old/debugging-optimizing/example-log.md b/docs/old/debugging-optimizing/example-log.md new file mode 100644 index 0000000000..6905766f96 --- /dev/null +++ b/docs/old/debugging-optimizing/example-log.md @@ -0,0 +1,113 @@ + + +# Example Log, Verbose Level = diagnose + +The following is an example log with `NM_LOGGING_LEVEL=diagnose` running a super_resolution network, where we only support running 70% of it. Different portions of the log are explained in [Parsing an Example Log.](diagnostics-debugging.md#parsing-an-example-log) + +```bash +onnx_filename : test-models/cv-resolution/super_resolution/none-bsd300-onnx-repo/model.onnx +[ INFO neuralmagic.py: 112 - neuralmagic_create() ] Construct network from ONNX = test-models/cv-resolution/super_resolution/none-bsd300-onnx-repo/model.onnx +NeuralMagic WAND version: 1.0.0.96ce2f6cb23b8ab377012ed9ef38d3da3b9f5313 (optimized) (system=avx512, binary=avx512) +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:104] == NMExecutionProvider::GetCapability == +Printing GraphViewer torch-jit-export: +Node 0: Conv +Node 1: Relu +Node 2: Conv +Node 3: Relu +Node 4: Conv +Node 5: Relu +Node 6: Conv +Node 7: Reshape +Node 8: Transpose +Node 9: Reshape +​ +[nm_ort 7f4fbbd3f740 >DIAGNOSE< unsupported /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/ops.cc:60] Unsupported Reshape , const shape greater than 5D +[nm_ort 7f4fbbd3f740 >DIAGNOSE< construct_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:595] == Constructing subgraphs from graph info +[nm_ort 7f4fbbd3f740 >WARN< construct_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:604] Cannot support patterns, defaulting to non-pattern-matched subgraphs +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:644] == Beginning new subgraph == +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:667] Runtime inputs for subgraph: +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:679] input (required) +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:684] Printing subgraph: +Node 0: Conv +Node 1: Relu +Node 2: Conv +Node 3: Relu +Node 4: Conv +Node 5: Relu +Node 6: Conv +​ +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:706] == Translating subgraph NM_Subgraph_1 to NM intake graph. +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:715] ( L1 graph + ( values: + (10 float [ 1, 64, 224, 224 ]) + (11 float [ 1, 64, 224, 224 ]) + (12 float [ 1, 64, 224, 224 ]) + (13 float [ 1, 32, 224, 224 ]) + (14 float [ 1, 32, 224, 224 ]) + (15 float [ 1, 9, 224, 224 ]) + (9 float [ 1, 64, 224, 224 ]) + (conv1.bias float [ 64 ]) + (conv1.weight float [ 64, 1, 5, 5 ]) + (conv2.bias float [ 64 ]) + (conv2.weight float [ 64, 64, 3, 3 ]) + (conv3.bias float [ 32 ]) + (conv3.weight float [ 32, 64, 3, 3 ]) + (conv4.bias float [ 9 ]) + (conv4.weight float [ 9, 32, 3, 3 ]) + (input float [ 1, 1, 224, 224 ]) + ) + ( operations: + (Constant conv1.bias (constant float [ 64 ])) + (Constant conv1.weight (constant float [ 64, 1, 5, 5 ])) + (Constant conv2.bias (constant float [ 64 ])) + (Constant conv2.weight (constant float [ 64, 64, 3, 3 ])) + (Constant conv3.bias (constant float [ 32 ])) + (Constant conv3.weight (constant float [ 32, 64, 3, 3 ])) + (Constant conv4.bias (constant float [ 9 ])) + (Constant conv4.weight (constant float [ 9, 32, 3, 3 ])) + (Input input (io 0)) + (Conv input -> 9 (conv kernel = [ 64, 1, 5, 5 ] bias = [ 64 ] padding = {{2, 2}, {2, 2}} strides = {1, 1})) + (Elementwise 9 -> 10 (calc Relu)) + (Conv 10 -> 11 (conv kernel = [ 64, 64, 3, 3 ] bias = [ 64 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) + (Elementwise 11 -> 12 (calc Relu)) + (Conv 12 -> 13 (conv kernel = [ 32, 64, 3, 3 ] bias = [ 32 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) + (Elementwise 13 -> 14 (calc Relu)) + (Conv 14 -> 15 (conv kernel = [ 9, 32, 3, 3 ] bias = [ 9 ] padding = {{1, 1}, {1, 1}} strides = {1, 1})) + (Output 15 (io 0)) + ) +) +​ +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:723] == Compiling NM_Subgraph_1 with batch size 1 using 18 cores. +[7f4fbbd3f740 >DIAGNOSE< allocate_buffers_pass ./src/include/wand/engine/units/planner.hpp:49] compiler: total buffer size = 25690112/33918976, ratio = 0.757396 +[nm_ort 7f4fbbd3f740 >DIAGNOSE< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:644] == Beginning new subgraph == +[nm_ort 7f4fbbd3f740 >WARN< supported_subgraphs /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/supported/subgraphs.cc:652] Filtered subgraph was empty, ignoring subgraph. +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:122] Created 1 compiled subgraphs. +[nm_ort 7f4fbbd3f740 >DIAGNOSE< validate_minimum_supported_fraction /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/utility/graph_util.cc:321] == NM Execution Provider supports 70% of the network +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:129] == End NMExecutionProvider::GetCapability == +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:140] == NMExecutionProvider::Compile == +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:157] Graph #0: 1 inputs and 1 outputs +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:276] == End NMExecutionProvider::Compile == +Generating 1 random inputs + -- 1 random input of shape = [1, 1, 224, 224] +[ INFO execute.py: 242 - nm_exec_test_iters() ] Starting tests +[ INFO neuralmagic.py: 121 - neuralmagic_execute() ] Executing TEST_1 +[ INFO neuralmagic.py: 124 - neuralmagic_execute() ] [1] input_data.shape = (1, 1, 224, 224) +[ INFO neuralmagic.py: 126 - neuralmagic_execute() ] -- START +[nm_ort 7f4fbbd3f740 >DIAGNOSE< operator() /home/jdoe/code/nyann/src/onnxruntime_neuralmagic/nm_execution_provider.cc:265] ORT NM EP compute_func: 6.478 ms +[ INFO neuralmagic.py: 130 - neuralmagic_execute() ] -- FINISH +[ INFO neuralmagic.py: 132 - neuralmagic_execute() ] [output] output_data.shape = (1, 1, 672, 672) +``` diff --git a/docs/old/debugging-optimizing/index.rst b/docs/old/debugging-optimizing/index.rst new file mode 100644 index 0000000000..1491e91049 --- /dev/null +++ b/docs/old/debugging-optimizing/index.rst @@ -0,0 +1,25 @@ +.. + Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +======================== +Debugging and Optimizing +======================== + +.. toctree:: + :maxdepth: 3 + + numactl-utility + diagnostics-debugging + example-log diff --git a/docs/old/debugging-optimizing/numactl-utility.md b/docs/old/debugging-optimizing/numactl-utility.md new file mode 100644 index 0000000000..b5f711cc88 --- /dev/null +++ b/docs/old/debugging-optimizing/numactl-utility.md @@ -0,0 +1,88 @@ + + +# Using the numactl Utility to Control Resource Utilization with the DeepSparse Engine + +The DeepSparse Engine achieves better performance on multiple-socket systems as well as with hyperthreading disabled; models with larger batch sizes are likely to see an improvement. One standard way of controlling compute/memory resources when running processes is to use the **numactl** utility. **numactl** can be used when multiple processes need to run on the same hardware but require their own CPU/memory resources to run optimally. + +To run the DeepSparse Engine on a single socket (N) of a multi-socket system, you would start the DeepSparse Engine using **numactl**. For example: + +```bash + numactl --cpunodebind N +``` + +To run the DeepSparse Engine on multiple sockets (N,M), run: + +```bash + numactl --cpunodebind N,M +``` + +It is advised to also allocate memory from the same socket on which the engine is running. So, `--membind` or `--preferred` should be used when using `--cpunodebind.` For example: + +```bash + numactl --cpunodebind N --preferred N + or + numactl --cpunodebind N --membind N +``` + +The difference between `--membind` and `--preferred` is that `--preferred` allows memory from other sockets to be allocated if the current socket is out of memory. `--membind` does not allow memory to be allocated outside the specified socket. + +For more fine-grained control, **numactl** can be used to bind the process running the DeepSparse Engine to a set of specific CPUs using `--physcpubind`. CPUs are numbered from 0-N, where N is the maximum number of logical cores available on the system. On systems with hyper-threading (or SMT), there may be more than one logical thread per physical CPU. Usually, the logical CPUs/threads are numbered after all the physical CPUs/threads. For example, in a system with two threads per CPU and N physical CPUs, the threads for a particular CPU (K) will be K and K+N for all 0<=K<N. The DeepSparse Engine currently works best with hyper-threading/SMT disabled, so only one set of threads should be selected using **numactl**, i.e., 0 through (N-1) or N through (N-1). + +Similarly, for a multi-socket system with N sockets and C physical CPUs per socket, the CPUs located on a single socket will range from K*C to ((K+1)*C)-1 where 0<=K<N. For multi-socket, multi-thread systems, the logical threads are separated by N*C. For example, for a two socket, two thread per CPU system with 8 cores per CPU, the logical threads for socket 0 would be numbered 0-7 and 16-23, and the threads for socket 1 would be numbered 8-15 and 24-31. + +Given the architecture above, to run the DeepSparse Engine on the first four CPUs on the second socket, you would use the following: + +```bash + numactl --physcpubind 8-11 --preferred 1 +``` + +Appending `--preferred 1` is needed here since the DeepSparse Engine is being bound to CPUs on the second socket. + +Note: When running on multiple sockets using a batch size that is evenly divisible by the number of sockets will yield the best performance. + + +## DeepSparse Engine and Thread Pinning + +When using **numactl** to specify which CPUs/sockets the engine is allowed to run on, there is no restriction as to which CPU a particular computation thread is executed on. A single thread of computation may run on one or more CPUs during the course of execution. This is desirable if the system is being shared between multiple processes so that idle CPU threads are not prevented from doing other work. + +However, the engine works best when threads are pinned (i.e., not allowed to migrate from one CPU to another). Thread pinning can be enabled using the `NM_BIND_THREADS_TO_CORES` environment variable. For example: + +```bash + NM_BIND_THREADS_TO_CORES=1 + or + export NM_BIND_THREADS_TO_CORES=1 +``` + +`NM_BIND_THREADS_TO_CORES` should be used with care since it forces the DeepSparse Engine to run on only the threads it has been allocated at startup. If any other process ends up running on the same threads, it could result in a major degradation of performance. + +**Note:** The threads-to-cores mappings described above are specific to Intel only. AMD has a different mapping. For AMD, all the threads for a single core are consecutive, i.e., if each core has two threads and there are N cores, the threads for a particular core K are 2*K and 2*K+1. The mapping of cores to sockets is also straightforward, for a N socket system with C cores per socket, the cores for a particular socket S are numbered S*C to ((S+1)*C)-1. + +## Additional Notes + +`numactl --hardware` + +Displays the inventory of available sockets/CPUs on a system. + +`numactl --show` + +Displays the resources available to the current process. + +For further details about these and other parameters, see the man page on **numactl**: + +```bash + man numactl +``` diff --git a/docs/old/favicon.ico b/docs/old/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..784590876f3eda97e984282a4bf2e688030c6a99 GIT binary patch literal 15406 zcmeI0d9apM8NfflaK%s&H}GC%k<0~S5g6p6s3dBoW{OOBHB-yfG(<&eM5fVjEXpON zV1Ge>iSKtM2Y2^B1X1hV@5Oki}ybQlujVR%_`BKbzR3D&_0ke9~N@d4Zbo8bcZ3;Y)xZ+=Z{VS|JCsdq~o36 zdEY7bEh7H{{Ht7lom~5EcWgXfA6^FQ%kA3E&H5w_wD5DMTseY=1hk84f?n4um< zgYl`#vdJ>#2byBv;y6>txv`hDZm``;4(@5uNjNYD5`X*&hR!CG(+ybEoiUtP}S8TZ|A z?wB{I9|;%2#;C7W{m$$CxdHs}$hxLc@D9ZD zSbhk4!TlM1o{dZ4M2O>XOY{$C?08rQv2ORnGhPQzC4I_mfb-xA_$IhF`pLdL{dbo8 zgC2EX4gL-aZRVcw@4%rDzpFvU_ih~A4B9Dk;2>}x9h>Fo<;rWyQ+WF{wHV~3Vv_*huM&xN9*^&0}zk*{-^7Y z`@u&x?1-JM$QXc|z`9-C1t;jGqP#;5ToO3HdVGoF^-7N8{y?#)~laarzDc zV}Le=_Mm?iZNtEGvp(t%a!=B>FH}m}_h$&>;@mxOF9yT&Wf}Dw7y80fNYA+A?}u(s zZQ23B>%emo>$mJ4Jp|rU&+k!SGG3?`nEM`1cMeqYJ>IFFcvz4Nt>^Q@Lrf- z2%hm;l771dQorWy=MZEcgA<@F)90D&b)x8gPr1pn)zay>$*Wi1!9D?72jP{=ovHkWrzuy$$jiOj{s$+%+VG{)X z=6UwpHxKrNMu^9Ra-RJgk)H%@VZVECm~`(`|5dVo0b_bXBLth{-1DF1|F*LKQ_O!V zEX>irE!*$joeQfV%kRODMdxP$W4DI?yQ6ms#P>AVo95e^{l;!%uy0CgYgWA)`_IXl z%k%GdSsH&@d;WVd*Bq$E?_n+5g8jaALm+)$f{o^RcVKJV?>#*Uo&(>$*yd2)O#9=I z?z8>1*nbw|YdJs0N8j(aAg6zc;qbgbyILH&~9c91!{W5$74ul`UdPr@yuI#IDHfXzX@?FT1EwcYH#?(X6 z{jKKwXwRQuF!Y9BL(zB4Qu>brzcufGqIbaG82uqD{YfkAm(Rc-A>6&xpMDFt z&cmP&{1)TiC~OLZ4Pz!<6h_oL*NhKdP0Ai`x|@` z41#(%9iD{vZr5VJwtIH{pOJH~gw#L5CgZs4+#8Mo&#P^rKh58!?=)zDXJ7%0g#E!- z^&vQ>8vDOimQ8hNe>b=UitefB*86EZ8V}l*&K2se$@jMbjw+W!pE1~Y-4oQk2SJHPb>;awyV{0lC>Yj^7paCXA)_LDR`+YD12EzGJ z2U+%e4~y(yLBI0_{b|08z6S8j+z#s?l?iqIei!TmLt!z5zBE^MDd^W7pck~p{&e0@ z-^_fj{SfF4&9DhVf0}!ieJc(C&xQF&@UHs(pUQ{2dYWJ~xSr4+@=E>>a&hjQvq9U9 zF%N-lu}oa{&6*6_doO73eE1T$j%n}~#AD*Jd$9<@eUAHDYX3*fGXwU7G4L{YrsFx| z@(TJ+0nhO~2?}oy@ za0e8{nDu3u9^`3q-+l&bq0&0s+jC$q@LgF3d3%v(zw0(uxc`H|7*lk&-lTsr`2OZ4 zzd097cWR8bU(30f;d;F$a2}QnfJsZHgZ~*w`couB4 zFSdUvb=PWq{1$j7Vt>SCePfJqe_ESd^KkGj^zOv-r)Bj$31>iGxB`|#u-~_6EO>su z4{IQuBh-yazX$!H4Yef4_kfGxJqSATa%EH$(P=*v&VscN_TnAN-WAtaDRn`> qGdTz>Z;j;naNj-eFO|#H*pBEnlk{C5=m%TNkK2qh{%=Uzd*FZjq!cdz literal 0 HcmV?d00001 diff --git a/docs/old/index.rst b/docs/old/index.rst new file mode 100644 index 0000000000..1df368ea01 --- /dev/null +++ b/docs/old/index.rst @@ -0,0 +1,45 @@ +.. + Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +.. mdinclude:: ../README.md + +.. toctree:: + :maxdepth: 3 + :caption: General + + source/hardware + +.. toctree:: + :maxdepth: 3 + :caption: Performance + + debugging-optimizing/index + source/scheduler + +.. toctree:: + :maxdepth: 2 + :caption: API + + source/c++api-overview + api/deepsparse + +.. toctree:: + :maxdepth: 3 + :caption: Connect Online + + Bugs, Feature Requests + Deep Sparse Community Slack + Neural Magic GitHub + Neural Magic Docs diff --git a/docs/old/source/c++api-overview.md b/docs/old/source/c++api-overview.md new file mode 100644 index 0000000000..2143124a53 --- /dev/null +++ b/docs/old/source/c++api-overview.md @@ -0,0 +1,223 @@ + + +# C++ API Overview + +You can use a C++ API in `libdeepsparse.so` as the interface between your application and the [Neural Magic DeepSparse Engine.](https://docs.neuralmagic.com/deepsparse/) You would start with a model that has been exported in ONNX format, client code on your server. Your application will write data to input tensors, execute the DeepSparse Engine for inference results, and read output tensors for result data. + +A simple demo with code is provided to invoke the DeepSparse Engine using the C++ API. Once you have installed the DeepSparse Engine, you will be ready to use the C++ API and take advantage of the library `libdeepsparse`. With `libdeepsparse`, you can run the demo by building and running `demo.bin`. + +You can find more information about the engine at [https://docs.neuralmagic.com/deepsparse/.](https://docs.neuralmagic.com/deepsparse/) + + +## Prerequisites + +The following is required to build and run the demo. + +**OS** - Our engine binary is manylinux2014-compatible and built on CentOS 7. Linux distributions such as Ubuntu 20.04, which are compatible with manylinux2014, should support it. + + +**Hardware** - The utility arch.bin is included in deepsparse_api_demo.tar.gz. Run `arch.bin` to check hardware support. The “isa” field in the output states whether you are running on a machine that supports the avx512 or avx2 instruction set. + +``` +./arch.bin +``` + +**Installed Tools** - These tools are assumed to be installed to build the demo: + +* clang++ 11 or g++ 10 +* C++17 standard libraries + + +## Demo: Obtaining, Building, and Running + +To download a demo file, use the curl command: + + +``` +curl https://github.com/neuralmagic/deepsparse/releases/download/v0.8.0/deepsparse_api_demo.tar.gz --output deepsparse_api_demo.tar.gz +``` +or visit our [DeepSparse Engine Release page](https://github.com/neuralmagic/deepsparse/releases/tag/v0.8.0) and download `deepsparse_api_demo.tar.gz` from there. + + +Once you obtain the file deepsparse-api.tar.gz, follow these steps to unpack and build the demo: + should be either avx2 or avx512 based on the result of arch.bin. + +``` +tar xzvf deepsparse_api_demo.tar.gz +cd _deepsarse_api_demo +make +./bin//demo.bin ./data/model.onnx +``` + +**Note**. The makefile defaults to avx512 support. For tar files and machines with avx2 instruction set, to build, you must set the ARCH flag on the command line when you make the demo. + +``` +make ARCH=avx2 +``` + +## C++ API + +This document discusses the high-level overview of the API. For the exact signatures and classes of the API, review the header files under + +``` +deepsparse_api_demo/include/libdeepsparse/ +``` + +The API consists of five C++ header files: + +``` +compiler.hpp +config.hpp +dimensions.hpp +tensor.hpp +engine.hpp +``` + +### Compiler + +Helper header to export the API to a shared object. + +### Config + +This file contains a structure that is used in the call to create the engine. The **engine_config_t** fields are: + +**model_file_path** - This should be a file path to the model in the ONNX file format. See [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) on proper ONNX model files. + +**batch_size** - The batch size refers to the process of concatenating input and output tensors into a contiguous batched tensor. See [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) about the performance trade-offs of batching. + +**num_threads** - The number of worker threads for the engine to use while executing a model. If left as 0, the engine will select a default number of worker threads. + + +### Dimensions + +Review the [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) about expected input and output tensors. The **dimensions_t** object describes the extent or count of elements along each dimension of a tensor_t. + + +### Tensor + +A tensor is an n-dimensional array of data elements and metadata. An element is a concrete value of a supported primitive type (for example, an element of type float or uint8). + +**tensor_t** - members. + +**element_type()** - Return an enumeration value of the concrete type of a tensor element. + +**dims()** - Return a dimension_t that specifies the extents of the tensor. + +**data()** - Pointer to the first element of the tensor data memory. + +The data that tensor_t points to may have either of two different lifetime models for the memory. + +1. The tensor owns the data memory. +2. The tensor aliases a pointer to the memory location. The lifetime of the data memory is delegated to a lambda passed to the tensor. For externally-owned data pointers, the lambda can be a no-op. + +_Code Example: Memory lifetime allocated and owned by tensor_t_ + + +``` +#include "deepsparse/engine.hpp" + +void my_engine_inference() +{ + // ... + { + // create a float tensor with y and x 10 by 10 + auto t = deepsparse::create_tensor( + deepsparse::element_type_t::float32, + deepsparse::dimensions_t{1, 1, 1, 10, 10}); + + float* p_raw = t.data(); // 100 floats + // read and write values to p_raw and send tensor to the engine. + + + } + // data memory of t deallocated when t goes out of scope + // ... +} +``` + +_Code Example: Tensor data memory lifetime is delegated to lambda_ + +``` +#include +#include "deepsparse/engine.hpp" + +void my_engine_inference() +{ + // tensor data memory MUST aligned. + // dims must match total number of elements below + float* p_raw = static_cast(aligned_alloc( + deepsparse::minimum_required_alignment(), + sizeof(float) * 100)); + { + + // policy - tensor is just an alias to memory + auto alias_dealloc = [](void* p) {}; + + // policy - tensor destructor calls dealloc + auto owned_dealloc = [](void* p) { + free(p); + // or cast p to pointer to well known type + // and manually call your own delete + }; + + // create a alias float tensor with y and x 10 by 10 + auto t = deepsparse::tensor_t( + deepsparse::element_type_t::float32, + deepsparse::dimensions_t{1, 1, 1, 10, 10}, + p_raw, + alias_dealloc // lambda invoked in object destructor + ); + + // read and write p_raw and send to the engine. + } + +} +``` + +### Engine + +The engine API is the primary interface for external code to load and run the [Neural Magic DeepSparse Engine](https://docs.neuralmagic.com/deepsparse/). + +**engine_t()** - Construct an instance of the engine with the configuration struct. The config file path to the model is used to load and compile the model during the constructor. On error, exceptions will be thrown to the calling code. + +**execute()** - This is the primary method to run the inference specified in the ONNX model. The engine executes the model with the input tensors and returns output tensors. + +**Input and output getters** - these methods are used to get metadata on the input and output tensors of the loaded model. + + +#### Utility Functions + +**generate_random_inputs()** - Once the engine is instantiated and has a model loaded, this function can use the model’s definition of input tensors to generate a set of tensors with random values with the correct type and shape. Random input can be used to test the engine or its performance. + +**load_inputs()** - Creates a collection of tensor_t from the model input files. The input files are expected to be in the NumPy array format. See the [DeepSparse Engine documentation](https://docs.neuralmagic.com/deepsparse/) for more on input file support. + +**max_diff()** - Returns the largest absolute elementwise difference between two tensors. This function is used to compare the output result with the expected output when testing the engine. + +**allclose()** - Returns true if two tensors are less elementwise different than the specified absolute and relative tolerances. + + +### User Calling Code + +The expected workflow is that the calling code will create one engine per model. The model path will specify an ONNX file. During creation, the engine will load and compile the model from the file. + +The input tensors will be created by the calling code. The tensor dimensions and types must match the corresponding dimensions and types of the model inputs. And, the number of tensors must be the same as the number of model inputs. + +Call engine execute() with the collection of input tensors. During the execute() call, the engine will do the inference over the ONNX model with the input tensors and return the output tensors. + +The output tensors’ data members can then be read to extract values and results. + +For a detailed example of creating, loading, and running an engine, see the code in `deepsparse_api_demo/src/demo.cpp` diff --git a/docs/old/source/hardware.md b/docs/old/source/hardware.md new file mode 100644 index 0000000000..7d79a3af1a --- /dev/null +++ b/docs/old/source/hardware.md @@ -0,0 +1,28 @@ + + +## Hardware Support + +With support for AVX2, AVX-512, and VNNI instruction sets, the DeepSparse Engine is validated to work on x86 Intel (Haswell generation and later) and AMD CPUs running Linux. Mac and Windows require running Linux in a Docker or virtual machine. + +Here is a table detailing specific support for some algorithms over different microarchitectures: + +| x86 Extension | Microarchitectures | Activation Sparsity | Kernel Sparsity | Sparse Quantization | +|:----------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------------:|:---------------:|:-------------------:| +| [AMD AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX2) | [Zen 2,](https://en.wikipedia.org/wiki/Zen_2) [Zen 3](https://en.wikipedia.org/wiki/Zen_3) | not supported | optimized | not supported | +| [Intel AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX2) | [Haswell,](https://en.wikipedia.org/wiki/Haswell_%28microarchitecture%29) [Broadwell,](https://en.wikipedia.org/wiki/Broadwell_%28microarchitecture%29) and newer | not supported | optimized | not supported | +| [Intel AVX-512](https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512) | [Skylake](https://en.wikipedia.org/wiki/Skylake_%28microarchitecture%29) [Cannon Lake,](https://en.wikipedia.org/wiki/Cannon_Lake_%28microarchitecture%29) and newer | optimized | optimized | emulated | +| [Intel AVX-512](https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512) VNNI (DL Boost) | [Cascade Lake.](https://en.wikipedia.org/wiki/Cascade_Lake_%28microarchitecture%29) [Ice Lake,](https://en.wikipedia.org/wiki/Ice_Lake_%28microprocessor%29) [Cooper Lake,](https://en.wikipedia.org/wiki/Cooper_Lake_%28microarchitecture%29) [Tiger Lake](https://en.wikipedia.org/wiki/Tiger_Lake_%28microprocessor%29) | optimized | optimized | optimized | diff --git a/docs/old/source/icon-deepsparse.png b/docs/old/source/icon-deepsparse.png new file mode 100644 index 0000000000000000000000000000000000000000..5e0580f1e5423a969364204df0f0323de3f04531 GIT binary patch literal 2029 zcmY*adpwl+8h>X7<1#~5Xh_RgMr1B-X%3?qQsgr0)*yDq{W>#i24j(2!fHra6|u(3 zB^9kgp~BQA#w9BumYg=1(?*%*xH~g)>^Z;p^SsaZ_q^Zl_xU})f8HO-F2~{W8u9=D zz#UH5xrwu$ICN!Y#BY^n`~%_yV!0hB0d;-*=fs=V0rhkH=i6lW)VAR`)03t|KWqdCz`i3+gd;KeXHm`&5> zL`TtCc#bu4n}HW&Ng0FG-lni4t&tRGvbHTFCRp1XZHC4o3G&+7+Ey_^A$T`C`yIJ> zXN?SFvzd4dCO$qM9dC+e#DrpSM~)o9U`;S4CdOihF)M-2rg4nvEZr|A|Mjs8W(CHC zGuhz`y0*lZ7Ql#QTO*N@M!W0FIoaVMe|Ms@cE%D9h>?83;LupiuD4ifC8^?FW5R>Q zohAMRoYgk-|7tsZtT2-1|8tlxlWteVqY~t;FuT`Akay}tc>{oSse>KSgCf^hn8@{W z-<##@krknAM)R3|;Z{{%VRv)Pu{w;~e4RU+&oR%xN&1d9)jN^>NIA`nepqf zK++cUG(^3FFrxJOji#kyUe?P2kmF=Z>OX5QrP>`;3s*TU|2s{c;)@qecrx__sc{tO z)hr%)fiDi@gNuDs}=y2^cs=+*u@ki!Jm4S#Ei^_dn;V!hgwyA(sen&DAq*Z7K zF>$9x3<=PWaX79^1rpmzFT-p3pW`3(PQg7l7`tn(51&b}Ig|?Jsgcf{!m+A+CLq_^ z-Pr~d8=XiJ%C{q00~ASo@I&)+9DEVMKHDhLnO$(Y4~GdA2AVfdlcR9GA7WG)Z=ICG z`i2q=Z36lB2T|t^`#oOaO+HB?y}Ehk_`n%L=y~^RnwQLb0|b9QYy#`2ch~7a-}kiW ziOffrlp^)l@~itou3w?QVcdbwU+Vp}GDyqER%Qb-E=zB%W*>%`87L&~tDz9fPp!TJ zdo9smcKKtEq)Ls>Tb7LquCCz_^qKytQqdk*rnL<1qUI24%Hc=Bi$g}G4-7HB&Cyy0 z3pG7Ft!ZIl&pz7*?pzNSLDo&8R{4~JhS0OlO@g$D^c&tc$%~rdnzs-hgStF zCwA{VJOXmH(z4HflaaGF7bsIa;gcQh=e$ND=iR(_F7Nx)RdNWYmUttScR)Wa1%k^C z@qXG|W{Pa6?6vuPkqHxesXbbi&76Bu5O`0iGZ_np2@)#?4Fe+uPU{bs7V+z%fFAayV#&AI(9f!e<4j@+0S&spwt>|my3+4j;vaJChqdsSoH1ch zQSVgT^2~hck}*a7z?uQQxCm)rY6@!N+b^2Yr5RCMDC;6Lz9~CI36Zt5eBnj@g#l9I zWp<3IS-Nj+$p{ZRaA_I!m*%qK9lLL3SY2G|Fpm8PX+`j$b7oq)zsB}_ z9O6lQrHPhB`YvWtYwp5W@jr2l;dwa`kxnW|MNvNYHe?w{T6Fm{w#c`+6CTk058MvuL{wR6~3R}s8e09V;yVi zdG0wo0{E?Ha1=86x3SV}g1iO)hsEnYtnTVqOGPhYP|uVVkj1|{^@{~DSH;t)^j$sl z!lP7k5|mm)hpio=YIy17ZNsFAvno2f(1 z4AwgYMVT*!bZL|-)bqhz;OS9RL*EdE^TZwj?;h=~CHj{{q3^f`>ywPl^egjXz1mcU z-|WA$=qmMc2BbQ!CO^^iH0m#>I|V_ReOV})vKg_fJ+X$PXD#DF%Z5x@^hudgH#n|DuvCmc`cHEp2IO0yE+mz?LR4|NaFwi literal 0 HcmV?d00001 diff --git a/docs/old/source/multi-stream.png b/docs/old/source/multi-stream.png new file mode 100644 index 0000000000000000000000000000000000000000..769133fb0d5d903506488ec6fbb584813cf27bc2 GIT binary patch literal 31497 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 literal 0 HcmV?d00001 diff --git a/docs/old/source/scheduler.md b/docs/old/source/scheduler.md new file mode 100644 index 0000000000..c856b037c5 --- /dev/null +++ b/docs/old/source/scheduler.md @@ -0,0 +1,61 @@ + + +## Serial or Concurrent Inferences + +Schedulers are special system software which handle the distribution of work across cores in parallel computation. The goal of a good scheduler is to ensure that while work is available, cores aren’t sitting idle. On the contrary, as long as parallel tasks are available, all cores should be kept busy. + +In most use cases, the default scheduler is the preferred choice when running inferences with the DeepSparse Engine. It's highly optimized for minimum per-request latency, using all of the system's resources provided to it on every request it gets. Often, particularly when working with large batch sizes, the scheduler is able to distribute the workload of a single request across as many cores as it's provided. + +single stream diagram + +_Single stream scheduling; requests execute serially by default_ + +However, there are circumstances in which more cores does not imply better performance. If the computation can't be divided up to produce enough parallelism (while maximizing use of the CPU cache), then adding more cores simply adds more compute power with little to apply it to. + +An alternative, "multi-stream" scheduler is provided with the software. In cases where parallelism is low, sending multiple requests simultaneously can more adequately saturate the available cores. In other words, if speedup can't be achieved by adding more cores, then perhaps speedup can be achieved by adding more work. + +If increasing core count doesn't decrease latency, that's a strong indicator that parallelism is low in your particular model/batch-size combination. It may be that total throughput can be increased by making more requests simultaneously. Using the [deepsparse.engine.Scheduler API,](https://docs.neuralmagic.com/deepsparse/api/deepsparse.html) the multi-stream scheduler can be selected, and requests made by multiple Python threads will be handled concurrently. + +multi stream diagram + +_Multi-stream scheduling; requests execute in parallel and may utilize hardware resources better_ + +Whereas the default scheduler will queue up requests made simultaneously and handle them serially, the multi-stream scheduler allows multiple requests to be run in parallel. The num_streams argument to the Engine/Context classes controls how the multi-streams scheduler partitions up the machine. Each stream maps to a contiguous set of hardware threads. By default, only one hyperthread per core is used. There is no sharing amongst the partitions and it is generally good practice make sure that the num_streams value evenly divides into your number of cores. By default num_streams is set to multiplex requests across L3 caches. + +Here's an example: Consider a machine with 2 sockets, each with 8 cores. In this case the multi-stream scheduler will create two streams, one per socket by default. The first stream will contain cores 0-7 and the second stream will contain cores 8-15. + +Manually increasing num_streams to 3 will result in the following stream breakdown: threads 0-5 in the first stream, 6-10 in the second, and 11-15 in the last. This is problematic for our two socket system. The second stream (threads 6-10) is straddling both sockets, meaning that each request being serviced by that stream is going to incur a performance penalty each time one of its threads makes a remote memory access. The impact of this penalty will depend on the workload, but it will likely be significant. + +Manually increasing num_streams to 4 is interesting. Here's the stream breakdown: threads 0-3 in the first stream, 4-7 in the second, 8-11 in the third, and 12-15 in the fourth. Each stream is only making memory accesses that are local to its socket which is good. However, the first two and last two streams are sharing the same L3 cache which can result in worse performance due to cache thrashing. Depending on the workload, the performance gain from the increased parallelism may negate this penalty, though. + +The most common use cases for the multi-stream scheduler are where parallelism is low with respect to core count, and where requests need to be made asynchronously without time to batch them. Implementing a model server may fit such a scenario and be ideal for using multi-stream scheduling. + +Depending on your engine execution strategy, enable one of these options by running: + +```python +engine = compile_model(model_path, scheduler="single_stream") +``` + +or + +```python +engine = compile_model(model_path, scheduler="multi_stream", num_streams=None) # None is the default +``` + +or pass in the enum value directly, since` "multi_stream" == Scheduler.multi_stream` + +By default, the scheduler will map to a single stream. diff --git a/docs/old/source/single-stream.png b/docs/old/source/single-stream.png new file mode 100644 index 0000000000000000000000000000000000000000..c324a2045d7103401bc87db2258e3937ce28982e GIT binary patch literal 17490 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 S0nct=VDNPHb6Mw<&;$THg2_Sv literal 0 HcmV?d00001 From b483960a58dc9a193523d0356433bb7a8f109023 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 16:47:49 -0400 Subject: [PATCH 061/149] Update image-classification.md --- docs/use-cases/cv/image-classification.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md index 9916869c4b..f25329b7c6 100644 --- a/docs/use-cases/cv/image-classification.md +++ b/docs/use-cases/cv/image-classification.md @@ -11,7 +11,7 @@ and post-processing steps, allowing you to make requests on raw data and receive - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. -We will walk through an example of each. +We will walk through an example of each using ResNet-50. For a full list of pre-sparsified image classification models, [check out the SparseZoo](https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=classification&page=1). ## Installation Requirements @@ -50,7 +50,7 @@ deepsparse.benchmark \ -b 64 -s sync -nstreams 1 \ -e deepsparse -> Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned90_quant-none +> Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none > Batch Size: 64 > Scenario: sync > Throughput (items/sec): 345.69 @@ -71,10 +71,9 @@ import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" -batch_size = 1 compiled_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size=batch_size # defaults to batch size 1 + batch_size=1 # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes @@ -184,7 +183,7 @@ print(prediction.labels) # labels=['lion, king of beasts, Panthera leo'] ``` ### Cross Use Case Functionality -Check out the Pipeline User Guide for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. @@ -194,7 +193,7 @@ The CLI command below launches an image classification pipeline with a 95% prune ```bash deepsparse.server \ --task image_classification \ - --model_path zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none + --model_path zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none ``` You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. @@ -211,14 +210,15 @@ print(resp.text) # {"labels":[291],"scores":[24.185693740844727]} ``` #### Use Case Specific Arguments -To use the `top_k` argument, create a server configuration file for passing the argument via kwargs. + +To use a use-case specific argument, create a server configuration file for passing the argument via kwargs. This configuration file sets `top_k` classes to 3: ```yaml # image_classification-config.yaml endpoints: - task: image_classification - model: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none + model: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none kwargs: top_k: 3 ``` @@ -242,4 +242,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the [Server User Guide](/user-guide/deepsparse/deepsparse-server) for more details on configuring the Server. +Check out the [Server User Guide](../user-guide/deepsparse-server.md) for more details on configuring the Server. From 0fd2eb1382c5e52aa7cde093757b243038218e59 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 16:48:39 -0400 Subject: [PATCH 062/149] Update image-classification.md --- docs/use-cases/cv/image-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md index f25329b7c6..ef8c4b35eb 100644 --- a/docs/use-cases/cv/image-classification.md +++ b/docs/use-cases/cv/image-classification.md @@ -11,7 +11,7 @@ and post-processing steps, allowing you to make requests on raw data and receive - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. -We will walk through an example of each using ResNet-50. For a full list of pre-sparsified image classification models, [check out the SparseZoo](https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=classification&page=1). +This example uses ResNet-50. For a full list of pre-sparsified image classification models, [check out the SparseZoo](https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=classification&page=1). ## Installation Requirements From d89739bcdeea13e23558bcbf4ab8685d92157a63 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 16:59:09 -0400 Subject: [PATCH 063/149] Update image-classification.md --- docs/use-cases/cv/image-classification.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md index ef8c4b35eb..23666f0442 100644 --- a/docs/use-cases/cv/image-classification.md +++ b/docs/use-cases/cv/image-classification.md @@ -63,7 +63,7 @@ Engine is the lowest-level API for interacting with DeepSparse. As much as possi With Engine, we can compile an ONNX file and run inference on raw tensors. -Here's an example, using a 90% pruned-quantized ResNet-50 trained on `imagenet` from SparseZoo: +Here's an example, using a 95% pruned-quantized ResNet-50 trained on `imagenet` from SparseZoo: ```python from deepsparse import Engine from deepsparse.utils import generate_random_inputs, model_to_path @@ -71,9 +71,10 @@ import numpy as np # download onnx from sparsezoo and compile with batchsize 1 sparsezoo_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95_quant-none" +batch_size = 1 compiled_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX - batch_size=1 # defaults to batch size 1 + batch_size=batch_size # defaults to batch size 1 ) # input is raw numpy tensors, output is raw scores for classes @@ -183,7 +184,7 @@ print(prediction.labels) # labels=['lion, king of beasts, Panthera leo'] ``` ### Cross Use Case Functionality -Check out the [Pipeline User Guide](../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. @@ -242,4 +243,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the [Server User Guide](../user-guide/deepsparse-server.md) for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From 6942c5c3cac1cabf763af14de910e401ac764c1f Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:02:07 -0400 Subject: [PATCH 064/149] Update object-detection-yolov5.md --- docs/use-cases/cv/object-detection-yolov5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md index 0b00eae5b5..39ecbf458c 100644 --- a/docs/use-cases/cv/object-detection-yolov5.md +++ b/docs/use-cases/cv/object-detection-yolov5.md @@ -9,7 +9,7 @@ There are three interfaces for interacting with DeepSparse: - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. -We will walk through an example of each. +This example uses YOLOv5s. For a full list of pre-sparsified object detection models, [check out the SparseZoo](https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=detection&page=1). ## Installation Requirements @@ -262,4 +262,4 @@ print(labels) ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file +Check out the Server User Guide for more details on configuring the Server. From bc28cf1426f5d1ac5538470d72372482e6fb7769 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:05:42 -0400 Subject: [PATCH 065/149] Update object-detection-yolov5.md --- docs/use-cases/cv/object-detection-yolov5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md index 39ecbf458c..494f99c09f 100644 --- a/docs/use-cases/cv/object-detection-yolov5.md +++ b/docs/use-cases/cv/object-detection-yolov5.md @@ -27,11 +27,11 @@ As a baseline, let's check out ONNX Runtime's performance on YOLOv5s. Make sure ```bash deepsparse.benchmark \ - zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none \ + zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/base-none \ -b 64 -s sync -nstreams 1 \ -e onnxruntime -> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none +> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/base-none > Batch Size: 64 > Scenario: sync > Throughput (items/sec): 13.1266 From 2297becc9fb0f67e312f1c77538352517276ab65 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:12:41 -0400 Subject: [PATCH 066/149] Update object-detection-yolov5.md --- docs/use-cases/cv/object-detection-yolov5.md | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md index 494f99c09f..795ecbd8f1 100644 --- a/docs/use-cases/cv/object-detection-yolov5.md +++ b/docs/use-cases/cv/object-detection-yolov5.md @@ -34,12 +34,13 @@ deepsparse.benchmark \ > Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/base-none > Batch Size: 64 > Scenario: sync -> Throughput (items/sec): 13.1266 +> Throughput (items/sec): 12.2369 ``` -ONNX Runtime achieves 13 items/second with batch 64. +ONNX Runtime achieves 12 items/second with batch 64. ### DeepSparse Speedup -Now, let's run DeepSparse on an inference-optimized sparse version of YOLOv5 . This model has been 94% pruned-quantized, while retaining >99% accuracy of the dense baseline on the `coco` dataset. +Now, let's run DeepSparse on an inference-optimized sparse version of YOLOv5s. This model has been 85% pruned and quantized. + ```bash deepsparse.benchmark \ zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned85_quant-none \ @@ -57,7 +58,7 @@ Engine is the lowest-level API for interacting with DeepSparse. As much as possi With Engine, we can compile an ONNX file and run inference on raw tensors. -Here's an example, using a 85% pruned-quantized YOLOv5 from SparseZoo: +Here's an example, using a 85% pruned-quantized YOLOv5s model from SparseZoo: ```python from deepsparse import Engine @@ -93,7 +94,8 @@ Let's start by downloading a sample image: ```bash wget -O basilica.jpg https://raw.githubusercontent.com/neuralmagic/deepsparse/main/src/deepsparse/yolo/sample_images/basilica.jpg ``` -We will use the `Pipeline.create()` constructor to create an instance of an object detection Pipeline with a 96% pruned version of YOLOv5 trained on `coco`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images) is handled by the `Pipeline`. + +We will use the `Pipeline.create()` constructor to create an instance of an object detection Pipeline with a 85% pruned version of YOLOv5s trained on `coco`. We can then pass images to the `Pipeline` and receive the predictions. All the pre-processing (such as resizing the images and running NMS) is handled by the `Pipeline`. ```python from deepsparse import Pipeline @@ -117,7 +119,7 @@ print(pipeline_outputs.labels) ``` ### Use Case Specific Arguments -The Object Detection Pipeline contains additional arguments for configuring a `Pipeline`. +The YOLOv5 pipeline contains additional arguments for configuring a Pipeline. #### Image Shape @@ -189,9 +191,13 @@ print(len(pipeline_outputs_low_conf.boxes[0])) # high threshold inference pipeline_outputs_high_conf = yolo_pipeline(images=images, iou_thres=0.5, conf_thres=0.8) print(len(pipeline_outputs_high_conf.boxes[0])) -# 1 << only one prediction>> +# 1 <> ``` +### Cross Use Case Functionality + +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. + ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. @@ -262,4 +268,4 @@ print(labels) ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring a Server. From ef32b3a985dd95222de09642a1bb925ed10905b9 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:19:19 -0400 Subject: [PATCH 067/149] Update image-segmentation-yolact.md --- docs/use-cases/cv/image-segmentation-yolact.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/use-cases/cv/image-segmentation-yolact.md b/docs/use-cases/cv/image-segmentation-yolact.md index 4cac0f0155..dfdcb543df 100644 --- a/docs/use-cases/cv/image-segmentation-yolact.md +++ b/docs/use-cases/cv/image-segmentation-yolact.md @@ -11,7 +11,7 @@ and post-processing steps, allowing you to make requests on raw data and receive - **Server** is a REST API wrapper around Pipelines built on [FastAPI](https://fastapi.tiangolo.com/) and [Uvicorn](https://www.uvicorn.org/). It enables you to start a model serving endpoint running DeepSparse with a single CLI. -We will walk through an example of each. +We will walk through an example of each using YOLACT. ## Installation Requirements @@ -78,7 +78,7 @@ compiled_model = Engine( batch_size=batch_size # defaults to batch size 1 ) -# input is raw numpy tensors, output is raw scores for classes +# input is raw numpy tensors, output is raw data inputs = generate_random_inputs(model_to_path(sparsezoo_stub), batch_size) output = compiled_model(inputs) @@ -155,7 +155,7 @@ If a `--model_filepath` arg isn't provided, then `zoo:cv/segmentation/yolact-dar ![Annotation Results](images/result-0.jpg) ### Cross Use Case Functionality -Check out the [Pipeline User Guide](/user-guide/deepsparse/deepsparse-pipelines) for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all the utilities provided by Pipelines. @@ -210,4 +210,4 @@ boxes, classes, masks, scores = annotations["boxes"], annotations["classes"], an ``` ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From 53900796c91f0614c938352529eadb6ae2e01771 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:22:25 -0400 Subject: [PATCH 068/149] Update image-segmentation-yolact.md --- docs/use-cases/cv/image-segmentation-yolact.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/cv/image-segmentation-yolact.md b/docs/use-cases/cv/image-segmentation-yolact.md index dfdcb543df..f553470b07 100644 --- a/docs/use-cases/cv/image-segmentation-yolact.md +++ b/docs/use-cases/cv/image-segmentation-yolact.md @@ -129,7 +129,6 @@ The `class_names` argument defines a dictionary containing the desired class map from deepsparse.pipeline import Pipeline model_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" -images = ["thailand.jpg"] yolact_pipeline = Pipeline.create( task="yolact", @@ -137,6 +136,7 @@ yolact_pipeline = Pipeline.create( class_names="coco", ) +images = ["thailand.jpeg"] predictions = yolact_pipeline(images=images, confidence_threshold=0.2, nms_threshold=0.5) # predictions has attributes `boxes`, `classes`, `masks` and `scores` predictions.classes[0] From 0755b3abff96797653226f7e1d11eb5eadf46eb8 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Tue, 18 Apr 2023 17:25:21 -0400 Subject: [PATCH 069/149] Update embedding-extraction.md --- docs/use-cases/cv/embedding-extraction.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/use-cases/cv/embedding-extraction.md b/docs/use-cases/cv/embedding-extraction.md index 8b6b03b8ad..d11872721d 100644 --- a/docs/use-cases/cv/embedding-extraction.md +++ b/docs/use-cases/cv/embedding-extraction.md @@ -52,7 +52,10 @@ print(len(embedding.embeddings[0][0])) # 2048 << size of final layer>> ``` -# DeepSparse Server +### Cross Use Case Functionality +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring the Pipeline. + +## DeepSparse Server As an alternative to the Python API, DeepSparse Server allows you to serve an Embedding Extraction Pipeline over HTTP. Configuring the server uses the same parameters and schemas as the Pipelines. Once launched, a `/docs` endpoint is created with full endpoint descriptions and support for making sample requests. @@ -71,11 +74,12 @@ Spin up the server: ```bash deepsparse.server --config_file config.yaml ``` + Make requests to the server: ```python import requests, json url = "http://0.0.0.0:5543/predict/from_files" -paths = ["pets.jpg"] +paths = ["lion.jpeg"] files = [("request", open(img, 'rb')) for img in paths] resp = requests.post(url=url, files=files) result = json.loads(resp.text) @@ -86,4 +90,4 @@ print(len(result["embeddings"][0][0])) ``` ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From 739ba15173ace34a8d5e4d4bc33f4417d5d2f95a Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Wed, 19 Apr 2023 14:35:04 +0300 Subject: [PATCH 070/149] Update sentiment-analysis.md --- docs/use-cases/nlp/sentiment-analysis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md index 44f9055030..43b009ec96 100644 --- a/docs/use-cases/nlp/sentiment-analysis.md +++ b/docs/use-cases/nlp/sentiment-analysis.md @@ -93,7 +93,7 @@ Just like Hugging Face Pipelines, DeepSparse Pipelines wrap pre- and post-proces This creates a clean API that allows you to pass raw images to DeepSparse and recieve back the post-processed prediction, making it easy to add DeepSparse to your application. -We will use the `Pipeline.create()` constructor to create an instance of an image classifcation Pipeline +We will use the `Pipeline.create()` constructor to create an instance of a sentiment analysis Pipeline with a 90% pruned-quantized version of BERT trained on SST2. We can then pass the Pipeline raw text and recieve the predictions. All of the pre-processing (such as tokenizing the input) is handled by the Pipeline. @@ -109,7 +109,7 @@ sa_pipeline = Pipeline.create( batch_size=1 # default batch size is 1 ) -# run inference on image file +# run inference prediction = sa_pipeline("The sentiment analysis pipeline is fast and easy to use") print(prediction) From dc724e11b2d156363dda69cdaf4dbd413ad5db99 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Wed, 19 Apr 2023 14:37:07 +0300 Subject: [PATCH 071/149] Update question-answering.md --- docs/use-cases/nlp/question-answering.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md index d88edb5c9a..d9e40ed2f2 100644 --- a/docs/use-cases/nlp/question-answering.md +++ b/docs/use-cases/nlp/question-answering.md @@ -129,7 +129,7 @@ qa_pipeline = Pipeline.create( max_question_length=32, ) -# run inference on image file +# run inference q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" question = "What is DeepSparse?" output = qa_pipeline(question=question, context=q_context) @@ -156,7 +156,7 @@ qa_pipeline = Pipeline.create( context=Context(num_streams=1) ) -# run inference on image file +# run inference q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" question = "What is DeepSparse?" output = qa_pipeline(question=question, context=q_context) @@ -183,7 +183,7 @@ qa_pipeline = Pipeline.create( doc_stride=4 ) -# run inference on image file +# run inference q_context = "I have been trying to accelerate my inference workloads. DeepSparse is a CPU runtime that helps me." question = "What is DeepSparse?" output = qa_pipeline(question=question, context=q_context) From 59790f97a1ae0304fbf1bb7f185ff548c2806655 Mon Sep 17 00:00:00 2001 From: Derrick Mwiti Date: Wed, 19 Apr 2023 14:38:22 +0300 Subject: [PATCH 072/149] Update text-classification.md --- docs/use-cases/nlp/text-classification.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index 5a75e1aff7..840771fd60 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -104,7 +104,7 @@ pipeline = Pipeline.create( batch_size=1 # default batch size is 1 ) -# run inference on image file +# run inference sequences = ["I think DeepSparse Pipelines are awesome!"] prediction = pipeline(sequences) print(prediction) @@ -181,7 +181,7 @@ pipeline = Pipeline.create( batch_size=1 # default batch size is 1 ) -# run inference on image file +# run inference prediction = pipeline(["I am so glad you came today"]) print(prediction) @@ -208,7 +208,7 @@ pipeline = Pipeline.create( batch_size=1, ) -# run inference on image file +# run inference sequences = [[ "I rented I AM CURIOUS-YELLOW from my video store because of all the controversy that surrounded it when it was first released in 1967. I also heard that at first it was seized by U.S. customs if it ever tried to enter this country, therefore being a fan of films considered 'controversial' I really had to see this for myself.

The plot is centered around a young Swedish drama student named Lena who wants to learn everything she can about life. In particular she wants to focus her attentions to making some sort of documentary on what the average Swede thought about certain political issues such as the Vietnam War and race issues in the United States. In between asking politicians and ordinary denizens of Stockholm about their opinions on politics, she has sex with her drama teacher, classmates, and married men.

What kills me about I AM CURIOUS-YELLOW is that 40 years ago, this was considered pornographic. Really, the sex and nudity scenes are few and far between, even then it's not shot like some cheaply made porno. While my countrymen mind find it shocking, in reality sex and nudity are a major staple in Swedish cinema. Even Ingmar Bergman, arguably their answer to good old boy John Ford, had sex scenes in his films.

I do commend the filmmakers for the fact that any sex shown in the film is shown for artistic purposes rather than just to shock people and make money to be shown in pornographic theaters in America. I AM CURIOUS-YELLOW is a good film for anyone wanting to study the meat and potatoes (no pun intended) of Swedish cinema. But really, this film doesn't have much of a plot." ]] @@ -232,14 +232,14 @@ pipeline = Pipeline.create( sequence_length=[32, 128], context=Context(num_streams=1) ) -# run inference on image file +# run inference prediction = pipeline([[ "Timely access to information is in the best interests of the agencies", "It is everyone's best interest to get info in a timely manner", ]]) print(prediction) -# run inference on image file +# run inference prediction = pipeline([[ "Timely access to information is in the best interests of both GAO and the agencies. Let's make information more accessible", "It is in everyone's best interest to have access to information in a timely manner. Information should be made more accessible.", From 8fbee7ac422b93ea09bdf28fd181b5074b5cd741 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:14:43 -0400 Subject: [PATCH 073/149] Update embedding-extraction.md --- docs/use-cases/cv/embedding-extraction.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/use-cases/cv/embedding-extraction.md b/docs/use-cases/cv/embedding-extraction.md index d11872721d..e85307ef77 100644 --- a/docs/use-cases/cv/embedding-extraction.md +++ b/docs/use-cases/cv/embedding-extraction.md @@ -27,14 +27,13 @@ The Embedding Extraction Pipeline handles some useful actions around inference: - Second, as with all DeepSparse Pipelines, it handles pre-processing such that you can pass raw input. You will notice that in addition to the typical task argument used in `Pipeline.create()`, the Embedding Extraction Pipeline includes a `base_task` argument. This argument tells the Pipeline the domain of the model, such that the Pipeline can figure out what pre-processing to do. -This is an example of extracting the last layer from ResNet-50: - Download an image to use with the Pipeline. ```bash wget https://huggingface.co/spaces/neuralmagic/image-classification/resolve/main/lion.jpeg ``` -Run the following to extract the embedding: +This is an example of extracting the last layer from ResNet-50: + ```python from deepsparse import Pipeline From c7287c51effff60c9a99eb488456bd5dcad8edfd Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:38:19 -0400 Subject: [PATCH 074/149] Create README.md --- docs/use-cases/README.md | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/use-cases/README.md diff --git a/docs/use-cases/README.md b/docs/use-cases/README.md new file mode 100644 index 0000000000..0b990e03a8 --- /dev/null +++ b/docs/use-cases/README.md @@ -0,0 +1,72 @@ +# Use Cases + +DeepSparse offers three interfaces for interacting with DeepSparse: + +- **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. + +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. + +- **Server** is a REST API wrapper around Pipelines built on FastAPI and Uvicorn. It enables you to start a model serving endpoint running DeepSparse with a single CLI. + +This directory offers examples + +### Supported Tasks + +DeepSparse supports the following tasks out of the box: + +| NLP | CV | +|-----------------------|---------------------------| +| [Text Classification `"text-classification"`](use-cases/nlp/text-classification.md) | [Image Classification `"image_classification"`](use-cases/cv/image-classification.md) | +| [Token Classification `"token-classification"`](use-cases/nlp/token-classification.md) | [Object Detection `"yolo"`](use-cases/cv/object-detection-yolov5.md) | +| [Sentiment Analysis `"sentiment-analysis"`](use-cases/nlp/sentiment-analysis.md) | [Instance Segmentation `"yolact"`](image-segmentation-yolact.md) | +| [Question Answering `"question-answering"`](use-cases/nlp/question-answering.md) | | +| [Zero-Shot Text Classification `"zero-shot-text-classification"`](use-cases/nlp/zero-shot-text-classification.md) | | +| [Embedding Extraction `"transformers_embedding_extraction"`](use-cases/nlp/transformers-embedding-extraction.md) | | + +**Pipeline Example** | Sentiment Analysis + +Here's an example of how a task is used to create a Pipeline: + +```python +from deepsparse import Pipeline + +pipeline = Pipeline.create( + task="sentiment_analysis", + model_path="zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none") + +print(pipeline("I love DeepSparse Pipelines!")) +# labels=['positive'] scores=[0.998009443283081] +``` + +**Server Example** | Sentiment Analysis + +Here's an example of how a task is used to create a Server: + +```bash +deepsparse.server \ + --task sentiment_analysis \ + --model_path zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +``` + +Making a request: + +```python +import requests + +# Uvicorn is running on this port +url = 'http://0.0.0.0:5543/predict' + +# send the data +obj = {"sequences": "Sending requests to DeepSparse Server is fast and easy!"} +resp = requests.post(url=url, json=obj) + +# recieve the post-processed output +print(resp.text) +# >> {"labels":["positive"],"scores":[0.9330279231071472]} +``` + +### Additional Resources + +- [Custom Tasks](../user-guide/deepsparse-pipelines.md#custom-use-case) +- [Pipeline User Guide](../user-guide/deepsparse-pipelines.md) +- [Server User Guide](../user-guide/deepsparse-server.md) From b00ea3cf2b28b1c7f2f35795c47367d6ee6b0cd2 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:39:21 -0400 Subject: [PATCH 075/149] Update README.md --- docs/use-cases/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/README.md b/docs/use-cases/README.md index 0b990e03a8..f0511e88d7 100644 --- a/docs/use-cases/README.md +++ b/docs/use-cases/README.md @@ -1,6 +1,6 @@ # Use Cases -DeepSparse offers three interfaces for interacting with DeepSparse: +There are three interfaces for interacting with DeepSparse: - **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. @@ -8,7 +8,7 @@ DeepSparse offers three interfaces for interacting with DeepSparse: - **Server** is a REST API wrapper around Pipelines built on FastAPI and Uvicorn. It enables you to start a model serving endpoint running DeepSparse with a single CLI. -This directory offers examples +This directory offers examples using each API in various supported tasks. ### Supported Tasks From cab0f2caec691b4dd5796d8d3b42a31957b9768c Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:39:36 -0400 Subject: [PATCH 076/149] Update README.md --- docs/use-cases/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/README.md b/docs/use-cases/README.md index f0511e88d7..137a70a750 100644 --- a/docs/use-cases/README.md +++ b/docs/use-cases/README.md @@ -4,7 +4,7 @@ There are three interfaces for interacting with DeepSparse: - **Engine** is the lowest-level API that enables you to compile a model and run inference on raw input tensors. -- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with pre-processing and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. +- **Pipeline** is the default DeepSparse API. Similar to Hugging Face Pipelines, it wraps Engine with task-specific pre-processing and post-processing steps, allowing you to make requests on raw data and receive post-processed predictions. - **Server** is a REST API wrapper around Pipelines built on FastAPI and Uvicorn. It enables you to start a model serving endpoint running DeepSparse with a single CLI. From c02a9a4be30c716a9fbbef7f3eed1c3b8c4a0bfe Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:40:30 -0400 Subject: [PATCH 077/149] Update README.md --- docs/use-cases/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/use-cases/README.md b/docs/use-cases/README.md index 137a70a750..b025e16022 100644 --- a/docs/use-cases/README.md +++ b/docs/use-cases/README.md @@ -16,12 +16,12 @@ DeepSparse supports the following tasks out of the box: | NLP | CV | |-----------------------|---------------------------| -| [Text Classification `"text-classification"`](use-cases/nlp/text-classification.md) | [Image Classification `"image_classification"`](use-cases/cv/image-classification.md) | -| [Token Classification `"token-classification"`](use-cases/nlp/token-classification.md) | [Object Detection `"yolo"`](use-cases/cv/object-detection-yolov5.md) | -| [Sentiment Analysis `"sentiment-analysis"`](use-cases/nlp/sentiment-analysis.md) | [Instance Segmentation `"yolact"`](image-segmentation-yolact.md) | -| [Question Answering `"question-answering"`](use-cases/nlp/question-answering.md) | | -| [Zero-Shot Text Classification `"zero-shot-text-classification"`](use-cases/nlp/zero-shot-text-classification.md) | | -| [Embedding Extraction `"transformers_embedding_extraction"`](use-cases/nlp/transformers-embedding-extraction.md) | | +| [Text Classification `"text-classification"`](nlp/text-classification.md) | [Image Classification `"image_classification"`](cv/image-classification.md) | +| [Token Classification `"token-classification"`](nlp/token-classification.md) | [Object Detection `"yolo"`](cv/object-detection-yolov5.md) | +| [Sentiment Analysis `"sentiment-analysis"`](nlp/sentiment-analysis.md) | [Instance Segmentation `"yolact"`](cv/image-segmentation-yolact.md) | +| [Question Answering `"question-answering"`](nlp/question-answering.md) | | +| [Zero-Shot Text Classification `"zero-shot-text-classification"`](nlp/zero-shot-text-classification.md) | | +| [Embedding Extraction `"transformers_embedding_extraction"`](nlp/transformers-embedding-extraction.md) | | **Pipeline Example** | Sentiment Analysis From 4e9a61c8157d8224d132b778e8837ee0e362fc23 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:40:55 -0400 Subject: [PATCH 078/149] Update README.md --- docs/use-cases/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/use-cases/README.md b/docs/use-cases/README.md index b025e16022..38f7939e1b 100644 --- a/docs/use-cases/README.md +++ b/docs/use-cases/README.md @@ -23,6 +23,8 @@ DeepSparse supports the following tasks out of the box: | [Zero-Shot Text Classification `"zero-shot-text-classification"`](nlp/zero-shot-text-classification.md) | | | [Embedding Extraction `"transformers_embedding_extraction"`](nlp/transformers-embedding-extraction.md) | | +### Examples + **Pipeline Example** | Sentiment Analysis Here's an example of how a task is used to create a Pipeline: From e0b3fc60f2799e1dddca4be714f9ef8a3aa96a27 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:41:36 -0400 Subject: [PATCH 079/149] Update README.md --- docs/user-guide/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 95d0d1e6d6..22e9f1ebfe 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -1,4 +1,4 @@ -# DeepSparse User Guide +# User Guide This directory demonstrates usage of DeepSparse's key API, including: - [Benchmarking CLI](#performance-benchmarking) From ea2915ca9fbb81d7fd4b05c71cb56ac6d3b0a848 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:43:18 -0400 Subject: [PATCH 080/149] Update README.md --- docs/user-guide/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 22e9f1ebfe..40e02ee155 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -119,7 +119,7 @@ engine = compile_model(onnx_filepath, batch_size) outputs = engine.run(inputs) ``` -### [Pipeline](deepsparse-pipeline.md) +### [Pipeline](deepsparse-pipelines.md) Pipeline is the default API for interacting with DeepSparse. Similar to Hugging Face Pipelines, DeepSparse Pipelines wrap Engine with pre- and post-processing (as well as other utilities), @@ -202,7 +202,7 @@ print(response.text) DeepSparse supports many common CV and NLP use cases out of the box. Check out the use case guide for more details on the task-specific APIs. **Computer Vision**: -- Image Classification: `task="image_classification"` +- [Image Classification: `task="image_classification"`](../use-cases/cv/image-classification.md) - Object Detection: `task="yolo"` - Instance Segmentation: `task="yolact"` From f05ba0153ea1b6de3bcb47392506118db9cca167 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:44:17 -0400 Subject: [PATCH 081/149] Update README.md --- docs/user-guide/README.md | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 40e02ee155..50c1c76b2e 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -199,19 +199,6 @@ print(response.text) ## Supported Tasks -DeepSparse supports many common CV and NLP use cases out of the box. Check out the use case guide for more details on the task-specific APIs. - -**Computer Vision**: -- [Image Classification: `task="image_classification"`](../use-cases/cv/image-classification.md) -- Object Detection: `task="yolo"` -- Instance Segmentation: `task="yolact"` - -**Natural Language Processing**: -- Embedding Extraction: `task="transformers_embedding_extraction"` -- Text Classification: `task="text-classification"` -- Zero Shot Text Classification: `task="zero-shot-text-classification"` -- Sentiment Analysis: `task="sentiment-analysis"` -- Token Classification: `task="token-classification"` -- Question Answering: `task="question-answering"` - -Check out the [pipeline page](deepsparse-pipeline.md) for details on creating a custom pipeline. +DeepSparse supports many common CV and NLP use cases out of the box. + +Check out the [use cases page](../use-cases) for more details on the task-specific APIs. From 847621bce3c2ed06b541404ddc93e2a3baf54142 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:47:29 -0400 Subject: [PATCH 082/149] Update deepsparse-pipelines.md --- docs/user-guide/deepsparse-pipelines.md | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/docs/user-guide/deepsparse-pipelines.md b/docs/user-guide/deepsparse-pipelines.md index 7c411dcd3d..4c3fc3d504 100644 --- a/docs/user-guide/deepsparse-pipelines.md +++ b/docs/user-guide/deepsparse-pipelines.md @@ -34,25 +34,11 @@ folder containing `model.onnx` and supporting files (e.g., the Hugging Face `tok ## Supported Use Cases -Pipelines support many CV and NLP use cases out of the box. Check out the use case pages for more details on task-specific APIs. - -**Computer Vision**: -- Image Classification: `task="image_classification"` -- Object Detection: `task="yolo"` -- Instance Segmentation: `task="yolact"` - -**Natural Language Processing**: -- Embedding Extraction:`task="transformers_embedding_extraction"` -- Text Classification: `task="text-classification"` -- Zero Shot Text Classification: `task="zero-shot-text-classification"` -- Sentiment Analysis: `task="sentiment-analysis"` -- Token Classification: `task="token-classification"` -- Question Answering: `task="question-answering"` +Pipelines support many CV and NLP use cases out of the box. Check out the [use case page](../use-cases) for more details on task-specific APIs. ## Custom Use Case -Beyond officially supported use cases, Pipelines can be extended to additional tasks via the -`CustomTaskPipeline`. +Beyond officially supported tasks, Pipelines can be extended to additional tasks via the `CustomTaskPipeline`. `CustomTaskPipelines` are passed the following arguments: - `model_path` - a SparseZoo stub or path to a local ONNX file From 1b672c949f88d99aaeed35cb0fa7bea1647b6a89 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:47:45 -0400 Subject: [PATCH 083/149] Update deepsparse-pipelines.md --- docs/user-guide/deepsparse-pipelines.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/deepsparse-pipelines.md b/docs/user-guide/deepsparse-pipelines.md index 4c3fc3d504..c7fc66137f 100644 --- a/docs/user-guide/deepsparse-pipelines.md +++ b/docs/user-guide/deepsparse-pipelines.md @@ -34,7 +34,9 @@ folder containing `model.onnx` and supporting files (e.g., the Hugging Face `tok ## Supported Use Cases -Pipelines support many CV and NLP use cases out of the box. Check out the [use case page](../use-cases) for more details on task-specific APIs. +Pipelines support many CV and NLP use cases out of the box. + +Check out the [use case page](../use-cases) for more details on task-specific APIs. ## Custom Use Case From 0482754198a4e45a5881b30feadde2f1566c1f0a Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 09:53:29 -0400 Subject: [PATCH 084/149] Update deepsparse-pipelines.md --- docs/user-guide/deepsparse-pipelines.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/user-guide/deepsparse-pipelines.md b/docs/user-guide/deepsparse-pipelines.md index c7fc66137f..1dd37ccca5 100644 --- a/docs/user-guide/deepsparse-pipelines.md +++ b/docs/user-guide/deepsparse-pipelines.md @@ -34,9 +34,7 @@ folder containing `model.onnx` and supporting files (e.g., the Hugging Face `tok ## Supported Use Cases -Pipelines support many CV and NLP use cases out of the box. - -Check out the [use case page](../use-cases) for more details on task-specific APIs. +Pipelines support many CV and NLP use cases out of the box. [Check out the Use Cases page for details on task-specific APIs](../use-cases). ## Custom Use Case From a78f494cdf6f5169e03c7e14d34ca9bb0bcf62b8 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:04:47 -0400 Subject: [PATCH 085/149] Update deepsparse-server.md --- docs/user-guide/deepsparse-server.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/docs/user-guide/deepsparse-server.md b/docs/user-guide/deepsparse-server.md index 101768bec8..e3095bb99a 100644 --- a/docs/user-guide/deepsparse-server.md +++ b/docs/user-guide/deepsparse-server.md @@ -38,20 +38,7 @@ deepsparse.server --help ## Supported Use Cases -DeepSparse Server supports all tasks available in DeepSparse Pipelines. Check out the use case guides for more details on task-specific APIs. - -**Computer Vision**: -- Image Classification: `task="image_classification"` -- Object Detection: `task="yolo"` -- Instance Segmentation: `task="yolact"` - -**Natural Language Processing**: -- Embedding Extraction: `task="transformers_embedding_extraction"` -- Text Classification: `task="text-classification"` -- Zero Shot Text Classification: `task="zero-shot-text-classification"` -- Sentiment Analysis: `task="sentiment-analysis"` -- Token Classification: `task="token-classification"` -- Question Answering: `task="question-answering"` +DeepSparse Server supports all tasks available in Pipelines. [Check out the Use Cases page for more details on task-specific APIs](../use-cases). ## Swagger UI From f9914bb31eb2f6c2c9e4fd1b149e1480a262a148 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:07:02 -0400 Subject: [PATCH 086/149] Update deepsparse-pipelines.md --- docs/user-guide/deepsparse-pipelines.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/user-guide/deepsparse-pipelines.md b/docs/user-guide/deepsparse-pipelines.md index 1dd37ccca5..ae4a707600 100644 --- a/docs/user-guide/deepsparse-pipelines.md +++ b/docs/user-guide/deepsparse-pipelines.md @@ -308,6 +308,10 @@ one DeepSparse Pipeline running in each container for proper process isolation. Stay tuned for documentation on enabling multi-stream scheduling with DeepSparse Pipelines. +### Bucketing + +Stay tuned for documentation on enabling bucketing with DeepSparse Pipelines. + ### Logging Stay tuned for documentation on enabling logging with DeepSparse Pipelines. From e6420d3d675841b5036cc69bbe6e4d7537d0aa79 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:09:05 -0400 Subject: [PATCH 087/149] Update deepsparse-server.md --- docs/user-guide/deepsparse-server.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/deepsparse-server.md b/docs/user-guide/deepsparse-server.md index e3095bb99a..37c28aa3f8 100644 --- a/docs/user-guide/deepsparse-server.md +++ b/docs/user-guide/deepsparse-server.md @@ -252,12 +252,16 @@ print(requests.post(sentiment_analysis_url, json=short_obj).text) # >>> {"labels":["positive","negative","positive"],"scores":[0.9665533900260925,0.9952980279922485,0.9939143061637878]} ``` -Check out the use case pages for detailed documentation on task-specific arguments that can be applied to the Server via `kwargs`. +Check out the [Use Case](../use-cases) page for detailed documentation on task-specific arguments that can be applied to the Server via `kwargs`. ## Custom Use Cases Stay tuned for documentation on using a custom DeepSparse Pipeline within the Server! +## Multi-Stream + +Stay tuned for documentation on multi-stream scheduling with DeepSparse! + ## Logging Stay tuned for documentation on DeepSparse Logging! From 66e264b9c71af5ce31b8d0fe26fb429d3a8ee58d Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:12:08 -0400 Subject: [PATCH 088/149] Update README.md --- docs/user-guide/README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 50c1c76b2e..5ffafb9f21 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -6,15 +6,9 @@ This directory demonstrates usage of DeepSparse's key API, including: - [Pipeline API](#pipeline) - [Server API](#server) -## Installation +## Installation Requirements -Install via `pip`. Using a virtual enviornment is highly recommended. - -```bash -pip install deepsparse[server] -``` - -See the [installation page](installation.md) for further installation options. +This page requires [DeepSparse Server installation](installation.md). ## [Performance Benchmarking](deepsparse-benchmarking.md) From 3633383b5108fdad6c9872e8987e1bb7bf8c86fe Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:13:43 -0400 Subject: [PATCH 089/149] Update README.md --- docs/user-guide/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 5ffafb9f21..702d4306c1 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -10,7 +10,7 @@ This directory demonstrates usage of DeepSparse's key API, including: This page requires [DeepSparse Server installation](installation.md). -## [Performance Benchmarking](deepsparse-benchmarking.md) +## Performance Benchmarking DeepSparse's key feature is its performance on commodity CPUs. @@ -43,6 +43,8 @@ DeepSparse achieves a ***12x speedup*** over ORT! **Pro-Tip:** In place of a [SparseZoo](https://sparsezoo.neuralmagic.com/) stubs, you can pass a local ONNX file to test your model. +Checkout the [Performance Benchmarking guide](deepsparse-benchmarking.md) for more details. + ## Deployment APIs Now that we have seen DeepSparse's performance gains, we can add DeepSparse to an application. @@ -113,7 +115,7 @@ engine = compile_model(onnx_filepath, batch_size) outputs = engine.run(inputs) ``` -### [Pipeline](deepsparse-pipelines.md) +### Pipeline Pipeline is the default API for interacting with DeepSparse. Similar to Hugging Face Pipelines, DeepSparse Pipelines wrap Engine with pre- and post-processing (as well as other utilities), @@ -140,8 +142,9 @@ print(prediction) # > labels=['positive'] scores=[0.9954759478569031] ``` +Checkout the [DeepSparse Pipeline guide](deepsparse-pipelines.md) for more details. -### [Server](deepsparse-server.md) +### Server Server wraps Pipelines with REST APIs, that make it easy to stand up a model serving endpoint running DeepSparse. This enables you to send raw data to DeepSparse over HTTP and receive the post-processed @@ -191,8 +194,8 @@ print(response.text) # {"labels":["positive"],"scores":[0.9965094327926636]} ``` -## Supported Tasks +Checkout the [DeepSparse Server guide](deepsparse-server.md) for more details. -DeepSparse supports many common CV and NLP use cases out of the box. +## Supported Tasks -Check out the [use cases page](../use-cases) for more details on the task-specific APIs. +DeepSparse supports many common CV and NLP use cases out of the box. Check out the [use cases page](../use-cases) for details on the task-specific APIs. From 981774a1be9749c2df520d0cce55975a959d6f12 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:14:19 -0400 Subject: [PATCH 090/149] Update README.md --- docs/user-guide/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 702d4306c1..71b91f1a30 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -198,4 +198,4 @@ Checkout the [DeepSparse Server guide](deepsparse-server.md) for more details. ## Supported Tasks -DeepSparse supports many common CV and NLP use cases out of the box. Check out the [use cases page](../use-cases) for details on the task-specific APIs. +DeepSparse supports many CV and NLP use cases out of the box. Check out the [use cases page](../use-cases) for details on the task-specific APIs. From a2ecfa784ad56ef109b3b0d60a4af95c820eaf01 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:14:34 -0400 Subject: [PATCH 091/149] Update README.md --- docs/user-guide/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 71b91f1a30..e85d1f77fc 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -198,4 +198,4 @@ Checkout the [DeepSparse Server guide](deepsparse-server.md) for more details. ## Supported Tasks -DeepSparse supports many CV and NLP use cases out of the box. Check out the [use cases page](../use-cases) for details on the task-specific APIs. +DeepSparse supports many CV and NLP use cases out of the box. Check out the [Use Cases page](../use-cases) for details on the task-specific APIs. From b8af6baa394c3e88f6859436cac70ad44abee1fe Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:15:42 -0400 Subject: [PATCH 092/149] Update README.md --- docs/user-guide/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index e85d1f77fc..779faa8dcf 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -12,9 +12,7 @@ This page requires [DeepSparse Server installation](installation.md). ## Performance Benchmarking -DeepSparse's key feature is its performance on commodity CPUs. - -For dense unoptimized models, DeepSparse is competitive with other CPU runtimes like ONNX Runtime. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. +DeepSparse's key feature is its performance on commodity CPUs. For dense unoptimized models, DeepSparse is competitive with other CPU runtimes like ONNX Runtime. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve an order-of-magnitude speedup. As an example, let's compare DeepSparse and ORT's performance on BERT using a [90% pruned-quantized version](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none) in SparseZoo on an AWS `c6i.16xlarge` instance (32 cores). From 0e123efce50145f0088481685501eae199087492 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:25:05 -0400 Subject: [PATCH 093/149] Update image-segmentation-yolact.md --- docs/use-cases/cv/image-segmentation-yolact.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/cv/image-segmentation-yolact.md b/docs/use-cases/cv/image-segmentation-yolact.md index f553470b07..8f8c677b70 100644 --- a/docs/use-cases/cv/image-segmentation-yolact.md +++ b/docs/use-cases/cv/image-segmentation-yolact.md @@ -15,7 +15,7 @@ We will walk through an example of each using YOLACT. ## Installation Requirements -This use case requires the installation of [DeepSparse Server and YOLO](https://docs.neuralmagic.com/get-started/install/deepsparse). +This tutorial requires the installation of [DeepSparse Server and YOLO](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). From c2aa20254c43265047f9a7124ff4912722f5ae18 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:25:53 -0400 Subject: [PATCH 094/149] Update image-classification.md --- docs/use-cases/cv/image-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md index 23666f0442..c1ddd95136 100644 --- a/docs/use-cases/cv/image-classification.md +++ b/docs/use-cases/cv/image-classification.md @@ -15,7 +15,7 @@ This example uses ResNet-50. For a full list of pre-sparsified image classificat ## Installation Requirements -This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). From 2e267cd02edf09dfba856581cc86878dc1ee0936 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:26:21 -0400 Subject: [PATCH 095/149] Update object-detection-yolov5.md --- docs/use-cases/cv/object-detection-yolov5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md index 795ecbd8f1..7bffe760d0 100644 --- a/docs/use-cases/cv/object-detection-yolov5.md +++ b/docs/use-cases/cv/object-detection-yolov5.md @@ -13,7 +13,7 @@ This example uses YOLOv5s. For a full list of pre-sparsified object detection mo ## Installation Requirements -This use case requires the installation of [DeepSparse Server and YOLO](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server and YOLO](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). From bc122631b46704c44942353a2d1c62ad6fce835b Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:26:42 -0400 Subject: [PATCH 096/149] Update question-answering.md --- docs/use-cases/nlp/question-answering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md index d9e40ed2f2..10a51e7fb1 100644 --- a/docs/use-cases/nlp/question-answering.md +++ b/docs/use-cases/nlp/question-answering.md @@ -15,7 +15,7 @@ We will walk through an example of each. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). From d18f1ebf87df1c9da0bec47e659ed88b49c5ec86 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:27:01 -0400 Subject: [PATCH 097/149] Update sentiment-analysis.md --- docs/use-cases/nlp/sentiment-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md index 43b009ec96..ae121cd211 100644 --- a/docs/use-cases/nlp/sentiment-analysis.md +++ b/docs/use-cases/nlp/sentiment-analysis.md @@ -11,7 +11,7 @@ There are three interfaces for interacting with DeepSparse: ## Installation Requirements -This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). From c55572c9768ff894b61c3651d253e875aa6c20a1 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:27:26 -0400 Subject: [PATCH 098/149] Update text-classification.md --- docs/use-cases/nlp/text-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index 840771fd60..97f7980f95 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -13,7 +13,7 @@ endpoint running DeepSparse with a single CLI. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). From a444ee33b8b26025e9ac18172ac1b4c6f5f68d45 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:27:40 -0400 Subject: [PATCH 099/149] Update token-classification.md --- docs/use-cases/nlp/token-classification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/nlp/token-classification.md b/docs/use-cases/nlp/token-classification.md index e6c1ac4bfb..1bc2a9cb95 100644 --- a/docs/use-cases/nlp/token-classification.md +++ b/docs/use-cases/nlp/token-classification.md @@ -13,7 +13,7 @@ endpoint running DeepSparse with a single CLI. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). @@ -256,4 +256,4 @@ print(resp.text) ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file +Check out the Server User Guide for more details on configuring the Server. From 94f2644677c4ce9e7a18f14e63b54d4d7f2a97be Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:27:51 -0400 Subject: [PATCH 100/149] Update transformers-embedding-extraction.md --- docs/use-cases/nlp/transformers-embedding-extraction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/nlp/transformers-embedding-extraction.md b/docs/use-cases/nlp/transformers-embedding-extraction.md index 7e0ae7bebc..0c805aecf3 100644 --- a/docs/use-cases/nlp/transformers-embedding-extraction.md +++ b/docs/use-cases/nlp/transformers-embedding-extraction.md @@ -15,7 +15,7 @@ For the embedding extraction case, we will walk through an example of Pipeline a ## Installation Requirements -This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). @@ -192,4 +192,4 @@ print(len(result["embeddings"][0])) ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file +Check out the Server User Guide for more details on configuring the Server. From cb8b5dd546398debb208cdafca300c187546299c Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:28:02 -0400 Subject: [PATCH 101/149] Update zero-shot-text-classification.md --- docs/use-cases/nlp/zero-shot-text-classification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/nlp/zero-shot-text-classification.md b/docs/use-cases/nlp/zero-shot-text-classification.md index 3043a25dee..8899c34c74 100644 --- a/docs/use-cases/nlp/zero-shot-text-classification.md +++ b/docs/use-cases/nlp/zero-shot-text-classification.md @@ -16,7 +16,7 @@ We will walk through an example of each. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](https://docs.neuralmagic.com/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). @@ -236,4 +236,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. \ No newline at end of file +Check out the Server User Guide for more details on configuring the Server. From 8bca17d446c50dd7a9e8c21dfd6d5eb97fde892e Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:37:10 -0400 Subject: [PATCH 102/149] Update question-answering.md --- docs/use-cases/nlp/question-answering.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md index 10a51e7fb1..1f16822498 100644 --- a/docs/use-cases/nlp/question-answering.md +++ b/docs/use-cases/nlp/question-answering.md @@ -29,11 +29,11 @@ As a baseline, let's check out ONNX Runtime's performance on BERT. Make sure you ````bash deepsparse.benchmark \ - zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/base-none \ + zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/base-none \ -b 64 -s sync -nstreams 1 -i [64,384] \ -e onnxruntime -> Original Model Path: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/base-none +> Original Model Path: zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none > Batch Size: 64 > Scenario: sync > Throughput (items/sec): 5.5482 @@ -41,7 +41,7 @@ deepsparse.benchmark \ ONNX Runtime achieves 5.5 items/second with batch 64 and sequence length 384. -## DeepSparse Engine +### DeepSparse Speedup Now, let's run DeepSparse on an inference-optimized sparse version of BERT. This model has been 90% pruned and quantized, while retaining >99% accuracy of the dense baseline on the [SQuAD](https://huggingface.co/datasets/squad) dataset. ```bash deepsparse.benchmark \ @@ -49,7 +49,7 @@ deepsparse.benchmark \ -b 64 -s sync -nstreams 1 -i [64,384] \ -e deepsparse -> Original Model Path: zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none +> Original Model Path: zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/base-none > Batch Size: 64 > Scenario: sync > Throughput (items/sec): 31.6372 @@ -70,7 +70,7 @@ from deepsparse.utils import generate_random_inputs, model_to_path import numpy as np # download onnx from sparsezoo and compile with batchsize 1 -sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +sparsezoo_stub = "zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none" batch_size = 1 complied_model = Engine( model=sparsezoo_stub, # sparsezoo stub or path to local ONNX @@ -98,7 +98,7 @@ from deepsparse import Pipeline task = "question-answering" qa_pipeline = Pipeline.create( task=task, - model_path="zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none", + model_path="zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none", ) q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" @@ -121,7 +121,7 @@ The example below compiles the model and runs inference with sequence length 64 from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +sparsezoo_stub = "zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none" qa_pipeline = Pipeline.create( task="question-answering", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX @@ -145,7 +145,7 @@ The example below creates a bucket for smaller input lengths (16 tokens) and for from deepsparse import Pipeline, Context # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +sparsezoo_stub = "zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none" task = "question-answering" qa_pipeline = Pipeline.create( @@ -172,7 +172,7 @@ If the context is too long to fit in the max sequence length of the model, the D from deepsparse import Pipeline, Context # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none" +sparsezoo_stub = "zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none" task = "question-answering" qa_pipeline = Pipeline.create( @@ -203,7 +203,7 @@ The CLI command below launches a question answering pipeline with a 90% pruned-q ```bash deepsparse.server \ --task question-answering \ - --model_path zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none # or path/to/onnx + --model_path zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none # or path/to/onnx ``` You should see Uvicorn report that it is running on http://0.0.0.0:5543. Once launched, a /docs path is created with full endpoint descriptions and support for making sample requests. @@ -235,7 +235,7 @@ This configuration file sets sequence length to 64: # question-answering-config.yaml endpoints: - task: question-answering - model: zoo:nlp/question_answering/bert-base_cased/pytorch/huggingface/squad/pruned90_quant-none + model: zoo:nlp/question_answering/obert-base/pytorch/huggingface/squad/pruned90_quant-none kwargs: sequence_length: 24 # uses sequence length 64 max_question_length: 8 From 248ea1318ec85e6283802e4a020a94b561ec2a80 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:39:30 -0400 Subject: [PATCH 103/149] Update question-answering.md --- docs/use-cases/nlp/question-answering.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md index 1f16822498..b1b93459e6 100644 --- a/docs/use-cases/nlp/question-answering.md +++ b/docs/use-cases/nlp/question-answering.md @@ -192,7 +192,7 @@ print(output.answer) ``` ### Cross Use Case Functionality -Check out the Pipeline User Guide for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server @@ -266,4 +266,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From c6dda09fc3d75c30780905c8cacf3ae879017e30 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:46:29 -0400 Subject: [PATCH 104/149] Update question-answering.md --- docs/use-cases/nlp/question-answering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md index b1b93459e6..98d7ce4ce0 100644 --- a/docs/use-cases/nlp/question-answering.md +++ b/docs/use-cases/nlp/question-answering.md @@ -17,7 +17,7 @@ We will walk through an example of each. This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## Benchmarking From 3f1a30d89fcaa26c82fbc82ca28941ce6be1d90f Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:50:32 -0400 Subject: [PATCH 105/149] Update sentiment-analysis.md --- docs/use-cases/nlp/sentiment-analysis.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md index ae121cd211..8041ef40d9 100644 --- a/docs/use-cases/nlp/sentiment-analysis.md +++ b/docs/use-cases/nlp/sentiment-analysis.md @@ -13,7 +13,7 @@ There are three interfaces for interacting with DeepSparse: This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## Benchmarking @@ -131,8 +131,6 @@ from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" -batch_size = 1 -sequence_length = 64 sa_pipeline = Pipeline.create( task="sentiment-analysis", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX @@ -156,13 +154,11 @@ from deepsparse import Pipeline, Context # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" -batch_size = 1 -buckets = [16, 128] sa_pipeline = Pipeline.create( task="sentiment-analysis", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX batch_size=1, # default batch size is 1 - sequence_length=buckets, # creates bucketed pipeline + sequence_length=[16, 128], # creates bucketed pipeline context = Context(num_streams=1) # creates scheduler with one stream ) From 2d3a89e47768fe42a17a7ff63fadba84e3fdcf6b Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:51:51 -0400 Subject: [PATCH 106/149] Update sentiment-analysis.md --- docs/use-cases/nlp/sentiment-analysis.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md index 8041ef40d9..7af063447e 100644 --- a/docs/use-cases/nlp/sentiment-analysis.md +++ b/docs/use-cases/nlp/sentiment-analysis.md @@ -218,7 +218,7 @@ print(prediction_b2) ### Cross Use Case Functionality -Check out the Pipeline User Guide for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server @@ -294,4 +294,4 @@ print(resp.text) ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From c90cb3ed3e04b65254d9bf5f3f2d0ce748833ce1 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:53:15 -0400 Subject: [PATCH 107/149] Update sentiment-analysis.md --- docs/use-cases/nlp/sentiment-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md index 7af063447e..423a9944d1 100644 --- a/docs/use-cases/nlp/sentiment-analysis.md +++ b/docs/use-cases/nlp/sentiment-analysis.md @@ -289,7 +289,7 @@ resp = requests.post(url=url, json=obj) # recieve the post-processed output print(resp.text) -# >> {"labels":[["1","0"]],"scores":[[0.9941965341567993,0.005803497973829508]]} +# >> {"labels":[["positive","negative"]],"scores":[[0.9330279231071472,0.06697207689285278]]} ``` ### Cross Use Case Functionality From a519ba0995eab46614e4b45832e8b3bbd975437e Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:54:18 -0400 Subject: [PATCH 108/149] Update text-classification.md --- docs/use-cases/nlp/text-classification.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index 97f7980f95..d0d58f7e75 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -15,7 +15,7 @@ endpoint running DeepSparse with a single CLI. This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## Benchmarking @@ -290,7 +290,7 @@ print(prediction_b2) ``` ### Cross Use Case Functionality -Check out the Pipeline User Guide for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. DeepSparse Server wraps the Pipeline API, so it inherits all the utilities provided by Pipelines. @@ -390,4 +390,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../deepsparse-server.md) for more details on configuring the Server. From 417cf3a1283f623c1005192992385aedfd6d989d Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:00:15 -0400 Subject: [PATCH 109/149] Update text-classification.md --- docs/use-cases/nlp/text-classification.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index d0d58f7e75..53f1e73695 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -108,7 +108,7 @@ pipeline = Pipeline.create( sequences = ["I think DeepSparse Pipelines are awesome!"] prediction = pipeline(sequences) print(prediction) -# labels=['0'] scores=[0.9986200332641602] +# labels=['positive'] scores=[0.9986492991447449] ``` @@ -121,7 +121,6 @@ from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" -batch_size = 1 pipeline = Pipeline.create( task="text-classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX @@ -148,7 +147,6 @@ from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/qqp/pruned90_quant-none" -batch_size = 1 pipeline = Pipeline.create( task="text-classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX @@ -166,7 +164,7 @@ print(prediction) # labels=['duplicate'] scores=[0.9978139996528625] ``` -### Single-Input Multi-Label Example (GoEmotions) +#### Single-Input Multi-Label Example (GoEmotions) Here's an example with a single input and multi label prediction with a model trained on GoEmotions: @@ -174,7 +172,7 @@ Here's an example with a single input and multi label prediction with a model tr from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 -sparsezoo_stub = "zoo:nlp/multilabel_text_classification/obert-base/pytorch/huggingface/goemotions/pruned90-none" +sparsezoo_stub = "zoo:nlp/multilabel_text_classification/obert-base/pytorch/huggingface/goemotions/pruned90_quant-none" pipeline = Pipeline.create( task="text-classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX From b13e28e30cb83027fef304ac58c2bd431a17b9a1 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:02:47 -0400 Subject: [PATCH 110/149] Update text-classification.md --- docs/use-cases/nlp/text-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index 53f1e73695..b1f6194582 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -320,7 +320,7 @@ print(resp.text) # {"labels":["positive"],"scores":[0.9330279231071472]} ``` -## Multi-Input Usage +#### Multi-Input Usage The CLI command below launches a single-input text classification pipeline with a 90% pruned-quantized oBERT model trained on MNLI: From f5a535c26c7d6e9f12b4b40ee600a946cf98533f Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:04:18 -0400 Subject: [PATCH 111/149] Update text-classification.md --- docs/use-cases/nlp/text-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index b1f6194582..84c704b53b 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -388,4 +388,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the [Server User Guide](../../deepsparse-server.md) for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From bae0836cf469ed367960a31a5d6cd055f05b003f Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:05:27 -0400 Subject: [PATCH 112/149] Update token-classification.md --- docs/use-cases/nlp/token-classification.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use-cases/nlp/token-classification.md b/docs/use-cases/nlp/token-classification.md index 1bc2a9cb95..f1f61eeb25 100644 --- a/docs/use-cases/nlp/token-classification.md +++ b/docs/use-cases/nlp/token-classification.md @@ -15,7 +15,7 @@ endpoint running DeepSparse with a single CLI. This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## Benchmarking @@ -190,7 +190,7 @@ print(output.predictions) ``` ### Cross Use Case Functionality -Check out the Pipeline User Guide for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server @@ -256,4 +256,4 @@ print(resp.text) ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From 98ec61e8388f9d0f06f767c51861061a90b866c9 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:14:47 -0400 Subject: [PATCH 113/149] Update token-classification.md --- docs/use-cases/nlp/token-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/token-classification.md b/docs/use-cases/nlp/token-classification.md index f1f61eeb25..98cf3d7adf 100644 --- a/docs/use-cases/nlp/token-classification.md +++ b/docs/use-cases/nlp/token-classification.md @@ -170,7 +170,7 @@ print(output.predictions) # TokenClassificationResult(entity='MISC', score=0.9991180896759033, word='italian', start=72, end=79, index=None, is_grouped=True)]] ``` -In comparison, here is the standard output withe no aggregation: +In comparison, here is the standard output with no aggregation: ```python from deepsparse import Pipeline From 8d1c257587932e6a3247afcf5c4dbae030c74ece Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:17:16 -0400 Subject: [PATCH 114/149] Update zero-shot-text-classification.md --- docs/use-cases/nlp/zero-shot-text-classification.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use-cases/nlp/zero-shot-text-classification.md b/docs/use-cases/nlp/zero-shot-text-classification.md index 8899c34c74..8ca13e0306 100644 --- a/docs/use-cases/nlp/zero-shot-text-classification.md +++ b/docs/use-cases/nlp/zero-shot-text-classification.md @@ -18,7 +18,7 @@ We will walk through an example of each. This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## Task Overview @@ -166,7 +166,7 @@ print(prediction) ``` ### Cross Use Case Functionality -Check out the Pipeline User Guide for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set up a REST endpoint for serving inferences over HTTP. DeepSparse Server wraps the Pipeline API, so it inherits all the utilities provided by Pipelines. @@ -236,4 +236,4 @@ print(resp.text) ``` ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From c3403d55f3966193fc854b630a778b5429a570ce Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:17:59 -0400 Subject: [PATCH 115/149] Update zero-shot-text-classification.md --- docs/use-cases/nlp/zero-shot-text-classification.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/use-cases/nlp/zero-shot-text-classification.md b/docs/use-cases/nlp/zero-shot-text-classification.md index 8ca13e0306..58e79b94f8 100644 --- a/docs/use-cases/nlp/zero-shot-text-classification.md +++ b/docs/use-cases/nlp/zero-shot-text-classification.md @@ -43,7 +43,6 @@ from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" -batch_size = 1 pipeline = Pipeline.create( task="zero_shot_text_classification", model_path=sparsezoo_stub, # sparsezoo stub or path to local ONNX From f86a976bf0c7c2f131264f22dba4cd58a896925e Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:19:55 -0400 Subject: [PATCH 116/149] Update zero-shot-text-classification.md --- docs/use-cases/nlp/zero-shot-text-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/nlp/zero-shot-text-classification.md b/docs/use-cases/nlp/zero-shot-text-classification.md index 58e79b94f8..14cdebd3bd 100644 --- a/docs/use-cases/nlp/zero-shot-text-classification.md +++ b/docs/use-cases/nlp/zero-shot-text-classification.md @@ -143,7 +143,7 @@ Additionally, we can pass a `model_config` to specify the form of the hypothesis For instance, rather than running the comparison with `"This text is related to {}"`, we can instead use `"This text is similiar to {}"` with the following: ```python -from deepsparse import Pipeline, Context +from deepsparse import Pipeline # download onnx from sparsezoo and compile with batch size 1 sparsezoo_stub = "zoo:nlp/text_classification/obert-base/pytorch/huggingface/mnli/pruned90_quant-none" From ffc491195a0a1d3fcd5a95a8344e11a681c93922 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:22:56 -0400 Subject: [PATCH 117/149] Update transformers-embedding-extraction.md --- docs/use-cases/nlp/transformers-embedding-extraction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use-cases/nlp/transformers-embedding-extraction.md b/docs/use-cases/nlp/transformers-embedding-extraction.md index 0c805aecf3..6753c360f1 100644 --- a/docs/use-cases/nlp/transformers-embedding-extraction.md +++ b/docs/use-cases/nlp/transformers-embedding-extraction.md @@ -17,7 +17,7 @@ For the embedding extraction case, we will walk through an example of Pipeline a This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## DeepSparse Pipelines @@ -118,7 +118,7 @@ print(len(embedding.embeddings[0])) # 768 ``` ### Cross Use Case Functionality -Check out the Pipeline User Guide for more details on configuring a Pipeline. +Check out the [Pipeline User Guide](../../user-guide/deepsparse-pipelines.md) for more details on configuring a Pipeline. ## DeepSparse Server Built on the popular FastAPI and Uvicorn stack, DeepSparse Server enables you to set-up a REST endpoint for serving inferences over HTTP. Since DeepSparse Server wraps the Pipeline API, it inherits all of the utilities provided by Pipelines. @@ -192,4 +192,4 @@ print(len(result["embeddings"][0])) ### Cross Use Case Functionality -Check out the Server User Guide for more details on configuring the Server. +Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. From 645fd24d4a40da63e4b961c48343d65994cd6c9e Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:27:10 -0400 Subject: [PATCH 118/149] Update embedding-extraction.md --- docs/use-cases/cv/embedding-extraction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/cv/embedding-extraction.md b/docs/use-cases/cv/embedding-extraction.md index e85307ef77..2ee9c81dab 100644 --- a/docs/use-cases/cv/embedding-extraction.md +++ b/docs/use-cases/cv/embedding-extraction.md @@ -2,9 +2,9 @@ This page explains how to deploy an Embedding Extraction Pipeline with DeepSparse. ## Installation Requirements -This use case requires the installation of [DeepSparse Server](/get-started/install/deepsparse). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](/user-guide/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## Model Format The Embedding Extraction Pipeline enables you to generate embeddings in any domain, meaning you can use it with any ONNX model. It (optionally) removes the projection head from the model, such that you can re-use SparseZoo models and custom models you have trained in the embedding extraction scenario. From f48b58dc25e4a95cf586564ebc9f537fe48a158c Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:27:27 -0400 Subject: [PATCH 119/149] Update image-classification.md --- docs/use-cases/cv/image-classification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md index c1ddd95136..6e4830d34e 100644 --- a/docs/use-cases/cv/image-classification.md +++ b/docs/use-cases/cv/image-classification.md @@ -17,7 +17,7 @@ This example uses ResNet-50. For a full list of pre-sparsified image classificat This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md). ## Benchmarking From aa59bc640e644f647f0b045f1c5c24df8559f3a7 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:27:38 -0400 Subject: [PATCH 120/149] Update image-segmentation-yolact.md --- docs/use-cases/cv/image-segmentation-yolact.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use-cases/cv/image-segmentation-yolact.md b/docs/use-cases/cv/image-segmentation-yolact.md index 8f8c677b70..bb31cc4f20 100644 --- a/docs/use-cases/cv/image-segmentation-yolact.md +++ b/docs/use-cases/cv/image-segmentation-yolact.md @@ -15,9 +15,9 @@ We will walk through an example of each using YOLACT. ## Installation Requirements -This tutorial requires the installation of [DeepSparse Server and YOLO](../../user-guide/installation.md). +This use case requires the installation of [DeepSparse Server](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md) ## Benchmarking From a62be3144f9f0c6cb5e3b7675dca54f923f55074 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:27:53 -0400 Subject: [PATCH 121/149] Update object-detection-yolov5.md --- docs/use-cases/cv/object-detection-yolov5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md index 7bffe760d0..06a7987f51 100644 --- a/docs/use-cases/cv/object-detection-yolov5.md +++ b/docs/use-cases/cv/object-detection-yolov5.md @@ -15,7 +15,7 @@ This example uses YOLOv5s. For a full list of pre-sparsified object detection mo This use case requires the installation of [DeepSparse Server and YOLO](../../user-guide/installation.md). -Confirm your machine is compatible with our [hardware requirements](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support). +Confirm your machine is compatible with our [hardware requirements](../../user-guide/hardware-support.md) ## Benchmarking From cb7d614f90a5049980cb1f8b81ba9a81a585692a Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:29:18 -0400 Subject: [PATCH 122/149] Update README.md --- src/deepsparse/server/README.md | 156 +------------------------------- 1 file changed, 1 insertion(+), 155 deletions(-) diff --git a/src/deepsparse/server/README.md b/src/deepsparse/server/README.md index c564ef1363..7e6db86bee 100644 --- a/src/deepsparse/server/README.md +++ b/src/deepsparse/server/README.md @@ -1,155 +1 @@ -## 🔌 DeepSparse Server - -```bash -pip install deepsparse[server] -``` - -The DeepSparse server allows you to serve models and pipelines for deployment in HTTP. The server runs on top of the popular FastAPI web framework and Uvicorn web server. -The server supports any task from deepsparse. Pipeline including NLP, image classification, and object detection tasks. -An updated list of available tasks can be found -[here](https://github.com/neuralmagic/deepsparse/blob/main/src/deepsparse/PIPELINES.md) - - - Run `deepsparse.server --help` to lookup the available CLI arguments. - -``` -Usage: deepsparse.server [OPTIONS] COMMAND [ARGS]... - - Start a DeepSparse inference server for serving the models and pipelines. - - 1. `deepsparse.server --config_file [OPTIONS] ` - - 2. `deepsparse.server task [OPTIONS] - - Examples for using the server: - - `deepsparse.server --config_file server-config.yaml` - - `deepsparse.server task question_answering --batch-size 2` - - `deepsparse.server task question_answering --host "0.0.0.0"` - - Example config.yaml for serving: - - \```yaml - num_cores: 2 - num_workers: 2 - endpoints: - - task: question_answering - route: /unpruned/predict - model: zoo:some/zoo/stub - name: question_answering_pipeline_1 - - task: question_answering - route: /pruned/predict - model: /path/to/local/model - name: question_answering_pipeline_2 - \``` - - Optionally, to manually specify the set of loggers, define a - dictionary that maps loggers' names to their initialization arguments: - - \```yaml - num_cores: 2 - num_workers: 2 - loggers: - prometheus: - port: 6100 - text_log_save_dir: /home/deepsparse-server/prometheus - text_log_save_frequency: 30 - endpoints: - - task: question_answering - ... - ... - \``` - -Options: - --help Show this message and exit. - -Commands: - config Run the server using configuration from a .yaml file. - task Run the server using configuration with CLI options, which can... -``` - -### Single Model Inference - -Example CLI command for serving a single model for the **question answering** task: - -```bash -deepsparse.server \ - task question_answering \ - --model_path "zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni" -``` - -To make a request to your server, use the `requests` library and pass the request URL: - -```python -import requests - -url = "http://localhost:5543/predict" - -obj = { - "question": "Who is Mark?", - "context": "Mark is batman." -} - -response = requests.post(url, json=obj) -``` - -In addition, you can make a request with a `curl` command from terminal: - -```bash -curl -X POST \ - 'http://localhost:5543/predict' \ - -H 'accept: application/json' \ - -H 'Content-Type: application/json' \ - -d '{ - "question": "Who is Mark?", - "context": "Mark is batman." -}' -``` -__ __ -### Multiple Model Inference -To serve multiple models you can build a `config.yaml` file. -In the sample YAML file below, we are defining two BERT models to be served by the `deepsparse.server` for the **question answering** task: - -```yaml -num_cores: 2 -num_workers: 2 -endpoints: - - task: question_answering - route: /unpruned/predict - model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/base-none - batch_size: 1 - - task: question_answering - route: /pruned/predict - model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni - batch_size: 1 -``` -You can now run the server with the config file path using the `config` sub command: - -```bash -deepsparse.server config config.yaml -``` - -You can send requests to a specific model by appending the model's `alias` from the `config.yaml` to the end of the request url. For example, to call the second model, you can send a request to its configured route: - -```python -import requests - -url = "http://localhost:5543/pruned/predict" - -obj = { - "question": "Who is Mark?", - "context": "Mark is batman." -} - -response = requests.post(url, json=obj) -``` - -💡 **PRO TIP** 💡: While your server is running, you can always use the awesome swagger UI that's built into FastAPI to view your model's pipeline `POST` routes. -The UI also enables you to easily make sample requests to your server. -All you need is to add `/docs` at the end of your host URL: - - localhost:5543/docs - -![alt text](./img/swagger_ui.png) - +Checkout [DeepSparse Server User Guide](../../../docs/user-guide/deepsparse-server.md) for usage details. From 352f7e3b3dfb5929fcfd647660ffa7dcb6bbdbd0 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:29:49 -0400 Subject: [PATCH 123/149] Update README.md --- src/deepsparse/server/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deepsparse/server/README.md b/src/deepsparse/server/README.md index 7e6db86bee..07cf22ab89 100644 --- a/src/deepsparse/server/README.md +++ b/src/deepsparse/server/README.md @@ -1 +1 @@ -Checkout [DeepSparse Server User Guide](../../../docs/user-guide/deepsparse-server.md) for usage details. +[Checkout DeepSparse Server User Guide for usage details](../../../docs/user-guide/deepsparse-server.md) From d215b47e2864a82986c438583508ce29fa4e4dd6 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:30:03 -0400 Subject: [PATCH 124/149] Update README.md --- src/deepsparse/server/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/deepsparse/server/README.md b/src/deepsparse/server/README.md index 07cf22ab89..8e53c2162e 100644 --- a/src/deepsparse/server/README.md +++ b/src/deepsparse/server/README.md @@ -1 +1,3 @@ +# DeepSparse Server + [Checkout DeepSparse Server User Guide for usage details](../../../docs/user-guide/deepsparse-server.md) From f365fba97c2d89bf50140e34ff799bc1412cc5f3 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:31:33 -0400 Subject: [PATCH 125/149] Update README.md --- src/deepsparse/image_classification/README.md | 200 +----------------- 1 file changed, 2 insertions(+), 198 deletions(-) diff --git a/src/deepsparse/image_classification/README.md b/src/deepsparse/image_classification/README.md index 1429ed8f5b..319d5eb0c9 100644 --- a/src/deepsparse/image_classification/README.md +++ b/src/deepsparse/image_classification/README.md @@ -1,199 +1,3 @@ -# Image Classification Inference Pipelines +# Image Classification Use Case - -[DeepSparse] Image Classification integration allows accelerated inference, -serving, and benchmarking of sparsified image classification models. -This integration allows for leveraging the DeepSparse Engine to run -sparsified image classification inference with GPU-class performance directly -on the CPU. - -The DeepSparse Engine takes advantage of sparsity within neural networks to -reduce compute as well as accelerate memory-bound workloads. -The Engine is particularly effective when leveraging sparsification methods -such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and -[quantization](https://arxiv.org/abs/1609.07061). These techniques result in -significantly more performant and smaller models with limited to no effect on -the baseline metrics. - -## Getting Started - -Before you start your adventure with the DeepSparse Engine, make sure that -your machine is compatible with our [hardware requirements]. - -### Installation - -```pip install deepsparse``` - -### Model Format - -By default, to deploy image classification models using the DeepSparse Engine, -the model should be supplied in the [ONNX] format. -This grants the Engine the flexibility to serve any model in a framework-agnostic -manner. - -Below we describe two possibilities to obtain the required ONNX model. - -#### Exporting the onnx file from the contents of a local checkpoint - -This pathway is relevant if you intend to deploy a model created using [SparseML] library. -For more information refer to the appropriate integration documentation in [SparseML]. - -1. The output of the [SparseML] training is saved to output directory `/{save_dir}` (e.g. `/trained_model`) -2. Depending on the chosen framework, the model files are saved to `model_path`=`/{save_dir}/{framework_name}/{model_tag}` (e.g `/trained_model/pytorch/resnet50/`) -3. To generate an onnx model, refer to the [script for image classification ONNX export](https://github.com/neuralmagic/sparseml/blob/main/src/sparseml/pytorch/image_classification/export.py). - -Example: -```bash -sparseml.image_classification.export_onnx \ - --arch-key resnet50 \ - --dataset imagenet \ - --dataset-path ~/datasets/ILSVRC2012 \ - --checkpoint-path ~/checkpoints/resnet50_checkpoint.pth -``` -This creates `model.onnx` file, in the parent directory of your `model_path` - -#### Directly using the SparseZoo stub - -Alternatively, you can skip the process of onnx model export by downloading all the required model data directly from Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). -Example: -```python -from sparsezoo import Model - -# you can lookup an appropriate model stub here: https://sparsezoo.neuralmagic.com/ -model_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none" -model = Model(model_stub) - -# directly download the model data to your local directory -model_path = model.path - -# the onnx model file is there, ready for deployment -import os -os.path.isfile(model.onnx_model.path) ->>>True -``` - - -## Deployment APIs - -DeepSparse provides both a python Pipeline API and an out-of-the-box model -server that can be used for end-to-end inference in either existing python -workflows or as an HTTP endpoint. Both options provide similar specifications -for configurations and support a variety of Image Classification models. - -### Python API - -Pipelines are the default interface for running the inference with the -DeepSparse Engine. - -Once a model is obtained, either through [SparseML] training or directly from [SparseZoo], -`deepsparse.Pipeline` can be used to easily facilitate end to end inference and deployment -of the sparsified image classification model. - -If no model is specified to the `Pipeline` for a given task, the `Pipeline` will automatically -select a pruned and quantized model for the task from the `SparseZoo` that can be used for accelerated -inference. Note that other models in the [SparseZoo] will have different tradeoffs between speed, size, -and accuracy. - -To learn about sparsification in more detail, refer to [SparseML docs](https://docs.neuralmagic.com/sparseml/) - -### HTTP Server - -As an alternative to Python API, the DeepSparse inference server allows you to -serve ONNX models and pipelines in HTTP. Both configuring and making requests -to the server follow the same parameters and schemas as the Pipelines enabling -simple deployment. Once launched, a `/docs` endpoint is created with full -endpoint descriptions and support for making sample requests. - -Example deployment using a 95% pruned resnet50 is given below -For full documentation on deploying sparse image classification models with the -DeepSparse Server, see the [documentation](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/server). - -##### Installation - -The deepsparse server requirements can be installed by specifying the `server` -extra dependency when installing DeepSparse. - -```bash -pip install deepsparse[server] -``` - -## Deployment Use Cases - -The following section includes example usage of the Pipeline and server APIs for -various image classification models. - -[List of Image Classification SparseZoo Models](https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=classification&page=1) - - -#### Python Pipeline - -```python -from deepsparse import Pipeline -cv_pipeline = Pipeline.create( - task='image_classification', - model_path='zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none', # Path to checkpoint or SparseZoo stub -) -input_image = "my_image.png" # path to input image -inference = cv_pipeline(images=input_image) -``` - -#### HTTP Server - -Spinning up: -```bash -deepsparse.server \ - task image_classification \ - --model_path "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none" \ - --port 5543 -``` - -Making a request: -```python -import requests - -url = 'http://0.0.0.0:5543/predict/from_files' -path = ['goldfish.jpeg'] # just put the name of images in here -files = [('request', open(img, 'rb')) for img in path] -resp = requests.post(url=url, files=files) -``` - -### Benchmarking - -The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. -Want to find out how fast our sparse ONNX models perform inference? -You can quickly do benchmarking tests on your own with a single CLI command! - -You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: -```bash -deepsparse.benchmark zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none -``` -Output: -```bash -Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none -Batch Size: 1 -Scenario: async -Throughput (items/sec): 299.2372 -Latency Mean (ms/batch): 16.6677 -Latency Median (ms/batch): 16.6748 -Latency Std (ms/batch): 0.1728 -Iterations: 2995 -``` - -To learn more about benchmarking, refer to the appropriate documentation. -Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! - -## Tutorials: -For a deeper dive into using image classification models within the Neural Magic -ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/): -- [CV Use Cases](https://neuralmagic.com/use-cases/#computervision) - -## Support -For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). - - -[DeepSparse]: https://github.com/neuralmagic/deepsparse -[hardware requirements]: https://docs.neuralmagic.com/deepsparse/source/hardware.html -[ONNX]: https://onnx.ai/ -[SparseML]: https://github.com/neuralmagic/sparseml -[SparseML Image Classification Documentation]: https://github.com/neuralmagic/sparseml/tree/main/src/sparseml/pytorch/image_classification/README_image_classification.md -[SparseZoo]: https://sparsezoo.neuralmagic.com/ \ No newline at end of file +[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/image-classification.md) From 73ec5494f5990e0dfba147d58bcf2afdcf80e3be Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:32:14 -0400 Subject: [PATCH 126/149] Update README.md --- src/deepsparse/benchmark/README.md | 174 +---------------------------- 1 file changed, 2 insertions(+), 172 deletions(-) diff --git a/src/deepsparse/benchmark/README.md b/src/deepsparse/benchmark/README.md index c67133744e..435e9f70f9 100644 --- a/src/deepsparse/benchmark/README.md +++ b/src/deepsparse/benchmark/README.md @@ -14,176 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> -## 📜 Benchmarking ONNX Models +# DeepSparse Benchmarking -`deepsparse.benchmark` is a command-line (CLI) tool for benchmarking the DeepSparse Engine with ONNX models. The tool will parse the arguments, download/compile the network into the engine, generate input tensors, and execute the model depending on the chosen scenario. By default, it will choose a multi-stream or asynchronous mode to optimize for throughput. - -### Quickstart - -After `pip install deepsparse`, the benchmark tool is available on your CLI. For example, to benchmark a dense BERT ONNX model fine-tuned on the SST2 dataset where the model path is the minimum input required to get started, run: - -``` -deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none -``` -__ __ -### Usage - -In most cases, good performance will be found in the default options so it can be as simple as running the command with a SparseZoo model stub or your local ONNX model. However, if you prefer to customize benchmarking for your personal use case, you can run `deepsparse.benchmark -h` or with `--help` to view your usage options: - -CLI Arguments: -``` -positional arguments: - - model_path Path to an ONNX model file or SparseZoo model stub. - -optional arguments: - - -h, --help show this help message and exit. - - -b BATCH_SIZE, --batch_size BATCH_SIZE - The batch size to run the analysis for. Must be - greater than 0. - - -shapes INPUT_SHAPES, --input_shapes INPUT_SHAPES - Override the shapes of the inputs, i.e. -shapes - "[1,2,3],[4,5,6],[7,8,9]" results in input0=[1,2,3] - input1=[4,5,6] input2=[7,8,9]. - - -ncores NUM_CORES, --num_cores NUM_CORES - The number of physical cores to run the analysis on, - defaults to all physical cores available on the system. - - -s {async,sync,elastic}, --scenario {async,sync,elastic} - Choose between using the async, sync and elastic - scenarios. Sync and async are similar to the single- - stream/multi-stream scenarios. Elastic is a newer - scenario that behaves similarly to the async scenario - but uses a different scheduling backend. Default value - is async. - - -t TIME, --time TIME - The number of seconds the benchmark will run. Default - is 10 seconds. - - -w WARMUP_TIME, --warmup_time WARMUP_TIME - The number of seconds the benchmark will warmup before - running.Default is 2 seconds. - - -nstreams NUM_STREAMS, --num_streams NUM_STREAMS - The number of streams that will submit inferences in - parallel using async scenario. Default is - automatically determined for given hardware and may be - sub-optimal. - - -pin {none,core,numa}, --thread_pinning {none,core,numa} - Enable binding threads to cores ('core' the default), - threads to cores on sockets ('numa'), or disable - ('none'). - - -e {deepsparse,onnxruntime}, --engine {deepsparse,onnxruntime} - Inference engine backend to run eval on. Choices are - 'deepsparse', 'onnxruntime'. Default is 'deepsparse'. - - -q, --quiet Lower logging verbosity. - - -x EXPORT_PATH, --export_path EXPORT_PATH - Store results into a JSON file. -``` -💡**PRO TIP**💡: save your benchmark results in a convenient JSON file! - -Example CLI command for benchmarking an ONNX model from the SparseZoo and saving the results to a `benchmark.json` file: - -``` -deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none -x benchmark.json -``` -Output of the JSON file: - -![alt text](./img/json_output.png) - -#### Sample CLI Argument Configurations - -To run a sparse FP32 MobileNetV1 at batch size 16 for 10 seconds for throughput using 8 streams of requests: - -``` -deepsparse.benchmark zoo:cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-moderate --batch_size 16 --time 10 --scenario async --num_streams 8 -``` - -To run a sparse quantized INT8 6-layer BERT at batch size 1 for latency: - -``` -deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_quant_6layers-aggressive_96 --batch_size 1 --scenario sync -``` -__ __ -### ⚡ Inference Scenarios - -#### Synchronous (Single-stream) Scenario - -Set by the `--scenario sync` argument, the goal metric is latency per batch (ms/batch). This scenario submits a single inference request at a time to the engine, recording the time taken for a request to return an output. This mimics an edge deployment scenario. - -The latency value reported is the mean of all latencies recorded during the execution period for the given batch size. - -#### Asynchronous (Multi-stream) Scenario - -Set by the `--scenario async` argument, the goal metric is throughput in items per second (i/s). This scenario submits `--num_streams` concurrent inference requests to the engine, recording the time taken for each request to return an output. This mimics a model server or bulk batch deployment scenario. - -The throughput value reported comes from measuring the number of finished inferences within the execution time and the batch size. - -#### Example Benchmarking Output of Synchronous vs. Asynchronous - -**BERT 3-layer FP32 Sparse Throughput** - -No need to add *scenario* argument since `async` is the default option: -``` -deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -[INFO benchmark_model.py:202 ] Thread pinning to cores enabled -DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) -[INFO benchmark_model.py:247 ] deepsparse.engine.Engine: - onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx - batch_size: 1 - num_cores: 18 - scheduler: Scheduler.multi_stream - cpu_avx_type: avx512 - cpu_vnni: False -[INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] -[INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] -[INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] -[INFO benchmark_model.py:264 ] num_streams default value chosen of 9. This requires tuning and may be sub-optimal -[INFO benchmark_model.py:270 ] Starting 'async' performance measurements for 10 seconds -Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -Batch Size: 1 -Scenario: multistream -Throughput (items/sec): 83.5037 -Latency Mean (ms/batch): 107.3422 -Latency Median (ms/batch): 107.0099 -Latency Std (ms/batch): 12.4016 -Iterations: 840 -``` - -**BERT 3-layer FP32 Sparse Latency** - -To select a *synchronous inference scenario*, add `-s sync`: - -``` -deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -s sync -[INFO benchmark_model.py:202 ] Thread pinning to cores enabled -DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) -[INFO benchmark_model.py:247 ] deepsparse.engine.Engine: - onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx - batch_size: 1 - num_cores: 18 - scheduler: Scheduler.single_stream - cpu_avx_type: avx512 - cpu_vnni: False -[INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] -[INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] -[INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] -[INFO benchmark_model.py:270 ] Starting 'sync' performance measurements for 10 seconds -Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -Batch Size: 1 -Scenario: singlestream -Throughput (items/sec): 62.1568 -Latency Mean (ms/batch): 16.0732 -Latency Median (ms/batch): 15.7850 -Latency Std (ms/batch): 1.0427 -Iterations: 622 -``` \ No newline at end of file +[Checkout DeepSparse Benchmarking User Guide for usage details](../../../docs/user-guide/deepsparse-benchmarking.md) From 08b5c692df8cf71ec3a5875895f3267f12f2c998 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:33:36 -0400 Subject: [PATCH 127/149] Update README.md --- src/deepsparse/yolact/README.md | 187 +------------------------------- 1 file changed, 2 insertions(+), 185 deletions(-) diff --git a/src/deepsparse/yolact/README.md b/src/deepsparse/yolact/README.md index bff000a13d..78d77576db 100644 --- a/src/deepsparse/yolact/README.md +++ b/src/deepsparse/yolact/README.md @@ -1,186 +1,3 @@ -# YOLACT Inference Pipelines -DeepSparse allows accelerated inference, serving, and benchmarking of sparsified YOLACT models [YOLACT] ([original codebase](https://github.com/dbolya/yolact) and [paper](https://arxiv.org/abs/1904.02689)). -This integration allows for leveraging the DeepSparse Engine to run the sparsified YOLACT inference with GPU-class performance directly on the CPU. +# Instance Segmentation Use Case (YOLACT) -The DeepSparse Engine is taking advantage of sparsity within neural networks to -reduce compute required as well as accelerate memory-bound workloads. The engine is particularly effective when leveraging sparsification -methods such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and [quantization](https://arxiv.org/abs/1609.07061). -These techniques result in significantly more performant and smaller models with limited to no effect on the baseline metrics. - - -## Getting Started - -Before you start your adventure with the DeepSparse Engine, make sure that your machine is -compatible with our [hardware requirements](https://docs.neuralmagic.com/deepsparse/source/hardware.html). - -### Installation - -```pip install deepsparse``` - -### Model Format -By default, to deploy YOLACT using DeepSparse Engine it is required to supply the model in the ONNX format. -This grants the engine the flexibility to serve any model in a framework-agnostic environment. - -Below we describe two possibilities to obtain the required ONNX model. - -### Exporting the ONNX File From the Contents of a Local Directory -This pathway is relevant if you intend to deploy a model created using the [SparseML](https://github.com/neuralmagic/sparseml) library. -For more information refer to the [appropriate YOLACT integration documentation in SparseML](https://github.com/neuralmagic/sparseml/tree/main/integrations/dbolya-yolact) - -After training your model with `SparseML`, locate the `.pth` file for the model you'd like to export and run the `SparseML` integrated YOLACT ONNX export script below. - -```bash -sparseml.yolact.export_onnx --checkpoint PATH_TO_YOLACT_PTH_CHECKPOINT -``` -This creates `model.onnx` file, in the directory of your `weights` (e.g. `/weights/model.onnx`). -For additional options invoke the command-line callable with `--help` option like `sparseml.yolact.export_onnx --help` - -#### SparseZoo Stub -Alternatively, you can skip the process of the ONNX model export by using Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). The SparseZoo contains pre-sparsified models and SparseZoo stubs enable you to reference any model on the SparseZoo in a convenient and predictable way. -All of DeepSparse's pipelines and APIs can use a SparseZoo stub in place of a local folder. The Deployment APIs examples use SparseZoo stubs to highlight this pathway. -## Deployment APIs -DeepSparse provides both a Python Pipeline API and an out-of-the-box model server -that can be used for end-to-end inference in either existing Python workflows or as an HTTP endpoint. -Both options provide similar specifications for configurations and support annotation serving for all -YOLACT models. - -### Python Pipelines -Pipelines are the default interface for running inference with the DeepSparse Engine. - -Once a model is obtained, either through SparseML training or directly from SparseZoo, `deepsparse.Pipeline` can be used to easily facilitate end-to-end inference and deployment of the sparsified neural networks. - -If no model is specified to the Pipeline for a given task, the Pipeline will automatically select a pruned and quantized model for the task from the SparseZoo that can be used for accelerated inference. Note that other models in the SparseZoo will have different tradeoffs between speed, size, and accuracy. - -### DeepSparse Server -As an alternative to Python API, the DeepSparse Server allows you to serve ONNX models and pipelines in HTTP. -Both configuring and making requests to the server follow the same parameters and schemas as the -Pipelines enabling simple deployment. Once launched, a `/docs` endpoint is created with full -endpoint descriptions and support for making sample requests. - -An example of starting and requesting a DeepSparse Server for YOLACT is given below. - -#### Installation -The Deepsparse Server requirements can be installed by specifying the `server` extra dependency when installing -DeepSparse. - -```bash -pip install deepsparse[server] -``` - -## Deployment Example -The following example uses pipelines to run a pruned and quantized YOLACT model for inference, downloaded by default from the SparseZoo. -As input the pipeline ingests an image (or a list of images) and returns for each image the detection boxes, classification labels, probability scores and segmentation masks. - -[List of the YOLACT SparseZoo Models]( -https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=segmentation&page=1) - -If you don't have an image ready, pull a sample image down with - -```bash -wget -O thailand.jpg https://raw.githubusercontent.com/neuralmagic/deepsparse/main/src/deepsparse/yolact/sample_images/thailand.jpg -``` - -```python -from deepsparse.pipeline import Pipeline - -model_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" -images = ["thailand.jpg"] - -yolact_pipeline = Pipeline.create( - task="yolact", - model_path=model_stub, - class_names="coco", -) - -predictions = yolact_pipeline(images=images, confidence_threshold=0.2,nms_threshold = 0.5) -# predictions has attributes `boxes`, `classes`, `masks` and `scores` -predictions.classes[0] ->> ['elephant', 'elephant', 'person', ...] - -``` - -#### Annotate CLI -You can also use the annotate command to have the engine save an annotated photo on disk. -```bash -deepsparse.instance_segmentation.annotate --source thailand.jpg #Try --source 0 to annotate your live webcam feed -``` - -Running the above command will create an `annotation-results` folder and save the annotated image inside. - -

-original annotated -

-

-Image annotated with 82.8% sparse and quantized YOLACT -

- -If a `--model_filepath` arg isn't provided, then `zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none` will be used by default. - - -#### HTTP Server -Spinning up: -```bash -deepsparse.server \ - task yolact \ - --model_path "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" -``` - -Making a request: -```python -import requests -import json - -url = 'http://0.0.0.0:5543/predict/from_files' -path = ['thailand.jpg'] # list of images for inference -files = [('request', open(img, 'rb')) for img in path] -resp = requests.post(url=url, files=files) -annotations = json.loads(resp.text) # dictionary of annotation results -boxes, classes, masks, scores = annotations["boxes"], annotations["classes"], annotations["masks"], annotations["scores"] -``` - -### Benchmarking -The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. Want to find out how fast our sparse YOLOv5 ONNX models perform inference? -You can quickly do benchmarking tests on your own with a single CLI command! - -You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: - -```bash -deepsparse.benchmark \ - zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none \ - --scenario sync -``` - -Output: - -```bash -2022-07-05 10:47:09 deepsparse.benchmark.benchmark_model INFO Thread pinning to cores enabled -DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.13.0.20220628 (51925ae2) (release) (optimized) (system=avx2, binary=avx2) -2022-07-05 10:47:23 deepsparse.benchmark.benchmark_model INFO num_streams default value chosen of 12. This requires tuning and may be sub-optimal -2022-07-05 10:47:23 deepsparse.benchmark.benchmark_model INFO deepsparse.engine.Engine: - onnx_file_path: /home/damian/.cache/sparsezoo/099e086b-1e84-450c-ab3b-90038c591554/model.onnx - batch_size: 1 - num_cores: 24 - num_streams: 0 - scheduler: Scheduler.default - cpu_avx_type: avx2 - cpu_vnni: False -2022-07-05 10:47:23 deepsparse.utils.onnx INFO Generating input 'input', type = float32, shape = [1, 3, 550, 550] -2022-07-05 10:47:23 deepsparse.benchmark.benchmark_model INFO Starting 'singlestream' performance measurements for 10 seconds -Original Model Path: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none -Batch Size: 1 -Scenario: sync -Throughput (items/sec): 26.8650 -Latency Mean (ms/batch): 37.2062 -Latency Median (ms/batch): 37.2043 -Latency Std (ms/batch): 0.0741 -Iterations: 269 -``` - -To learn more about benchmarking, refer to the appropriate documentation. -Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! - -## Tutorials: -For a deeper dive into using YOLACT within the Neural Magic ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/use-cases/#computervision). - -## Support -For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). +[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/image-segmentation-yolact.md) From 45815069fd744c0c4dd74944687bd792be302cc9 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:34:19 -0400 Subject: [PATCH 128/149] Update README.md --- src/deepsparse/yolo/README.md | 170 +--------------------------------- 1 file changed, 2 insertions(+), 168 deletions(-) diff --git a/src/deepsparse/yolo/README.md b/src/deepsparse/yolo/README.md index 5d37fbd944..6d3f9340bd 100644 --- a/src/deepsparse/yolo/README.md +++ b/src/deepsparse/yolo/README.md @@ -1,169 +1,3 @@ -# YOLOv5 Inference Pipelines +# Object Detection Use Case (YOLOv5) - -DeepSparse allows accelerated inference, serving, and benchmarking of sparsified [Ultralytics YOLOv5](https://github.com/ultralytics/yolo) models. -This integration allows for leveraging the DeepSparse Engine to run the sparsified YOLOv5 inference with GPU-class performance directly on the CPU. - -The DeepSparse Engine is taking advantage of sparsity within neural networks to -reduce compute required as well as accelerate memory-bound workloads. The engine is particularly effective when leveraging sparsification -methods such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and [quantization](https://arxiv.org/abs/1609.07061). -These techniques result in significantly more performant and smaller models with limited to no effect on the baseline metrics. - -This integration currently supports the original YOLOv5 and updated V6.1 architectures. - -## Getting Started - -Before you start your adventure with the DeepSparse Engine, make sure that your machine is -compatible with our [hardware requirements](https://docs.neuralmagic.com/deepsparse/source/hardware.html). - -### Installation - -```pip install deepsparse[yolo]``` - -### Model Format -By default, to deploy YOLOv5 using DeepSparse Engine it is required to supply the model in the ONNX format. -This grants the engine the flexibility to serve any model in a framework-agnostic environment. - -Below we describe two possibilities to obtain the required ONNX model. - -### Exporting the ONNX File From the Contents of a Local Directory -This pathway is relevant if you intend to deploy a model created using the [SparseML](https://github.com/neuralmagic/sparseml) library. -For more information refer to the [appropriate YOLOv5 integration documentation in SparseML](https://github.com/neuralmagic/sparseml/tree/main/src/sparseml/yolov5). - -After training your model with `SparseML`, locate the `.pt` file for the model you'd like to export and run the `SparseML` integrated YOLOv5 ONNX export script below. - -```bash -sparseml.yolov5.export_onnx \ - --weights path/to/your/model \ - --dynamic #Allows for dynamic input shape -``` -This creates `model.onnx` file, in the directory of your `weights` (e.g. `runs/train/weights/model.onnx`). - -#### SparseZoo Stub -Alternatively, you can skip the process of the ONNX model export by using Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). The SparseZoo contains pre-sparsified models and SparseZoo stubs enable you to reference any model on the SparseZoo in a convenient and predictable way. -All of DeepSparse's pipelines and APIs can use a SparseZoo stub in place of a local folder. The Deployment APIs examples use SparseZoo stubs to highlight this pathway. -## Deployment APIs - -DeepSparse provides both a Python Pipeline API and an out-of-the-box model server -that can be used for end-to-end inference in either existing Python workflows or as an HTTP endpoint. -Both options provide similar specifications for configurations and support annotation serving for all -YOLOv5 models. - -### Python Pipelines -Pipelines are the default interface for running inference with the DeepSparse Engine. - -Once a model is obtained, either through SparseML training or directly from SparseZoo, `deepsparse.Pipeline` can be used to easily facilitate end-to-end inference and deployment of the sparsified neural networks. - -If no model is specified to the Pipeline for a given task, the Pipeline will automatically select a pruned and quantized model for the task from the SparseZoo that can be used for accelerated inference. Note that other models in the SparseZoo will have different tradeoffs between speed, size, and accuracy. - -### DeepSparse Server -As an alternative to Python API, the DeepSparse Server allows you to serve ONNX models and pipelines in HTTP. -Both configuring and making requests to the server follow the same parameters and schemas as the -Pipelines enabling simple deployment. Once launched, a `/docs` endpoint is created with full -endpoint descriptions and support for making sample requests. - -An example of starting and requesting a DeepSparse Server for YOLOv5 is given below. - -#### Installation -The Deepsparse Server requirements can be installed by specifying the `server` extra dependency when installing -DeepSparse. - -```bash -pip install deepsparse[yolo,server] -``` - -## Deployment Example -The following example uses pipelines to run a pruned and quantized YOLOv5l model for inference, downloaded by default from the SparseZoo. As input the pipeline ingests a list of images and returns for each image the detection boxes in numeric form. - -[List of the YOLOv5 SparseZoo Models]( -https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=detection&page=1) - -If you don't have an image ready, pull a sample image down with - -```bash -wget -O basilica.jpg https://raw.githubusercontent.com/neuralmagic/deepsparse/main/src/deepsparse/yolo/sample_images/basilica.jpg -``` - -```python -from deepsparse import Pipeline - -model_stub = "zoo:cv/detection/yolov5-l/pytorch/ultralytics/coco/pruned-aggressive_98" -images = ["basilica.jpg"] - -yolo_pipeline = Pipeline.create( - task="yolo", - model_path=model_stub, -) - -pipeline_outputs = yolo_pipeline(images=images, iou_thres=0.6, conf_thres=0.001) -``` - -#### Annotate CLI -You can also use the annotate command to have the engine save an annotated photo on disk. -```bash -deepsparse.object_detection.annotate --source basilica.jpg #Try --source 0 to annotate your live webcam feed -``` - -Running the above command will create an `annotation-results` folder and save the annotated image inside. - -

-original annotated -

-

-Image annotated with 96% sparse YOLOv5s -

- -If a `--model_filepath` arg isn't provided, then `zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned-aggressive_96` will be used by default. - - -#### HTTP Server -Spinning up: -```bash -deepsparse.server \ - task yolo \ - --model_path "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94" -``` - -Making a request: -```python -import requests -import json - -url = 'http://0.0.0.0:5543/predict/from_files' -path = ['basilica.jpg'] # list of images for inference -files = [('request', open(img, 'rb')) for img in path] -resp = requests.post(url=url, files=files) -annotations = json.loads(resp.text) # dictionary of annotation results -bounding_boxes = annotations["boxes"] -labels = annotations["labels"] -``` - -### Benchmarking -The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. Want to find out how fast our sparse YOLOv5 ONNX models perform inference? -You can quickly do benchmarking tests on your own with a single CLI command! - -You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: - -```bash -deepsparse.benchmark \ - zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 \ - --scenario sync - ->> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 ->> Batch Size: 1 ->> Scenario: sync ->> Throughput (items/sec): 74.0355 ->> Latency Mean (ms/batch): 13.4924 ->> Latency Median (ms/batch): 13.4177 ->> Latency Std (ms/batch): 0.2166 ->> Iterations: 741 -``` - -To learn more about benchmarking, refer to the appropriate documentation. -Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! - -## Tutorials: -For a deeper dive into using YOLOv5 within the Neural Magic ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/use-cases/#computervision). - -## Support -For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). +[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/object-detection-yolo.md) From 3e733750c9d123ece71ed662934b6e6cb5cbdf52 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:34:34 -0400 Subject: [PATCH 129/149] Update README.md --- src/deepsparse/yolo/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deepsparse/yolo/README.md b/src/deepsparse/yolo/README.md index 6d3f9340bd..4746086859 100644 --- a/src/deepsparse/yolo/README.md +++ b/src/deepsparse/yolo/README.md @@ -1,3 +1,3 @@ # Object Detection Use Case (YOLOv5) -[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/object-detection-yolo.md) +[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/object-detection-yolov5.md) From c560c09bba591321ff4eb7e58101c5324f135710 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:37:38 -0400 Subject: [PATCH 130/149] Update README.md --- src/deepsparse/transformers/README.md | 333 +------------------------- 1 file changed, 9 insertions(+), 324 deletions(-) diff --git a/src/deepsparse/transformers/README.md b/src/deepsparse/transformers/README.md index 7cc0439dcc..dbf32ec1a4 100644 --- a/src/deepsparse/transformers/README.md +++ b/src/deepsparse/transformers/README.md @@ -1,324 +1,9 @@ -# Hugging Face Transformer Inference Pipelines - - -DeepSparse allows accelerated inference, serving, and benchmarking of sparsified [Hugging Face Transformer](https://github.com/huggingface/transformers) models. -This integration allows for leveraging the DeepSparse Engine to run the sparsified transformer inference with GPU-class performance directly on the CPU. - -The DeepSparse Engine is taking advantage of sparsity within neural networks to -reduce compute required as well as accelerate memory-bound workloads. The engine is particularly effective when leveraging sparsification -methods such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and [quantization](https://arxiv.org/abs/1609.07061). -These techniques result in significantly more performant and smaller models with limited to no effect on the baseline metrics. - -This integration currently supports several fundamental NLP tasks: -- **Question Answering** - posing questions about a document -- **Sentiment Analysis** - assigning a sentiment to a piece of text -- **Text Classification** - assigning a label or class to a piece of text (e.g duplicate question pairing) -- **Token Classification** - attributing a label to each token in a sentence (e.g. Named Entity Recognition task) - -We are actively working on adding more use cases, stay tuned! - -## Getting Started - -Before you start your adventure with the DeepSparse Engine, make sure that your machine is -compatible with our [hardware requirements](https://docs.neuralmagic.com/deepsparse/source/hardware.html). - -### Installation - -```pip install deepsparse``` - -### Model Format -By default, to deploy the transformer using DeepSparse Engine it is required to supply the model in the ONNX format along with the HuggingFace supporting files. -This grants the engine the flexibility to serve any model in a framework-agnostic environment. - -The DeepSparse pipelines require the following files within a folder on the local server to properly load a Transformers model: -- `model.onnx`: The exported Transformers model in the [ONNX format](https://github.com/onnx/onnx). -- `tokenizer.json`: The [HuggingFace compatible tokenizer configuration](https://huggingface.co/docs/transformers/fast_tokenizers) used with the model. -- `config.json`: The [HuggingFace compatible configuration file](https://huggingface.co/docs/transformers/main_classes/configuration) used with the model. - -Below we describe two possibilities to obtain the required structure. - -#### SparseML Export -This pathway is relevant if you intend to deploy a model created using [SparseML](https://github.com/neuralmagic/sparseml) library. -For more information, refer to the appropriate [transformers integration documentation in SparseML](https://github.com/neuralmagic/sparseml/tree/main/src/sparseml/transformers). - -ONNX models can be exported using the `sparseml.transformers.export_onnx` tool: - -```bash -sparseml.transformers.export_onnx --task question-answering --model_path model_path -``` - -This creates `model.onnx` file, in the directory of your `model_path`(e.g. `/trained_model/model.onnx`). -The `tokenizer.json` and `config.json` are stored under the `model_path` folder as well, so a DeepSparse pipeline ca be directly instantiated by using that folder after export (e.g. `/trained_model/`). - -#### SparseZoo Stub -Alternatively, you can skip the process of the ONNX model export by using Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). The SparseZoo contains pre-sparsified models and SparseZoo stubs enable you to reference any model on the SparseZoo in a convenient and predictable way. -All of DeepSparse's pipelines and APIs can use a SparseZoo stub in place of a local folder. The Deployment APIs examples use SparseZoo stubs to highlight this pathway. - -## Deployment APIs - -DeepSparse provides both a Python Pipeline API and an out-of-the-box model server -that can be used for end-to-end inference in either existing python workflows or as an HTTP endpoint. -Both options provide similar specifications for configurations and support a variety of NLP transformers -tasks including question answering, text classification, sentiment analysis, and token classification. - -### Python Pipelines -Pipelines are the default interface for running inference with the DeepSparse Engine. - -Once a model is obtained, either through `SparseML` training or directly from `SparseZoo`, -`deepsparse.Pipeline` can be used to easily facilitate end to end inference and deployment -of the sparsified transformers model. - -If no model is specified to the `Pipeline` for a given task, the `Pipeline` will automatically -select a pruned and quantized model for the task from the `SparseZoo` that can be used for accelerated -inference. Note that other models in the SparseZoo will have different tradeoffs between speed, size, -and accuracy. - -### HTTP Server -As an alternative to Python API, the DeepSparse Server allows you to serve ONNX models and pipelines in HTTP. -Both configuring and making requests to the server follow the same parameters and schemas as the -Pipelines enabling simple deployment. Once launched, a `/docs` endpoint is created with full -endpoint descriptions and support for making sample requests. - -Example deployments using NLP transformer models are provided below. -For full documentation on deploying sparse transformer models with the DeepSparse Server, see the -[documentation](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/server). - -#### Installation -The DeepSparse Server requirements can be installed by specifying the `server` extra dependency when installing -DeepSparse. - -```bash -pip install deepsparse[server] -``` - -## Deployment Use Cases -The following section includes example usage of the Pipeline and server APIs for various NLP transformers tasks. - -### Question Answering -The question answering tasks accepts a `question` and a `context`. The pipeline will predict an answer -for the `question` as a substring of the `context`. The following examples use a pruned and quantized -question answering BERT model trained on the `SQuAD` dataset downloaded by default from the SparseZoo. - -[List of available SparseZoo Question Answering Models]( -https://sparsezoo.neuralmagic.com/?page=1&domain=nlp&sub_domain=question_answering) - -#### Python Pipeline - -```python -from deepsparse import Pipeline - -qa_pipeline = Pipeline.create(task="question-answering") -inference = qa_pipeline(question="What's my name?", context="My name is Snorlax") - ->> {'score': 0.9947717785835266, 'start': 11, 'end': 18, 'answer': 'Snorlax'} -``` - -#### HTTP Server -Spinning up: -```bash -deepsparse.server \ - task question-answering \ - --model_path "zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni" -``` - -Making a request: -```python -import requests - -url = "http://localhost:5543/predict" # Server's port default to 5543 - -obj = { - "question": "Who is Mark?", - "context": "Mark is batman." -} - -response = requests.post(url, json=obj) -response.text - ->> '{"score":0.9534820914268494,"start":8,"end":14,"answer":"batman"}' -``` - -### Sentiment Analysis -The sentiment analysis task takes in a sentence and classifies its sentiment. The following example -uses a pruned and quantized text sentiment analysis BERT model trained on the `sst2` dataset downloaded -from the SparseZoo. This `sst2` model classifies sentences as positive or negative. - -[List of available SparseZoo Sentiment Analysis Models]( -https://sparsezoo.neuralmagic.com/?domain=nlp&sub_domain=sentiment_analysis) - -#### Python Pipeline -```python -from deepsparse import Pipeline - -sa_pipeline = Pipeline.create(task="sentiment-analysis") - -inference = sa_pipeline("Snorlax loves my Tesla!") - ->> [{'label': 'LABEL_1', 'score': 0.9884248375892639}] # positive sentiment - -inference = sa_pipeline("Snorlax hates pineapple pizza!") - ->> [{'label': 'LABEL_0', 'score': 0.9981569051742554}] # negative sentiment -``` - -#### HTTP Server -Spinning up: -```bash -deepsparse.server \ - task sentiment-analysis \ - --model_path "zoo:nlp/sentiment_analysis/bert-base/pytorch/huggingface/sst2/12layer_pruned80_quant-none-vnni" -``` - -Making a request: -```python -import requests - -url = "http://localhost:5543/predict" # Server's port default to 5543 - -obj = {"sequences": "Snorlax loves my Tesla!"} - -response = requests.post(url, json=obj) -response.text - ->> '{"labels":["LABEL_1"],"scores":[0.9884248375892639]}' -``` - -### Text Classification -The text classification task supports binary, multi class, and regression predictions over -sentence inputs. The following example uses a pruned and quantized text classification -DistilBERT model trained on the `qqp` dataset downloaded from a SparseZoo stub. -The `qqp` dataset takes pairs of questions and predicts if they are a duplicate or not. - -[List of available SparseZoo Text Classification Models]( -https://sparsezoo.neuralmagic.com/?page=1&domain=nlp&sub_domain=text_classification) - -#### Python Pipeline -```python -from deepsparse import Pipeline - -tc_pipeline = Pipeline.create( - task="text-classification", - model_path="zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/qqp/pruned80_quant-none-vnni", -) - -# inference of duplicate question pair -inference = tc_pipeline( - sequences=[ - [ - "Which is the best gaming laptop under 40k?", - "Which is the best gaming laptop under 40,000 rs?", - ] - ] -) - ->> TextClassificationOutput(labels=['duplicate'], scores=[0.9947025775909424]) -``` - -#### HTTP Server -Spinning up: -```bash -deepsparse.server \ - task text-classification \ - --model_path "zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/qqp/pruned80_quant-none-vnni" -``` - -Making a request: -```python -import requests - -url = "http://localhost:5543/predict" # Server's port default to 5543 - -obj = { - "sequences": [ - [ - "Which is the best gaming laptop under 40k?", - "Which is the best gaming laptop under 40,000 rs?", - ] - ] -} - -response = requests.post(url, json=obj) -response.text - ->> '{"labels": ["duplicate"], "scores": [0.9947025775909424]}' -``` - -#### Token Classification Pipeline -The token classification task takes in sequences as inputs and assigns a class to each token. -The following example uses a pruned and quantized token classification NER BERT model -trained on the `CoNLL` dataset downloaded from the SparseZoo. - -[List of available SparseZoo Token Classification Models]( -https://sparsezoo.neuralmagic.com/?page=1&domain=nlp&sub_domain=token_classification) - -#### Python Pipeline -```python -from deepsparse import Pipeline - -# default model is a pruned + quantized NER model trained on the CoNLL dataset -tc_pipeline = Pipeline.create(task="token-classification") -inference = tc_pipeline("Drive from California to Texas!") - ->> [{'entity': 'LABEL_0','word': 'drive', ...}, - {'entity': 'LABEL_0','word': 'from', ...}, - {'entity': 'LABEL_5','word': 'california', ...}, - {'entity': 'LABEL_0','word': 'to', ...}, - {'entity': 'LABEL_5','word': 'texas', ...}, - {'entity': 'LABEL_0','word': '!', ...}] -``` - -#### HTTP Server -Spinning up: -```bash -deepsparse.server \ - task token-classification \ - --model_path "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" -``` - -Making a request: -```python -import requests - -url = "http://localhost:5543/predict" # Server's port default to 5543 - -obj = {"inputs": "Drive from California to Texas!"} - - -response = requests.post(url, json=obj) -response.text - ->> '{"predictions":[[{"entity":"LABEL_0","score":0.9998655915260315,"index":1,"word":"drive","start":0,"end":5,"is_grouped":false},{"entity":"LABEL_0","score":0.9998604655265808,"index":2,"word":"from","start":6,"end":10,"is_grouped":false},{"entity":"LABEL_5","score":0.9994636178016663,"index":3,"word":"california","start":11,"end":21,"is_grouped":false},{"entity":"LABEL_0","score":0.999838650226593,"index":4,"word":"to","start":22,"end":24,"is_grouped":false},{"entity":"LABEL_5","score":0.9994573593139648,"index":5,"word":"texas","start":25,"end":30,"is_grouped":false},{"entity":"LABEL_0","score":0.9998716711997986,"index":6,"word":"!","start":30,"end":31,"is_grouped":false}]]}' -``` - -## Benchmarking -The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. Want to find out how fast our sparse Hugging Face ONNX models perform inference? -You can quickly do benchmarking tests on your own with a single CLI command! - -You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: - -```bash -deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni - ->> Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni ->> Batch Size: 1 ->> Scenario: multistream ->> Throughput (items/sec): 76.3484 ->> Latency Mean (ms/batch): 157.1049 ->> Latency Median (ms/batch): 157.0088 ->> Latency Std (ms/batch): 1.4860 ->> Iterations: 768 -``` - -To learn more about benchmarking, refer to the appropriate documentation. -Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! - -## Tutorials: -For a deeper dive into using transformers within the Neural Magic ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/): -- [Token Classification: Named Entity Recognition](https://neuralmagic.com/use-cases/sparse-named-entity-recognition/) -- [Text Classification: Multi-Class](https://neuralmagic.com/use-cases/sparse-multi-class-text-classification/) -- [Text Classification: Binary](https://neuralmagic.com/use-cases/sparse-binary-text-classification/) -- [Text Classification: Sentiment Analysis](https://neuralmagic.com/use-cases/sparse-sentiment-analysis/) -- [Question Answering](https://neuralmagic.com/use-cases/sparse-question-answering/) - -## Support -For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). +# NLP Use Cases + +Checkout DeepSparse Use Cases for usage details: +- [Sentiment Analysis](../../../docs/use-cases/nlp/sentiment-analysis.md) +- [Text Classification](../../../docs/use-cases/nlp/text-classification.md) +- [Token Classification](../../../docs/use-cases/nlp/token-classification.md) +- [Question Answering](../../../docs/use-cases/nlp/question-answering.md) +- [Zero Shot Text Classification](../../../docs/use-cases/nlp/zero-shot-text-classification.md) +- [Embedding Extraction](../../../docs/use-cases/nlp/transformers-embedding-extraction.md) From 2435705de4949b8cb518354c2bb0bc28ef4ae89d Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:48:18 -0400 Subject: [PATCH 131/149] Update README.md --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 08586df612..4e2b5c241b 100644 --- a/README.md +++ b/README.md @@ -52,11 +52,11 @@ limitations under the License. -A CPU runtime that takes advantage of sparsity within neural networks to reduce compute. Read [more about sparsification](https://docs.neuralmagic.com/user-guides/sparsification). -Neural Magic's DeepSparse is able to integrate into popular deep learning libraries (e.g., Hugging Face, Ultralytics) allowing you to leverage DeepSparse for loading and deploying sparse models with ONNX. -ONNX gives the flexibility to serve your model in a framework-agnostic environment. -Support includes [PyTorch,](https://pytorch.org/docs/stable/onnx.html) [TensorFlow,](https://github.com/onnx/tensorflow-onnx) [Keras,](https://github.com/onnx/keras-onnx) and [many other frameworks](https://github.com/onnx/onnxmltools). +DeepSparse is a CPU inference runtime that takes advantage of sparsity within neural networks to execute inferences quickly. Coupled with SparseML, Neural Magic's open-source optimization library, DeepSparse enables you to achieve GPU-class performance on commodity hardware. + + + ## Installation @@ -72,16 +72,18 @@ DeepSparse is available in two editions: ## 🧰 Hardware Support and System Requirements -To ensure that your CPU is compatible with DeepSparse, it is recommended to review the [Supported Hardware for DeepSparse](https://docs.neuralmagic.com/user-guides/deepsparse-engine/hardware-support) documentation. +DeepSparse is tested on Python versions 3.7-3.10, ONNX versions 1.5.0-1.12.0, ONNX opset version 11 or higher, and manylinux compliant systems. It is highly recommended to use a [virtual environment](https://docs.python.org/3/library/venv.html) when running DeepSparse. Please note that DeepSparse is only supported natively on Linux. For those using Mac or Windows, running Linux in a Docker or virtual machine is necessary to use DeepSparse. -To ensure that you get the best performance from DeepSparse, it has been thoroughly tested on Python versions 3.7-3.10, ONNX versions 1.5.0-1.12.0, ONNX opset version 11 or higher, and manylinux compliant systems. It is highly recommended to use a [virtual environment](https://docs.python.org/3/library/venv.html) when running DeepSparse. Please note that DeepSparse is only supported natively on Linux. For those using Mac or Windows, running Linux in a Docker or virtual machine is necessary to use DeepSparse. +[Supported Hardware for DeepSparse](docs/user-guide/hardware-support.md) ## Features -- 👩‍💻 Pipelines for [NLP](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/transformers), [CV Classification](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/image_classification), [CV Detection](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/yolo), [CV Segmentation](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/yolact) and more! -- 🔌 [DeepSparse Server](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/server) -- 📜 [DeepSparse Benchmark](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark) -- ☁️ [Cloud Deployments and Demos](https://github.com/neuralmagic/deepsparse/tree/main/examples) +- 📜 [DeepSparse Benchmark](docs/user-guide/deepsparse-benchmarking.md) +- 👩‍💻 [DeepSparse Pipelines](docs/user-guide/deepsparse-pipelines.md) +- 🔌 [DeepSparse Server](docs/user-guide/deepsparse-server.md) +- ☁️ [Cloud Deployments and Demos](examples/) + +### ### 👩‍💻 Pipelines From 867552364bb819444fb31338f241bd52c62fc023 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 11:49:12 -0400 Subject: [PATCH 132/149] Add files via upload --- docs/neural-magic-workflow.png | Bin 0 -> 149836 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/neural-magic-workflow.png diff --git a/docs/neural-magic-workflow.png b/docs/neural-magic-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..f870b4e97c32a0cf2ae9a568d99c933794d951f6 GIT binary patch literal 149836 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 C&KPX~ literal 0 HcmV?d00001 From eef8116876a133657ed51fb04ef1b805145c3178 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:04:50 -0400 Subject: [PATCH 133/149] Update README.md --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4e2b5c241b..8478195c81 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,34 @@ limitations under the License. DeepSparse is a CPU inference runtime that takes advantage of sparsity within neural networks to execute inferences quickly. Coupled with SparseML, Neural Magic's open-source optimization library, DeepSparse enables you to achieve GPU-class performance on commodity hardware. +

+ SparseML Flow +

+## Installation + +DeepSparse is available in two editions: +1. **DeepSparse Community** is free for evaluation, research, and non-production use with our [DeepSparse Community License](https://neuralmagic.com/legal/engine-license-agreement/). +2. **DeepSparse Enterprise** requires a [trial license](https://neuralmagic.com/deepsparse-free-trial/) or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. +### Install via Docker (Recommended) -## Installation +DeepSparse Community is available via [GitHub container registry](https://github.com/neuralmagic/deepsparse/pkgs/container/deepsparse). -Install DeepSparse Community as follows: +```bash +sudo docker pull ghcr.io/neuralmagic/deepsparse:1.4.2 +sudo docker tag ghcr.io/neuralmagic/deepsparse:1.4.2 deepsparse-docker +sudo docker run -it deepsparse-docker +``` + +### Install via PyPI +DeepSparse Community is also available via PyPI. We recommend using a virtual enviornment. ```bash pip install deepsparse ``` -DeepSparse is available in two editions: -1. [**DeepSparse Community**](#installation) is open-source and free for evaluation, research, and non-production use with our [DeepSparse Community License](https://neuralmagic.com/legal/engine-license-agreement/). -2. [**DeepSparse Enterprise**](https://docs.neuralmagic.com/products/deepsparse-ent) requires a Trial License or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. +Note that there are some optional dependencies. Checkout the [installation page](docs/user-guide/installation.md) for more details. ## 🧰 Hardware Support and System Requirements From 0bc84a88e4bbac1d556f76cbfbba847fe5f63286 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:45:07 -0400 Subject: [PATCH 134/149] Update README.md --- README.md | 242 +++++++++++++++++++++++++++++------------------------- 1 file changed, 129 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 8478195c81..5b0727a211 100644 --- a/README.md +++ b/README.md @@ -53,170 +53,187 @@ limitations under the License. -DeepSparse is a CPU inference runtime that takes advantage of sparsity within neural networks to execute inferences quickly. Coupled with SparseML, Neural Magic's open-source optimization library, DeepSparse enables you to achieve GPU-class performance on commodity hardware. +DeepSparse is a CPU inference runtime that takes advantage of sparsity within neural networks to execute inference quickly. Coupled with SparseML, an open-source optimization library, DeepSparse enables you to achieve GPU-class performance on commodity hardware.

- SparseML Flow + NM Flow

+For details of training an inference-optimized sparse model for deployment with DeepSparse, [checkout SparseML](https://github.com/neuralmagic/sparseml). + ## Installation DeepSparse is available in two editions: -1. **DeepSparse Community** is free for evaluation, research, and non-production use with our [DeepSparse Community License](https://neuralmagic.com/legal/engine-license-agreement/). -2. **DeepSparse Enterprise** requires a [trial license](https://neuralmagic.com/deepsparse-free-trial/) or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. +1. DeepSparse Community is free for evaluation, research, and non-production use with our [DeepSparse Community License](https://neuralmagic.com/legal/engine-license-agreement/). +2. DeepSparse Enterprise requires a [trial license](https://neuralmagic.com/deepsparse-free-trial/) or [can be fully licensed](https://neuralmagic.com/legal/master-software-license-and-service-agreement/) for production, commercial applications. -### Install via Docker (Recommended) +#### Install via Docker (Recommended) -DeepSparse Community is available via [GitHub container registry](https://github.com/neuralmagic/deepsparse/pkgs/container/deepsparse). +DeepSparse Community is available as a container image hosted on [GitHub container registry](https://github.com/neuralmagic/deepsparse/pkgs/container/deepsparse). ```bash -sudo docker pull ghcr.io/neuralmagic/deepsparse:1.4.2 -sudo docker tag ghcr.io/neuralmagic/deepsparse:1.4.2 deepsparse-docker -sudo docker run -it deepsparse-docker +docker pull ghcr.io/neuralmagic/deepsparse:1.4.2 +docker tag ghcr.io/neuralmagic/deepsparse:1.4.2 deepsparse-docker +docker run -it deepsparse-docker ``` -### Install via PyPI +- [Checkout the Docker page](docker/) for more details. + +#### Install via PyPI DeepSparse Community is also available via PyPI. We recommend using a virtual enviornment. ```bash pip install deepsparse ``` -Note that there are some optional dependencies. Checkout the [installation page](docs/user-guide/installation.md) for more details. - -## 🧰 Hardware Support and System Requirements +- [Checkout the Installation page](docs/user-guide/installation.md) for optional dependencies. -DeepSparse is tested on Python versions 3.7-3.10, ONNX versions 1.5.0-1.12.0, ONNX opset version 11 or higher, and manylinux compliant systems. It is highly recommended to use a [virtual environment](https://docs.python.org/3/library/venv.html) when running DeepSparse. Please note that DeepSparse is only supported natively on Linux. For those using Mac or Windows, running Linux in a Docker or virtual machine is necessary to use DeepSparse. +## Hardware Support and System Requirements [Supported Hardware for DeepSparse](docs/user-guide/hardware-support.md) -## Features +DeepSparse is tested on Python versions 3.7-3.10, ONNX versions 1.5.0-1.12.0, ONNX opset version 11 or higher, and manylinux compliant systems. Please note that DeepSparse is only supported natively on Linux. For those using Mac or Windows, running Linux in a Docker or virtual machine is necessary to use DeepSparse. -- 📜 [DeepSparse Benchmark](docs/user-guide/deepsparse-benchmarking.md) -- 👩‍💻 [DeepSparse Pipelines](docs/user-guide/deepsparse-pipelines.md) -- 🔌 [DeepSparse Server](docs/user-guide/deepsparse-server.md) -- ☁️ [Cloud Deployments and Demos](examples/) +## Benchmarking Performance -### +DeepSparse's key feature is its performance on commodity CPUs. -### 👩‍💻 Pipelines +For dense unoptimized models, DeepSparse is competitive with other CPU runtimes like ONNX Runtime. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve a significant speedup. -Pipelines are a high-level Python interface for running inference with DeepSparse across select tasks in NLP and CV: +As an example, let's compare DeepSparse's performance on ONNX Runtime on BERT using a 90% pruned and quantized version hosted in [SparseZoo](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none) on an AWS `c6i.2xlarge` instance (4 cores) using the [benchmarking script](docs/user-guide/deepsparse-benchmarking.md). -| NLP | CV | -|-----------------------|---------------------------| -| Text Classification `"text_classification"` | Image Classification `"image_classification"` | -| Token Classification `"token_classification"` | Object Detection `"yolo"` | -| Sentiment Analysis `"sentiment_analysis"` | Instance Segmentation `"yolact"` | -| Question Answering `"question_answering"` | Keypoint Detection `"open_pif_paf"` | -| MultiLabel Text Classification `"text_classification"` | | -| Document Classification `"text_classification"` | | -| Zero-Shot Text Classification `"zero_shot_text_classification"` | | +#### ORT Baseline +Make sure you have ONNX Runtime Installed (`pip install onnxruntime`). -**NLP Example** | Question Answering -```python -from deepsparse import Pipeline +We can see ORT achieves 4.6 items/sec on BERT: -qa_pipeline = Pipeline.create( - task="question-answering", - model_path="zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni", -) +```bash +deepsparse.benchmark \ + zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none \ + -b 64 -i [64,384] -e onnxruntime + +> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 4.6343 +``` + +#### DeepSparse Speedup + +DeepSparse achieves 27.5 items/sec running the pruned-quantized version of BERT. ***This is a 6x speedup***! -inference = qa_pipeline(question="What's my name?", context="My name is Snorlax") +```bash +deepsparse.benchmark \ + zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none \ + -b 64 -i [64,384] -e deepsparse + +> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 27.5325 ``` -**CV Example** | Image Classification + +Checkout the [Benchmarking User Guide](docs/user-guide/deepsparse-benchmarking.md) for more usage details. + +## Deployment APIs + +DeepSparse includes three deployment APIs: + +- **Engine** is the lowest-level API. With Engine, you pass tensors and receive the raw logits. +- **Pipeline** wraps the Engine with pre- and post-processing. With Pipeline, you pass raw data and receive the prediction. +- **Server** wraps Pipelines with a REST API using FastAPI. With Server, you send raw data over HTTP and receive the prediction. + +### Engine + +The example below downloads a 90% pruned-quantized BERT model for sentiment analysis in ONNX format from SparseZoo, compiles the model, and runs inference on randomly generated input. ```python -from deepsparse import Pipeline +from deepsparse import Engine +from deepsparse.utils import generate_random_inputs, model_to_path -cv_pipeline = Pipeline.create( - task='image_classification', - model_path='zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none', -) +# download onnx, compile +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +batch_size = 1 +compiled_model = Engine(model=zoo_stub, batch_size=batch_size) -input_image = "my_image.png" -inference = cv_pipeline(images=input_image) +# run inference (input is raw numpy tensors, output is raw scores) +inputs = generate_random_inputs(model_to_path(zoo_stub), batch_size) +output = compiled_model(inputs) +print(output) + +# > [array([[-0.3380675 , 0.09602544]], dtype=float32)] << raw scores ``` +### DeepSparse Pipelines -### 🔌 DeepSparse Server +Pipeline is the default API for interacting with DeepSparse. Similar to Hugging Face Pipelines, DeepSparse Pipelines wrap Engine with pre- and post-processing (as well as other utilities), enabling you to send raw data to DeepSparse and receive the post-processed prediction. -DeepSparse Server is a tool that enables you to serve your models and pipelines directly from your terminal. +The example below downloads a 90% pruned-quantized BERT model for sentiment analysis in ONNX format from SparseZoo, sets up a pipeline, and runs inference on sample data. -The server is built on top of two powerful libraries: the FastAPI web framework and the Uvicorn web server. This combination ensures that DeepSparse Server delivers excellent performance and reliability. Install with this command: +```python +from deepsparse import Pipeline -```bash -pip install deepsparse[server] +# download onnx, set up pipeline +zoo_stub = "zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none" +sentiment_analysis_pipeline = Pipeline.create( + task="sentiment-analysis", # name of the task + model_path=zoo_stub, # zoo stub or path to local onnx file +) + +# run inference (input is a sentence, output is the prediction) +prediction = sentiment_analysis_pipeline("I love using DeepSparse Pipelines") +print(prediction) +# > labels=['positive'] scores=[0.9954759478569031] ``` -#### Single Model +#### Additional Resources +- Checkout the [Use Cases Page](docs/use-cases) for more details on supported tasks. +- Checkout the [Pipelines User Guide](docs/user-guide/deepsparse-pipelines.md) for more usage details. -Once installed, the following example CLI command is available for running inference with a single BERT model: +### DeepSparse Server -```bash -deepsparse.server \ - task question_answering \ - --model_path "zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni" -``` +Server wraps Pipelines with REST APIs, enabling you to stand up model serving endpoint running DeepSparse. This enables you to send raw data to DeepSparse over HTTP and receive the post-processed predictions. -To look up arguments run: `deepsparse.server --help`. - -#### Multiple Models -To deploy multiple models in your setup, a `config.yaml` file should be created. In the example provided, two BERT models are configured for the question-answering task: - -```yaml -num_workers: 1 -endpoints: - - task: question_answering - route: /predict/question_answering/base - model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/base-none - batch_size: 1 - - task: question_answering - route: /predict/question_answering/pruned_quant - model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni - batch_size: 1 -``` +DeepSparse Server is launched from the command line, configured via arguments or a server configuration file. The following downloads a 90% pruned-quantized BERT model for sentiment analysis in ONNX format from SparseZoo and launches a sentiment analysis endpoint: -After the `config.yaml` file has been created, the server can be started by passing the file path as an argument: ```bash -deepsparse.server config config.yaml +deepsparse.server \ + --task sentiment-analysis \ + --model_path zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none ``` -Read the [DeepSparse Server](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/server) README for further details. - -### 📜 DeepSparse Benchmark +Sending a request: -DeepSparse Benchmark, a command-line (CLI) tool, is used to evaluate the DeepSparse Engine's performance with ONNX models. This tool processes arguments, downloads and compiles the network into the engine, creates input tensors, and runs the model based on the selected scenario. - -Run `deepsparse.benchmark -h` to look up arguments: +```python +import requests -```shell -deepsparse.benchmark [-h] [-b BATCH_SIZE] [-i INPUT_SHAPES] [-ncores NUM_CORES] [-s {async,sync,elastic}] [-t TIME] - [-w WARMUP_TIME] [-nstreams NUM_STREAMS] [-pin {none,core,numa}] [-e ENGINE] [-q] [-x EXPORT_PATH] - model_path +url = "http://localhost:5543/predict" # Server's port default to 5543 +obj = {"sequences": "Snorlax loves my Tesla!"} +response = requests.post(url, json=obj) +print(response.text) +# {"labels":["positive"],"scores":[0.9965094327926636]} ``` +#### Additional Resources +- Checkout the [Use Cases Page](docs/use-cases) for more details on supported tasks. +- Checkout the [Server User Guide](docs/user-guide/deepsparse-server.md) for more usage details. -Refer to the [Benchmark](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark) README for examples of specific inference scenarios. +## ONNX Model -### 🦉 Custom ONNX Model Support +DeepSparse accepts models in the ONNX format. ONNX models can be passed in one of two ways: -DeepSparse is capable of accepting ONNX models from two sources: +- **SparseZoo Stub**: [SparseZoo](https://sparsezoo.neuralmagic.com/) is an open-source repository of sparse models available for download. The examples on this page used this pathway. -**SparseZoo ONNX**: This is an open-source repository of sparse models available for download. [SparseZoo](https://github.com/neuralmagic/sparsezoo) offers inference-optimized models, which are trained using repeatable sparsification recipes and state-of-the-art techniques from [SparseML](https://github.com/neuralmagic/sparseml). - -**Custom ONNX**: Users can provide their own ONNX models, whether dense or sparse. By plugging in a custom model, users can compare its performance with other solutions. +- **Local ONNX File**: Users can provide their own ONNX models, whether dense or sparse. For example: ```bash -> wget https://github.com/onnx/models/raw/main/vision/classification/mobilenet/model/mobilenetv2-7.onnx -Saving to: ‘mobilenetv2-7.onnx’ +wget https://github.com/onnx/models/raw/main/vision/classification/mobilenet/model/mobilenetv2-7.onnx ``` -Custom ONNX Benchmark example: ```python -from deepsparse import compile_model +from deepsparse import Engine from deepsparse.utils import generate_random_inputs onnx_filepath = "mobilenetv2-7.onnx" batch_size = 16 @@ -225,34 +242,34 @@ batch_size = 16 inputs = generate_random_inputs(onnx_filepath, batch_size) # Compile and run -engine = compile_model(onnx_filepath, batch_size) -outputs = engine.run(inputs) +compiled_model = Engine(model=onnx_filepath, batch_size=batch_size) +outputs = compiled_model(inputs) +print(outputs[0].shape) +# (16, 1000) << batch, num_classes ``` -The [GitHub repository](https://github.com/neuralmagic/deepsparse) repository contains package APIs and examples that help users swiftly begin benchmarking and performing inference on sparse models. - -### Scheduling Single-Stream, Multi-Stream, and Elastic Inference +## Inference Models -DeepSparse offers different inference scenarios based on your use case. Read more details here: [Inference Types](https://github.com/neuralmagic/deepsparse/blob/main/docs/source/scheduler.md). +DeepSparse offers different inference scenarios based on your use case. -⚡ **Single-stream** scheduling: the latency/synchronous scenario, requests execute serially. [`default`] +**Single-stream** scheduling: the latency/synchronous scenario, requests execute serially. [`default`] single stream diagram It's highly optimized for minimum per-request latency, using all of the system's resources provided to it on every request it gets. -⚡ **Multi-stream** scheduling: the throughput/asynchronous scenario, requests execute in parallel. +**Multi-stream** scheduling: the throughput/asynchronous scenario, requests execute in parallel. multi stream diagram The most common use cases for the multi-stream scheduler are where parallelism is low with respect to core count, and where requests need to be made asynchronously without time to batch them. -## Resources -#### Libraries -- [DeepSparse](https://docs.neuralmagic.com/deepsparse/) -- [SparseML](https://docs.neuralmagic.com/sparseml/) -- [SparseZoo](https://docs.neuralmagic.com/sparsezoo/) -- [Sparsify](https://docs.neuralmagic.com/sparsify/) +[Check out the Scheduler User Guide](docs/user-guide/scheduler.md) for more details. + +## Additional Resources +- [User Guide](docs/user-guide) +- [Use Cases](docs/use-cases) +- [Cloud Deployments and Demos](examples/) #### Versions - [DeepSparse](https://pypi.org/project/deepsparse) | stable @@ -267,7 +284,6 @@ The most common use cases for the multi-stream scheduler are where parallelism i ### Be Part of the Future... And the Future is Sparse! - Contribute with code, examples, integrations, and documentation as well as bug reports and feature requests! [Learn how here.](https://github.com/neuralmagic/deepsparse/blob/main/CONTRIBUTING.md) For user help or questions about DeepSparse, sign up or log in to our **[Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ)**. We are growing the community member by member and happy to see you there. Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue.](https://github.com/neuralmagic/deepsparse/issues) You can get the latest news, webinar and event invites, research papers, and other ML Performance tidbits by [subscribing](https://neuralmagic.com/subscribe/) to the Neural Magic community. From a31969a511d961b4bc9bc7d84a4d41cd4eb5b5a7 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:46:14 -0400 Subject: [PATCH 135/149] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b0727a211..520b98732c 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ print(response.text) - Checkout the [Use Cases Page](docs/use-cases) for more details on supported tasks. - Checkout the [Server User Guide](docs/user-guide/deepsparse-server.md) for more usage details. -## ONNX Model +## ONNX DeepSparse accepts models in the ONNX format. ONNX models can be passed in one of two ways: From d1fa16970ea74d8895515d67d0e6fb9b28333748 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Wed, 19 Apr 2023 12:52:40 -0400 Subject: [PATCH 136/149] added copyrights --- docs/use-cases/README.md | 16 ++++++++++++++++ docs/use-cases/cv/embedding-extraction.md | 16 ++++++++++++++++ docs/use-cases/cv/image-classification.md | 16 ++++++++++++++++ docs/use-cases/cv/image-segmentation-yolact.md | 16 ++++++++++++++++ docs/use-cases/cv/object-detection-yolov5.md | 16 ++++++++++++++++ docs/use-cases/nlp/question-answering.md | 16 ++++++++++++++++ docs/use-cases/nlp/sentiment-analysis.md | 16 ++++++++++++++++ docs/use-cases/nlp/text-classification.md | 16 ++++++++++++++++ docs/use-cases/nlp/token-classification.md | 16 ++++++++++++++++ .../nlp/transformers-embedding-extraction.md | 16 ++++++++++++++++ .../nlp/zero-shot-text-classification.md | 16 ++++++++++++++++ docs/user-guide/README.md | 16 ++++++++++++++++ docs/user-guide/deepsparse-benchmarking.md | 16 ++++++++++++++++ docs/user-guide/deepsparse-pipelines.md | 16 ++++++++++++++++ docs/user-guide/deepsparse-server.md | 16 ++++++++++++++++ docs/user-guide/hardware-support.md | 16 ++++++++++++++++ docs/user-guide/installation.md | 16 ++++++++++++++++ docs/user-guide/scheduler.md | 16 ++++++++++++++++ 18 files changed, 288 insertions(+) diff --git a/docs/use-cases/README.md b/docs/use-cases/README.md index 38f7939e1b..8d7532d398 100644 --- a/docs/use-cases/README.md +++ b/docs/use-cases/README.md @@ -1,3 +1,19 @@ + + # Use Cases There are three interfaces for interacting with DeepSparse: diff --git a/docs/use-cases/cv/embedding-extraction.md b/docs/use-cases/cv/embedding-extraction.md index 2ee9c81dab..1cb8177fb7 100644 --- a/docs/use-cases/cv/embedding-extraction.md +++ b/docs/use-cases/cv/embedding-extraction.md @@ -1,3 +1,19 @@ + + # Deploying Embedding Extraction Models With DeepSparse This page explains how to deploy an Embedding Extraction Pipeline with DeepSparse. diff --git a/docs/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md index 6e4830d34e..d66d0792d5 100644 --- a/docs/use-cases/cv/image-classification.md +++ b/docs/use-cases/cv/image-classification.md @@ -1,3 +1,19 @@ + + # Deploying Image Classification Models with DeepSparse This page explains how to benchmark and deploy an image classification model with DeepSparse. diff --git a/docs/use-cases/cv/image-segmentation-yolact.md b/docs/use-cases/cv/image-segmentation-yolact.md index bb31cc4f20..81b0f0f911 100644 --- a/docs/use-cases/cv/image-segmentation-yolact.md +++ b/docs/use-cases/cv/image-segmentation-yolact.md @@ -1,3 +1,19 @@ + + # Deploying Image Segmentation Models with DeepSparse This page explains how to benchmark and deploy an image segmentation with DeepSparse. diff --git a/docs/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md index 06a7987f51..95af3ec6ca 100644 --- a/docs/use-cases/cv/object-detection-yolov5.md +++ b/docs/use-cases/cv/object-detection-yolov5.md @@ -1,3 +1,19 @@ + + # Deploying YOLOv5 Object Detection Models with DeepSparse This page explains how to benchmark and deploy a YOLOv5 object detection model with DeepSparse. diff --git a/docs/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md index 98d7ce4ce0..527c99e08f 100644 --- a/docs/use-cases/nlp/question-answering.md +++ b/docs/use-cases/nlp/question-answering.md @@ -1,3 +1,19 @@ + + # Deploying Question Answering Models with DeepSparse This page explains how to benchmark and deploy a question answering model with DeepSparse. diff --git a/docs/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md index 423a9944d1..9633fd1c6b 100644 --- a/docs/use-cases/nlp/sentiment-analysis.md +++ b/docs/use-cases/nlp/sentiment-analysis.md @@ -1,3 +1,19 @@ + + # Deploying Sentiment Analysis Models with DeepSparse This page explains how to benchmark and deploy a sentiment analysis model with DeepSparse. diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index 84c704b53b..f6a1845aaa 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -1,3 +1,19 @@ + + # Deploying Text Classification Models with DeepSparse This page explains how to benchmark and deploy a text classification model with DeepSparse. diff --git a/docs/use-cases/nlp/token-classification.md b/docs/use-cases/nlp/token-classification.md index 98cf3d7adf..ac9351790c 100644 --- a/docs/use-cases/nlp/token-classification.md +++ b/docs/use-cases/nlp/token-classification.md @@ -1,3 +1,19 @@ + + # Deploying Token Classification Models with DeepSparse This page explains how to benchmark and deploy a token classification model with DeepSparse. diff --git a/docs/use-cases/nlp/transformers-embedding-extraction.md b/docs/use-cases/nlp/transformers-embedding-extraction.md index 6753c360f1..db62e13d2b 100644 --- a/docs/use-cases/nlp/transformers-embedding-extraction.md +++ b/docs/use-cases/nlp/transformers-embedding-extraction.md @@ -1,3 +1,19 @@ + + # Deploying Transformers Embedding Extraction Models with DeepSparse This page explains how to deploy a transformers embedding extraction Pipeline with DeepSparse. diff --git a/docs/use-cases/nlp/zero-shot-text-classification.md b/docs/use-cases/nlp/zero-shot-text-classification.md index 14cdebd3bd..4fe2e7c08b 100644 --- a/docs/use-cases/nlp/zero-shot-text-classification.md +++ b/docs/use-cases/nlp/zero-shot-text-classification.md @@ -1,3 +1,19 @@ + + # Deploying Zero Shot Text Classification Models This page explains how to benchmark and deploy a zero-shot text classification model with DeepSparse. diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index 779faa8dcf..4ea16d42b1 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -1,3 +1,19 @@ + + # User Guide This directory demonstrates usage of DeepSparse's key API, including: diff --git a/docs/user-guide/deepsparse-benchmarking.md b/docs/user-guide/deepsparse-benchmarking.md index 0578a92a4d..c4ab15b57b 100644 --- a/docs/user-guide/deepsparse-benchmarking.md +++ b/docs/user-guide/deepsparse-benchmarking.md @@ -1,3 +1,19 @@ + + # DeepSparse Benchmarking This page explains how to use DeepSparse's CLI utilties for benchmarking performance in a variety of scenarios. diff --git a/docs/user-guide/deepsparse-pipelines.md b/docs/user-guide/deepsparse-pipelines.md index ae4a707600..5d1e7fa92b 100644 --- a/docs/user-guide/deepsparse-pipelines.md +++ b/docs/user-guide/deepsparse-pipelines.md @@ -1,3 +1,19 @@ + + # DeepSparse Pipelines Pipelines are the default API for deploying a model with DeepSparse. diff --git a/docs/user-guide/deepsparse-server.md b/docs/user-guide/deepsparse-server.md index 37c28aa3f8..63147a442e 100644 --- a/docs/user-guide/deepsparse-server.md +++ b/docs/user-guide/deepsparse-server.md @@ -1,3 +1,19 @@ + + # DeepSparse Server DeepSparse Server wraps [Pipelines](deepsparse-pipelines.md) with a REST API, making it easy to stand up a inference diff --git a/docs/user-guide/hardware-support.md b/docs/user-guide/hardware-support.md index 398c17971a..6602699e00 100644 --- a/docs/user-guide/hardware-support.md +++ b/docs/user-guide/hardware-support.md @@ -1,3 +1,19 @@ + + # Supported Hardware for DeepSparse With support for AVX2, AVX-512, and VNNI instruction sets, DeepSparse is validated to work on x86 Intel (Haswell generation and later) and AMD (Zen 2 and later) CPUs running Linux. diff --git a/docs/user-guide/installation.md b/docs/user-guide/installation.md index 48903c7271..75b7346bb6 100644 --- a/docs/user-guide/installation.md +++ b/docs/user-guide/installation.md @@ -1,3 +1,19 @@ + + # DeepSparse Installation DeepSparse is tested on Python 3.7-3.10, ONNX 1.5.0-1.10.1, ONNX opset version 11+ and is [manylinux compliant](https://peps.python.org/pep-0513/). diff --git a/docs/user-guide/scheduler.md b/docs/user-guide/scheduler.md index d3631cdd69..a26c8aa282 100644 --- a/docs/user-guide/scheduler.md +++ b/docs/user-guide/scheduler.md @@ -1,3 +1,19 @@ + + # Inference Types With DeepSparse Scheduler This page explains the various settings for DeepSparse, which enable you to tune the performance to your workload. From 593e130cda3237a3acac9987521f9d3cc38a83b8 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:19:52 -0400 Subject: [PATCH 137/149] Update README.md Co-authored-by: Michael Goin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 520b98732c..c35868c99a 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ DeepSparse is a CPU inference runtime that takes advantage of sparsity within ne NM Flow

-For details of training an inference-optimized sparse model for deployment with DeepSparse, [checkout SparseML](https://github.com/neuralmagic/sparseml). +For details of training an inference-optimized sparse model for deployment with DeepSparse, [check out SparseML](https://github.com/neuralmagic/sparseml). ## Installation From 60b8dbddb0974fd0ccfbc023a274f3f3b04458b1 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:19:57 -0400 Subject: [PATCH 138/149] Update README.md Co-authored-by: Michael Goin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c35868c99a..aff3ff6a3a 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ DeepSparse Community is also available via PyPI. We recommend using a virtual en pip install deepsparse ``` -- [Checkout the Installation page](docs/user-guide/installation.md) for optional dependencies. +[Check out the Installation page](docs/user-guide/installation.md) for optional dependencies. ## Hardware Support and System Requirements From 9be3f5039a735d5ca81778d9770a00f6ed6ba4dc Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:20:07 -0400 Subject: [PATCH 139/149] Update README.md Co-authored-by: Michael Goin --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aff3ff6a3a..d947909ce9 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,8 @@ print(prediction) ``` #### Additional Resources -- Checkout the [Use Cases Page](docs/use-cases) for more details on supported tasks. -- Checkout the [Pipelines User Guide](docs/user-guide/deepsparse-pipelines.md) for more usage details. +- Check out the [Use Cases Page](docs/use-cases) for more details on supported tasks. +- Check out the [Pipelines User Guide](docs/user-guide/deepsparse-pipelines.md) for more usage details. ### DeepSparse Server From 7ec9e9e69664ed0f1654790d1ad7331322d7b369 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:20:24 -0400 Subject: [PATCH 140/149] Update README.md Co-authored-by: Michael Goin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d947909ce9..d533ad6800 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ limitations under the License. -DeepSparse is a CPU inference runtime that takes advantage of sparsity within neural networks to execute inference quickly. Coupled with SparseML, an open-source optimization library, DeepSparse enables you to achieve GPU-class performance on commodity hardware. +[DeepSparse](https://github.com/neuralmagic/deepsparse) is a CPU inference runtime that takes advantage of sparsity within neural networks to execute inference quickly. Coupled with [SparseML](https://github.com/neuralmagic/sparseml), an open-source optimization library, DeepSparse enables you to achieve GPU-class performance on commodity hardware.

NM Flow From 6be665e87e6c8a8ced1bf039f309310cc78fe2de Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:21:13 -0400 Subject: [PATCH 141/149] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d533ad6800..58247d6741 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ limitations under the License. NM Flow

-For details of training an inference-optimized sparse model for deployment with DeepSparse, [check out SparseML](https://github.com/neuralmagic/sparseml). +For details of training an optimized sparse model for deployment with DeepSparse, [check out SparseML](https://github.com/neuralmagic/sparseml). ## Installation From 1e85839089364d81027d21019ad7f23dfb1856e0 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:22:31 -0400 Subject: [PATCH 142/149] Update README.md --- README.md | 85 +++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 58247d6741..4f6ddf810f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ DeepSparse Community is also available via PyPI. We recommend using a virtual en pip install deepsparse ``` -[Check out the Installation page](docs/user-guide/installation.md) for optional dependencies. +- [Check out the Installation page](docs/user-guide/installation.md) for optional dependencies. ## Hardware Support and System Requirements @@ -94,48 +94,6 @@ pip install deepsparse DeepSparse is tested on Python versions 3.7-3.10, ONNX versions 1.5.0-1.12.0, ONNX opset version 11 or higher, and manylinux compliant systems. Please note that DeepSparse is only supported natively on Linux. For those using Mac or Windows, running Linux in a Docker or virtual machine is necessary to use DeepSparse. -## Benchmarking Performance - -DeepSparse's key feature is its performance on commodity CPUs. - -For dense unoptimized models, DeepSparse is competitive with other CPU runtimes like ONNX Runtime. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve a significant speedup. - -As an example, let's compare DeepSparse's performance on ONNX Runtime on BERT using a 90% pruned and quantized version hosted in [SparseZoo](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none) on an AWS `c6i.2xlarge` instance (4 cores) using the [benchmarking script](docs/user-guide/deepsparse-benchmarking.md). - -#### ORT Baseline - -Make sure you have ONNX Runtime Installed (`pip install onnxruntime`). - -We can see ORT achieves 4.6 items/sec on BERT: - -```bash -deepsparse.benchmark \ - zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none \ - -b 64 -i [64,384] -e onnxruntime - -> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none -> Batch Size: 64 -> Scenario: sync -> Throughput (items/sec): 4.6343 -``` - -#### DeepSparse Speedup - -DeepSparse achieves 27.5 items/sec running the pruned-quantized version of BERT. ***This is a 6x speedup***! - -```bash -deepsparse.benchmark \ - zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none \ - -b 64 -i [64,384] -e deepsparse - -> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -> Batch Size: 64 -> Scenario: sync -> Throughput (items/sec): 27.5325 -``` - -Checkout the [Benchmarking User Guide](docs/user-guide/deepsparse-benchmarking.md) for more usage details. - ## Deployment APIs DeepSparse includes three deployment APIs: @@ -266,6 +224,47 @@ The most common use cases for the multi-stream scheduler are where parallelism i [Check out the Scheduler User Guide](docs/user-guide/scheduler.md) for more details. +## Benchmarking Performance + +DeepSparse's key feature is its performance on commodity CPUs. + +For dense unoptimized models, DeepSparse is competitive with other CPU runtimes like ONNX Runtime. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve a significant speedup. + +As an example, let's compare DeepSparse's performance on ONNX Runtime on BERT using a 90% pruned and quantized version hosted in [SparseZoo](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none) on an AWS `c6i.2xlarge` instance (4 cores) using the [benchmarking script](docs/user-guide/deepsparse-benchmarking.md). + +#### ORT Baseline + +Make sure you have ONNX Runtime Installed (`pip install onnxruntime`). + +We can see ORT achieves 4.6 items/sec on BERT: + +```bash +deepsparse.benchmark \ + zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none \ + -b 64 -i [64,384] -e onnxruntime + +> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 4.6343 +``` + +#### DeepSparse Speedup + +DeepSparse achieves 27.5 items/sec running the pruned-quantized version of BERT. ***This is a 6x speedup***! + +```bash +deepsparse.benchmark \ + zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none \ + -b 64 -i [64,384] -e deepsparse + +> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none +> Batch Size: 64 +> Scenario: sync +> Throughput (items/sec): 27.5325 +``` + + ## Additional Resources - [User Guide](docs/user-guide) - [Use Cases](docs/use-cases) From 517d5d8995b7299de96ce98ee849e39fb47e9fef Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:25:25 -0400 Subject: [PATCH 143/149] Update README.md --- README.md | 50 +++++--------------------------------------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 4f6ddf810f..9b67082711 100644 --- a/README.md +++ b/README.md @@ -175,8 +175,8 @@ print(response.text) ``` #### Additional Resources -- Checkout the [Use Cases Page](docs/use-cases) for more details on supported tasks. -- Checkout the [Server User Guide](docs/user-guide/deepsparse-server.md) for more usage details. +- Check out the [Use Cases Page](docs/use-cases) for more details on supported tasks. +- Check out the [Server User Guide](docs/user-guide/deepsparse-server.md) for more usage details. ## ONNX @@ -206,7 +206,7 @@ print(outputs[0].shape) # (16, 1000) << batch, num_classes ``` -## Inference Models +## Inference Mode DeepSparse offers different inference scenarios based on your use case. @@ -222,50 +222,10 @@ It's highly optimized for minimum per-request latency, using all of the system's The most common use cases for the multi-stream scheduler are where parallelism is low with respect to core count, and where requests need to be made asynchronously without time to batch them. -[Check out the Scheduler User Guide](docs/user-guide/scheduler.md) for more details. - -## Benchmarking Performance - -DeepSparse's key feature is its performance on commodity CPUs. - -For dense unoptimized models, DeepSparse is competitive with other CPU runtimes like ONNX Runtime. However, when optimization techniques like pruning and quantization are applied to a model, DeepSparse can achieve a significant speedup. - -As an example, let's compare DeepSparse's performance on ONNX Runtime on BERT using a 90% pruned and quantized version hosted in [SparseZoo](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none) on an AWS `c6i.2xlarge` instance (4 cores) using the [benchmarking script](docs/user-guide/deepsparse-benchmarking.md). - -#### ORT Baseline - -Make sure you have ONNX Runtime Installed (`pip install onnxruntime`). - -We can see ORT achieves 4.6 items/sec on BERT: - -```bash -deepsparse.benchmark \ - zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none \ - -b 64 -i [64,384] -e onnxruntime - -> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/base-none -> Batch Size: 64 -> Scenario: sync -> Throughput (items/sec): 4.6343 -``` - -#### DeepSparse Speedup - -DeepSparse achieves 27.5 items/sec running the pruned-quantized version of BERT. ***This is a 6x speedup***! - -```bash -deepsparse.benchmark \ - zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none \ - -b 64 -i [64,384] -e deepsparse - -> Original Model Path: zoo:nlp/sentiment_analysis/obert-base/pytorch/huggingface/sst2/pruned90_quant-none -> Batch Size: 64 -> Scenario: sync -> Throughput (items/sec): 27.5325 -``` - +- [Check out the Scheduler User Guide](docs/user-guide/scheduler.md) for more details. ## Additional Resources +- [Benchmarking Performance](docs/user-guide/deepsparse-benchmarking.md) - [User Guide](docs/user-guide) - [Use Cases](docs/use-cases) - [Cloud Deployments and Demos](examples/) From a84edb0333a2a54e957d4caff83cf4524315964b Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:27:03 -0400 Subject: [PATCH 144/149] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b67082711..62d3710c5c 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ print(response.text) DeepSparse accepts models in the ONNX format. ONNX models can be passed in one of two ways: -- **SparseZoo Stub**: [SparseZoo](https://sparsezoo.neuralmagic.com/) is an open-source repository of sparse models available for download. The examples on this page used this pathway. +- **SparseZoo Stub**: [SparseZoo](https://sparsezoo.neuralmagic.com/) is an open-source repository of sparse models. The examples on this page use SparseZoo stubs to identify models and download them for deployment in DeepSparse. - **Local ONNX File**: Users can provide their own ONNX models, whether dense or sparse. For example: @@ -206,7 +206,7 @@ print(outputs[0].shape) # (16, 1000) << batch, num_classes ``` -## Inference Mode +## Inference Modes DeepSparse offers different inference scenarios based on your use case. From 89714eb149c64e5ac8c44f871bc00b8ea51e4f7f Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:29:31 -0400 Subject: [PATCH 145/149] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62d3710c5c..2658fbed9a 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ limitations under the License. NM Flow

-For details of training an optimized sparse model for deployment with DeepSparse, [check out SparseML](https://github.com/neuralmagic/sparseml). +For details of training a sparse model for deployment with DeepSparse, [check out SparseML](https://github.com/neuralmagic/sparseml). ## Installation From bc4590b343b4b818fdec02efa5422eea2c3184e7 Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:30:42 -0400 Subject: [PATCH 146/149] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2658fbed9a..f8f3f3d565 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ docker tag ghcr.io/neuralmagic/deepsparse:1.4.2 deepsparse-docker docker run -it deepsparse-docker ``` -- [Checkout the Docker page](docker/) for more details. +- [Check out the Docker page](docker/) for more details. #### Install via PyPI DeepSparse Community is also available via PyPI. We recommend using a virtual enviornment. From f8893ed69fec22b7553701a5064d2713583e44cf Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+rsnm2@users.noreply.github.com> Date: Wed, 19 Apr 2023 13:34:11 -0400 Subject: [PATCH 147/149] Update README.md --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index f8f3f3d565..4b49c3cfbe 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ limitations under the License. [DeepSparse](https://github.com/neuralmagic/deepsparse) is a CPU inference runtime that takes advantage of sparsity within neural networks to execute inference quickly. Coupled with [SparseML](https://github.com/neuralmagic/sparseml), an open-source optimization library, DeepSparse enables you to achieve GPU-class performance on commodity hardware.

- NM Flow + NM Flow

For details of training a sparse model for deployment with DeepSparse, [check out SparseML](https://github.com/neuralmagic/sparseml). @@ -77,7 +77,7 @@ docker tag ghcr.io/neuralmagic/deepsparse:1.4.2 deepsparse-docker docker run -it deepsparse-docker ``` -- [Check out the Docker page](docker/) for more details. +- [Check out the Docker page](https://github.com/neuralmagic/deepsparse/tree/main/docker/) for more details. #### Install via PyPI DeepSparse Community is also available via PyPI. We recommend using a virtual enviornment. @@ -86,11 +86,11 @@ DeepSparse Community is also available via PyPI. We recommend using a virtual en pip install deepsparse ``` -- [Check out the Installation page](docs/user-guide/installation.md) for optional dependencies. +- [Check out the Installation page](https://github.com/neuralmagic/deepsparse/tree/main/docs/user-guide/installation.md) for optional dependencies. ## Hardware Support and System Requirements -[Supported Hardware for DeepSparse](docs/user-guide/hardware-support.md) +[Supported Hardware for DeepSparse](https://github.com/neuralmagic/deepsparse/tree/main/docs/user-guide/hardware-support.md) DeepSparse is tested on Python versions 3.7-3.10, ONNX versions 1.5.0-1.12.0, ONNX opset version 11 or higher, and manylinux compliant systems. Please note that DeepSparse is only supported natively on Linux. For those using Mac or Windows, running Linux in a Docker or virtual machine is necessary to use DeepSparse. @@ -146,8 +146,8 @@ print(prediction) ``` #### Additional Resources -- Check out the [Use Cases Page](docs/use-cases) for more details on supported tasks. -- Check out the [Pipelines User Guide](docs/user-guide/deepsparse-pipelines.md) for more usage details. +- Check out the [Use Cases Page](https://github.com/neuralmagic/deepsparse/tree/main/docs/use-cases) for more details on supported tasks. +- Check out the [Pipelines User Guide](https://github.com/neuralmagic/deepsparse/tree/main/docs/user-guide/deepsparse-pipelines.md) for more usage details. ### DeepSparse Server @@ -175,8 +175,8 @@ print(response.text) ``` #### Additional Resources -- Check out the [Use Cases Page](docs/use-cases) for more details on supported tasks. -- Check out the [Server User Guide](docs/user-guide/deepsparse-server.md) for more usage details. +- Check out the [Use Cases Page](https://github.com/neuralmagic/deepsparse/tree/main/docs/use-cases) for more details on supported tasks. +- Check out the [Server User Guide](https://github.com/neuralmagic/deepsparse/tree/main/docs/user-guide/deepsparse-server.md) for more usage details. ## ONNX @@ -222,13 +222,13 @@ It's highly optimized for minimum per-request latency, using all of the system's The most common use cases for the multi-stream scheduler are where parallelism is low with respect to core count, and where requests need to be made asynchronously without time to batch them. -- [Check out the Scheduler User Guide](docs/user-guide/scheduler.md) for more details. +- [Check out the Scheduler User Guide](https://github.com/neuralmagic/deepsparse/tree/main/docs/user-guide/scheduler.md) for more details. ## Additional Resources -- [Benchmarking Performance](docs/user-guide/deepsparse-benchmarking.md) -- [User Guide](docs/user-guide) -- [Use Cases](docs/use-cases) -- [Cloud Deployments and Demos](examples/) +- [Benchmarking Performance](https://github.com/neuralmagic/deepsparse/tree/main/docs/user-guide/deepsparse-benchmarking.md) +- [User Guide](https://github.com/neuralmagic/deepsparse/tree/main/docs/user-guide) +- [Use Cases](https://github.com/neuralmagic/deepsparse/tree/main/docs/use-cases) +- [Cloud Deployments and Demos](https://github.com/neuralmagic/deepsparse/tree/main/examples/) #### Versions - [DeepSparse](https://pypi.org/project/deepsparse) | stable From 0d799b7c4e9a750e6a2c40d6f58212b5c11ef80e Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 24 Apr 2023 16:27:43 -0400 Subject: [PATCH 148/149] reset to qaed state --- docs/use-cases/cv/embedding-extraction.md | 22 ++++++++++++ docs/use-cases/cv/image-classification.md | 21 +++++++++++ .../use-cases/cv/image-segmentation-yolact.md | 22 ++++++++++++ docs/use-cases/cv/object-detection-yolov5.md | 36 +++++++++++++++++++ docs/use-cases/nlp/question-answering.md | 31 ++++++++++++++++ docs/use-cases/nlp/sentiment-analysis.md | 29 +++++++++++++++ docs/use-cases/nlp/text-classification.md | 31 ++++++++++++++++ docs/use-cases/nlp/token-classification.md | 22 ++++++++++++ .../nlp/transformers-embedding-extraction.md | 27 ++++++++++++++ .../nlp/zero-shot-text-classification.md | 29 +++++++++++++++ 10 files changed, 270 insertions(+) diff --git a/docs/use-cases/cv/embedding-extraction.md b/docs/use-cases/cv/embedding-extraction.md index 1cb8177fb7..ff7e9f7ad1 100644 --- a/docs/use-cases/cv/embedding-extraction.md +++ b/docs/use-cases/cv/embedding-extraction.md @@ -103,6 +103,28 @@ print(len(result["embeddings"][0][0])) # 2048 << size of final layer>> ``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to define custom ONNX files for embedding extraction. +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [ResNet-50 - ImageNet page](https://sparsezoo.neuralmagic.com/models/cv%2Fclassification%2Fresnet_v1-50%2Fpytorch%2Fsparseml%2Fimagenet%2Fpruned95_uniform_quant-none) to download a ONNX ResNet model for demonstration. + +Extract the downloaded file and use the ResNet-50 ONNX model for embedding extraction: +```python +from deepsparse import Pipeline + +# this step removes the projection head before compiling the model +rn50_embedding_pipeline = Pipeline.create( + task="embedding-extraction", + base_task="image-classification", # tells the pipeline to expect images and normalize input with ImageNet means/stds + model_path="resnet.onnx", + emb_extraction_layer=-3, # extracts last layer before projection head and softmax +) + +# this step runs pre-processing, inference and returns an embedding +embedding = rn50_embedding_pipeline(images="lion.jpeg") +print(len(embedding.embeddings[0][0])) +# 2048 +``` ### Cross Use Case Functionality Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. diff --git a/docs/use-cases/cv/image-classification.md b/docs/use-cases/cv/image-classification.md index d66d0792d5..6d99374dd2 100644 --- a/docs/use-cases/cv/image-classification.md +++ b/docs/use-cases/cv/image-classification.md @@ -257,6 +257,27 @@ resp = requests.post(url=url, files=files) print(resp.text) # {"labels":[291,260,244],"scores":[24.185693740844727,18.982254028320312,16.390701293945312]} ``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to define custom ONNX files when deploying a model. + +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [ResNet-50 - ImageNet page](https://sparsezoo.neuralmagic.com/models/cv%2Fclassification%2Fresnet_v1-50%2Fpytorch%2Fsparseml%2Fimagenet%2Fpruned95_uniform_quant-none) to download a ONNX ResNet model for demonstration. + +Extract the downloaded file and use the ResNet-50 ONNX model for inference: +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +pipeline = Pipeline.create( + task="image_classification", + model_path="resnet.onnx", # sparsezoo stub or path to local ONNX +) + +# run inference on image file +prediction = pipeline(images=["lion.jpeg"]) +print(prediction.labels) +# [291] +``` ### Cross Use Case Functionality Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. diff --git a/docs/use-cases/cv/image-segmentation-yolact.md b/docs/use-cases/cv/image-segmentation-yolact.md index 81b0f0f911..cc7be1e044 100644 --- a/docs/use-cases/cv/image-segmentation-yolact.md +++ b/docs/use-cases/cv/image-segmentation-yolact.md @@ -224,6 +224,28 @@ resp = requests.post(url=url, files=files) annotations = json.loads(resp.text) # dictionary of annotation results boxes, classes, masks, scores = annotations["boxes"], annotations["classes"], annotations["masks"], annotations["scores"] ``` + +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to define custom ONNX files when deploying a model. + +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [YOLCAT page](https://sparsezoo.neuralmagic.com/models/cv%2Fsegmentation%2Fyolact-darknet53%2Fpytorch%2Fdbolya%2Fcoco%2Fpruned82_quant-none) to download a ONNX YOLACT model for demonstration. + +Extract the downloaded file and use the YOLACT ONNX model for inference: +```python +from deepsparse.pipeline import Pipeline + +yolact_pipeline = Pipeline.create( + task="yolact", + model_path="yolact.onnx", +) + +images = ["thailand.jpeg"] +predictions = yolact_pipeline(images=images) +# predictions has attributes `boxes`, `classes`, `masks` and `scores` +predictions.classes[0] +# [20,20, .......0, 0,24] +``` ### Cross Use Case Functionality Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. diff --git a/docs/use-cases/cv/object-detection-yolov5.md b/docs/use-cases/cv/object-detection-yolov5.md index 95af3ec6ca..1843a4d6ee 100644 --- a/docs/use-cases/cv/object-detection-yolov5.md +++ b/docs/use-cases/cv/object-detection-yolov5.md @@ -281,6 +281,42 @@ labels = annotations["labels"] print(labels) # [['person', 'person', 'car', 'person', 'motorcycle', 'person', 'person', 'person', 'motorcycle', 'person']] ``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to define custom ONNX files when deploying a model. + +The first step is to obtain the YOLOv5 ONNX model. This could be a YOLOv5 model you have trained and converted to ONNX. +In this case, let's demonstrate by converting a YOLOv5 model to ONNX using the `ultralytics` package: +```python +from ultralytics import YOLO + +# Load a model +model = YOLO("yolov5nu.pt") # load a pretrained model +success = model.export(format="onnx") # export the model to ONNX format +``` +Download a sample image for detection: +```bash +wget -O basilica.jpg https://raw.githubusercontent.com/neuralmagic/deepsparse/main/src/deepsparse/yolo/sample_images/basilica.jpg + +``` +Next, run the DeepSparse object detection pipeline with the custom ONNX file: + +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +yolo_pipeline = Pipeline.create( + task="yolo", + model_path="yolov5nu.onnx", # sparsezoo stub or path to local ONNX +) +images = ["basilica.jpg"] + +# run inference on image file +pipeline_outputs = yolo_pipeline(images=images) +print(pipeline_outputs.boxes) +print(pipeline_outputs.labels) +# [[[-0.8809833526611328, 5.1244752407073975, 27.885415077209473, 57.20366072654724], [-9.014896631240845, -2.4366320967674255, 21.488688468933105, 37.2245477437973], [14.241515636444092, 11.096746131777763, 30.164274215698242, 22.02291651070118], [7.107024908065796, 5.017698150128126, 15.09239387512207, 10.45704211294651]]] +# [['8367.0', '1274.0', '8192.0', '6344.0']] +``` ### Cross Use Case Functionality diff --git a/docs/use-cases/nlp/question-answering.md b/docs/use-cases/nlp/question-answering.md index 527c99e08f..4f49cf5cf9 100644 --- a/docs/use-cases/nlp/question-answering.md +++ b/docs/use-cases/nlp/question-answering.md @@ -280,6 +280,37 @@ resp = requests.post(url, json=obj) print(resp.text) # {"score":19.74649429321289,"answer":"CPU runtime","start":73,"end":84} ``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to deploy question answering pipelines with custom ONNX files. + +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [DistilBERT - SQuAD page](https://sparsezoo.neuralmagic.com/models/nlp%2Fquestion_answering%2Fdistilbert-none%2Fpytorch%2Fhuggingface%2Fsquad%2Fpruned80_quant-none-vnni) to download an ONNX DistilBERT model for demonstration. + +Extract the downloaded file and create a folder containing the following required files: +- `config.json` +- `tokenizer.json` +- `model.onnx` + +Use the folder as the model path to the question answering pipeline: +```python +from deepsparse import Pipeline +from sparsezoo import Model + +task = "question-answering" +stub = "zoo:nlp/question_answering/distilbert-none/pytorch/huggingface/squad/pruned80_quant-none-vnni" +model = Model(stub) +model_path = f"{model.path}/deployment" +qa_pipeline = Pipeline.create( + task=task, + model_path=model_path, + ) + +q_context = "DeepSparse is sparsity-aware inference runtime offering GPU-class performance on CPUs and APIs to integrate ML into your application" +question = "What is DeepSparse?" +output = qa_pipeline(question=question, context=q_context) +print(output.answer) +# sparsity-aware +``` ### Cross Use Case Functionality Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. diff --git a/docs/use-cases/nlp/sentiment-analysis.md b/docs/use-cases/nlp/sentiment-analysis.md index 9633fd1c6b..45422a21cf 100644 --- a/docs/use-cases/nlp/sentiment-analysis.md +++ b/docs/use-cases/nlp/sentiment-analysis.md @@ -307,7 +307,36 @@ resp = requests.post(url=url, json=obj) print(resp.text) # >> {"labels":[["positive","negative"]],"scores":[[0.9330279231071472,0.06697207689285278]]} ``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to deploy sentiment analysis pipelines with custom ONNX files. +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [oBERT base uncased - sst2 page](https://sparsezoo.neuralmagic.com/models/nlp%2Fsentiment_analysis%2Fobert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fpruned90_quant-none) +to download an ONNX oBERT base uncased model for demonstration. + +Extract the downloaded file and create a folder containing the following required files: +- `config.json` +- `tokenizer.json` +- `model.onnx` + +Use the folder as the model path to the sentiment analysis pipeline: +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +batch_size = 1 +sa_pipeline = Pipeline.create( + task="sentiment-analysis", + model_path="sentiment-analysis", # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference +prediction = sa_pipeline("The sentiment analysis pipeline is fast and easy to use") +print(prediction) +# labels=['positive'] scores=[0.9955807328224182] + +``` ### Cross Use Case Functionality Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. diff --git a/docs/use-cases/nlp/text-classification.md b/docs/use-cases/nlp/text-classification.md index f6a1845aaa..26d8ecfe83 100644 --- a/docs/use-cases/nlp/text-classification.md +++ b/docs/use-cases/nlp/text-classification.md @@ -401,6 +401,37 @@ resp = requests.post(url=url, json=obj) print(resp.text) # {"labels":[["1","0"]],"scores":[[0.9941965341567993,0.005803497973829508]]} +``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to deploy text classification pipelines with custom ONNX files. + +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [BERT base uncased page](https://sparsezoo.neuralmagic.com/models/nlp%2Ftext_classification%2Fbert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fbase-none) +to download an ONNX BERT base uncased model for demonstration. + +Extract the downloaded file and create a folder containing the following required files: +- `config.json` +- `tokenizer.json` +- `model.onnx` + +Use the folder as the model path to the text classification pipeline: +```python +from deepsparse import Pipeline + +from sparsezoo import Model + +# download onnx from sparsezoo and compile with batch size 1 +pipeline = Pipeline.create( + task="text-classification", + model_path="text-classification", # sparsezoo stub or path to local ONNX + batch_size=1 # default batch size is 1 +) + +# run inference +sequences = ["I think DeepSparse Pipelines are awesome!"] +prediction = pipeline(sequences) +print(prediction) +# labels=['LABEL_1'] scores=[0.9996163845062256] ``` ### Cross Use Case Functionality diff --git a/docs/use-cases/nlp/token-classification.md b/docs/use-cases/nlp/token-classification.md index ac9351790c..a6a76c572a 100644 --- a/docs/use-cases/nlp/token-classification.md +++ b/docs/use-cases/nlp/token-classification.md @@ -269,7 +269,29 @@ resp = requests.post(url=url, json=obj) print(resp.text) # {"predictions":[[{"entity":"PER","score":0.9966245293617249,"word":"mary","start":0,"end":4,"index":null,"is_grouped":true},{"entity":"LOC","score":0.999544084072113,"word":"nairobi","start":20,"end":27,"index":null,"is_grouped":true},{"entity":"LOC","score":0.9982004165649414,"word":"new york","start":31,"end":39,"index":null,"is_grouped":true}]]} ``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to deploy token classification pipelines with custom ONNX files. +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [oBERT page](https://sparsezoo.neuralmagic.com/models/nlp%2Ftoken_classification%2Fobert-base%2Fpytorch%2Fhuggingface%2Fconll2003%2Fpruned90_quant-none) +to download an ONNX oBERT model for demonstration. + +Extract the downloaded file and create a folder containing the following required files: +- `config.json` +- `tokenizer.json` +- `model.onnx` + +Use the folder as the model path to the token classification pipeline: +```python +from deepsparse import Pipeline +pipeline = Pipeline.create( + task="token_classification", + model_path="token_classification", + ) +output = pipeline("Mary is flying from Nairobi to New York") +print(output.predictions) +# [[TokenClassificationResult(entity='B-PER', score=0.9971914291381836, word='mary', start=0, end=4, index=1, is_grouped=False), TokenClassificationResult(entity='B-LOC', score=0.9993892312049866, word='nairobi', start=20, end=27, index=5, is_grouped=False), TokenClassificationResult(entity='B-LOC', score=0.9993736147880554, word='new', start=31, end=34, index=7, is_grouped=False), TokenClassificationResult(entity='I-LOC', score=0.997299075126648, word='york', start=35, end=39, index=8, is_grouped=False)]] +``` ### Cross Use Case Functionality Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. diff --git a/docs/use-cases/nlp/transformers-embedding-extraction.md b/docs/use-cases/nlp/transformers-embedding-extraction.md index db62e13d2b..f0cd974859 100644 --- a/docs/use-cases/nlp/transformers-embedding-extraction.md +++ b/docs/use-cases/nlp/transformers-embedding-extraction.md @@ -205,7 +205,34 @@ result = json.loads(resp.text) print(len(result["embeddings"][0])) # 768 ``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to deploy transformer embedding extraction pipelines with custom ONNX files. +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [DistilBERT - wikipedia_bookcorpus page](https://sparsezoo.neuralmagic.com/models/nlp%2Fmasked_language_modeling%2Fdistilbert-none%2Fpytorch%2Fhuggingface%2Fwikipedia_bookcorpus%2Fpruned80_quant-none-vnni) +to download an ONNX DistilBERT model for demonstration. + +Extract the downloaded file and create a folder containing the following required files: +- `config.json` +- `tokenizer.json` +- `model.onnx` + +Use the folder as the model path to the transformer embedding extraction pipeline: +```python +from deepsparse import Pipeline + +bert_emb_pipeline = Pipeline.create( + task="transformers_embedding_extraction", + model_path="transformers_embedding_extraction", + emb_extraction_layer=-1, # (default: detect last layer) + extraction_strategy="per_token" # (default: concat embedding for each token) +) + +input_sequence = "The generalized embedding extraction Pipeline is the best!" +embedding = bert_emb_pipeline(input_sequence) +print(len(embedding.embeddings[0])) +# 98304 +``` ### Cross Use Case Functionality Check out the [Server User Guide](../../user-guide/deepsparse-server.md) for more details on configuring the Server. diff --git a/docs/use-cases/nlp/zero-shot-text-classification.md b/docs/use-cases/nlp/zero-shot-text-classification.md index 4fe2e7c08b..88ea6a94cb 100644 --- a/docs/use-cases/nlp/zero-shot-text-classification.md +++ b/docs/use-cases/nlp/zero-shot-text-classification.md @@ -248,6 +248,35 @@ resp = requests.post(url=url, json=obj) print(resp.text) # {"sequences":["The Boston Red Sox are my favorite baseball team!"],"labels":[["sports","politics","public health"]],"scores":[[0.7818478941917419,0.17189143598079681,0.04626065865159035]]} +``` +## Using a Custom ONNX File +Apart from using models from the SparseZoo, DeepSparse allows you to deploy zero-shot text classification pipelines with custom ONNX files. + +The first step is to obtain the ONNX model. You can obtain the file by converting your model to ONNX after training. +Click Download on the [BERT base uncased page](https://sparsezoo.neuralmagic.com/models/nlp%2Ftext_classification%2Fbert-base%2Fpytorch%2Fhuggingface%2Fsst2%2Fbase-none) +to download an ONNX BERT base uncased model for demonstration. + +Extract the downloaded file and create a folder containing the following required files: +- `config.json` +- `tokenizer.json` +- `model.onnx` + +Use the folder as the model path to the zero-shot text classification pipeline: +```python +from deepsparse import Pipeline + +# download onnx from sparsezoo and compile with batch size 1 +pipeline = Pipeline.create( + task="zero_shot_text_classification", + model_path="text-classification", # sparsezoo stub or path to local ONNX + batch_size=1, # default batch size is 1 + labels=["poltics", "public health", "sports"] +) + +# run inference +prediction = pipeline("Who are you voting for in the upcoming election") +print(prediction) +# sequences='Who are you voting for in the upcoming election' labels=['sports', 'poltics', 'public health'] scores=[0.35093653202056885, 0.3335352838039398, 0.31552815437316895] ``` ### Cross Use Case Functionality From 4a15621910a657b9d2d6690f44faf04ac23ca6e4 Mon Sep 17 00:00:00 2001 From: Robert Shaw Date: Mon, 24 Apr 2023 16:30:01 -0400 Subject: [PATCH 149/149] restored src/* --- src/deepsparse/__init__.py | 14 +- src/deepsparse/benchmark/README.md | 174 ++++++++- src/deepsparse/benchmark/__init__.py | 5 - src/deepsparse/benchmark/benchmark_model.py | 1 - src/deepsparse/benchmark/ort_engine.py | 37 +- src/deepsparse/cpu.py | 58 +-- src/deepsparse/engine.py | 201 +++-------- src/deepsparse/image_classification/README.md | 200 ++++++++++- .../image_classification/__init__.py | 4 - src/deepsparse/open_pif_paf/__init__.py | 5 - src/deepsparse/server/README.md | 156 +++++++- src/deepsparse/server/__init__.py | 5 - src/deepsparse/server/cli.py | 2 +- src/deepsparse/transformers/README.md | 333 +++++++++++++++++- src/deepsparse/transformers/__init__.py | 4 - src/deepsparse/utils/onnx.py | 23 +- src/deepsparse/yolact/README.md | 187 +++++++++- src/deepsparse/yolact/__init__.py | 4 - src/deepsparse/yolo/README.md | 170 ++++++++- src/deepsparse/yolo/__init__.py | 5 - src/deepsparse/yolo/pipelines.py | 6 - src/deepsparse/yolo/utils/utils.py | 2 - src/deepsparse/yolov8/__init__.py | 5 - .../yolov8/utils/validation/helpers.py | 39 +- src/deepsparse/yolov8/validation.py | 23 +- 25 files changed, 1303 insertions(+), 360 deletions(-) diff --git a/src/deepsparse/__init__.py b/src/deepsparse/__init__.py index 0c0e7e0d1b..7ac2b698ef 100644 --- a/src/deepsparse/__init__.py +++ b/src/deepsparse/__init__.py @@ -31,11 +31,19 @@ cpu_vnni_compatible, ) from .engine import * -from .tasks import * from .timing import * from .pipeline import * from .loggers import * from .version import __version__, is_release -from .analytics import deepsparse_analytics as _analytics -_analytics.send_event("python__init") +try: + from sparsezoo.package import check_package_version as _check_package_version + + _check_package_version( + package_name=__name__ if is_release else f"{__name__}-nightly", + package_version=__version__, + ) +except Exception as err: + print( + f"Need sparsezoo version above 0.9.0 to run Neural Magic's latest-version check\n{err}" + ) diff --git a/src/deepsparse/benchmark/README.md b/src/deepsparse/benchmark/README.md index 435e9f70f9..c67133744e 100644 --- a/src/deepsparse/benchmark/README.md +++ b/src/deepsparse/benchmark/README.md @@ -14,6 +14,176 @@ See the License for the specific language governing permissions and limitations under the License. --> -# DeepSparse Benchmarking +## 📜 Benchmarking ONNX Models -[Checkout DeepSparse Benchmarking User Guide for usage details](../../../docs/user-guide/deepsparse-benchmarking.md) +`deepsparse.benchmark` is a command-line (CLI) tool for benchmarking the DeepSparse Engine with ONNX models. The tool will parse the arguments, download/compile the network into the engine, generate input tensors, and execute the model depending on the chosen scenario. By default, it will choose a multi-stream or asynchronous mode to optimize for throughput. + +### Quickstart + +After `pip install deepsparse`, the benchmark tool is available on your CLI. For example, to benchmark a dense BERT ONNX model fine-tuned on the SST2 dataset where the model path is the minimum input required to get started, run: + +``` +deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none +``` +__ __ +### Usage + +In most cases, good performance will be found in the default options so it can be as simple as running the command with a SparseZoo model stub or your local ONNX model. However, if you prefer to customize benchmarking for your personal use case, you can run `deepsparse.benchmark -h` or with `--help` to view your usage options: + +CLI Arguments: +``` +positional arguments: + + model_path Path to an ONNX model file or SparseZoo model stub. + +optional arguments: + + -h, --help show this help message and exit. + + -b BATCH_SIZE, --batch_size BATCH_SIZE + The batch size to run the analysis for. Must be + greater than 0. + + -shapes INPUT_SHAPES, --input_shapes INPUT_SHAPES + Override the shapes of the inputs, i.e. -shapes + "[1,2,3],[4,5,6],[7,8,9]" results in input0=[1,2,3] + input1=[4,5,6] input2=[7,8,9]. + + -ncores NUM_CORES, --num_cores NUM_CORES + The number of physical cores to run the analysis on, + defaults to all physical cores available on the system. + + -s {async,sync,elastic}, --scenario {async,sync,elastic} + Choose between using the async, sync and elastic + scenarios. Sync and async are similar to the single- + stream/multi-stream scenarios. Elastic is a newer + scenario that behaves similarly to the async scenario + but uses a different scheduling backend. Default value + is async. + + -t TIME, --time TIME + The number of seconds the benchmark will run. Default + is 10 seconds. + + -w WARMUP_TIME, --warmup_time WARMUP_TIME + The number of seconds the benchmark will warmup before + running.Default is 2 seconds. + + -nstreams NUM_STREAMS, --num_streams NUM_STREAMS + The number of streams that will submit inferences in + parallel using async scenario. Default is + automatically determined for given hardware and may be + sub-optimal. + + -pin {none,core,numa}, --thread_pinning {none,core,numa} + Enable binding threads to cores ('core' the default), + threads to cores on sockets ('numa'), or disable + ('none'). + + -e {deepsparse,onnxruntime}, --engine {deepsparse,onnxruntime} + Inference engine backend to run eval on. Choices are + 'deepsparse', 'onnxruntime'. Default is 'deepsparse'. + + -q, --quiet Lower logging verbosity. + + -x EXPORT_PATH, --export_path EXPORT_PATH + Store results into a JSON file. +``` +💡**PRO TIP**💡: save your benchmark results in a convenient JSON file! + +Example CLI command for benchmarking an ONNX model from the SparseZoo and saving the results to a `benchmark.json` file: + +``` +deepsparse.benchmark zoo:nlp/text_classification/bert-base/pytorch/huggingface/sst2/base-none -x benchmark.json +``` +Output of the JSON file: + +![alt text](./img/json_output.png) + +#### Sample CLI Argument Configurations + +To run a sparse FP32 MobileNetV1 at batch size 16 for 10 seconds for throughput using 8 streams of requests: + +``` +deepsparse.benchmark zoo:cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-moderate --batch_size 16 --time 10 --scenario async --num_streams 8 +``` + +To run a sparse quantized INT8 6-layer BERT at batch size 1 for latency: + +``` +deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_quant_6layers-aggressive_96 --batch_size 1 --scenario sync +``` +__ __ +### ⚡ Inference Scenarios + +#### Synchronous (Single-stream) Scenario + +Set by the `--scenario sync` argument, the goal metric is latency per batch (ms/batch). This scenario submits a single inference request at a time to the engine, recording the time taken for a request to return an output. This mimics an edge deployment scenario. + +The latency value reported is the mean of all latencies recorded during the execution period for the given batch size. + +#### Asynchronous (Multi-stream) Scenario + +Set by the `--scenario async` argument, the goal metric is throughput in items per second (i/s). This scenario submits `--num_streams` concurrent inference requests to the engine, recording the time taken for each request to return an output. This mimics a model server or bulk batch deployment scenario. + +The throughput value reported comes from measuring the number of finished inferences within the execution time and the batch size. + +#### Example Benchmarking Output of Synchronous vs. Asynchronous + +**BERT 3-layer FP32 Sparse Throughput** + +No need to add *scenario* argument since `async` is the default option: +``` +deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 +[INFO benchmark_model.py:202 ] Thread pinning to cores enabled +DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) +[INFO benchmark_model.py:247 ] deepsparse.engine.Engine: + onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx + batch_size: 1 + num_cores: 18 + scheduler: Scheduler.multi_stream + cpu_avx_type: avx512 + cpu_vnni: False +[INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] +[INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] +[INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] +[INFO benchmark_model.py:264 ] num_streams default value chosen of 9. This requires tuning and may be sub-optimal +[INFO benchmark_model.py:270 ] Starting 'async' performance measurements for 10 seconds +Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 +Batch Size: 1 +Scenario: multistream +Throughput (items/sec): 83.5037 +Latency Mean (ms/batch): 107.3422 +Latency Median (ms/batch): 107.0099 +Latency Std (ms/batch): 12.4016 +Iterations: 840 +``` + +**BERT 3-layer FP32 Sparse Latency** + +To select a *synchronous inference scenario*, add `-s sync`: + +``` +deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 -s sync +[INFO benchmark_model.py:202 ] Thread pinning to cores enabled +DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.10.0 (9bba6971) (optimized) (system=avx512, binary=avx512) +[INFO benchmark_model.py:247 ] deepsparse.engine.Engine: + onnx_file_path: /home/mgoin/.cache/sparsezoo/c89f3128-4b87-41ae-91a3-eae8aa8c5a7c/model.onnx + batch_size: 1 + num_cores: 18 + scheduler: Scheduler.single_stream + cpu_avx_type: avx512 + cpu_vnni: False +[INFO onnx.py:176 ] Generating input 'input_ids', type = int64, shape = [1, 384] +[INFO onnx.py:176 ] Generating input 'attention_mask', type = int64, shape = [1, 384] +[INFO onnx.py:176 ] Generating input 'token_type_ids', type = int64, shape = [1, 384] +[INFO benchmark_model.py:270 ] Starting 'sync' performance measurements for 10 seconds +Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/pruned_3layers-aggressive_83 +Batch Size: 1 +Scenario: singlestream +Throughput (items/sec): 62.1568 +Latency Mean (ms/batch): 16.0732 +Latency Median (ms/batch): 15.7850 +Latency Std (ms/batch): 1.0427 +Iterations: 622 +``` \ No newline at end of file diff --git a/src/deepsparse/benchmark/__init__.py b/src/deepsparse/benchmark/__init__.py index 432d48cf44..91d264f339 100644 --- a/src/deepsparse/benchmark/__init__.py +++ b/src/deepsparse/benchmark/__init__.py @@ -14,10 +14,5 @@ # flake8: noqa -from deepsparse.analytics import deepsparse_analytics as _analytics - from .ort_engine import * from .results import * - - -_analytics.send_event("python__benchmark__init") diff --git a/src/deepsparse/benchmark/benchmark_model.py b/src/deepsparse/benchmark/benchmark_model.py index ae37d7807f..f66c36039c 100644 --- a/src/deepsparse/benchmark/benchmark_model.py +++ b/src/deepsparse/benchmark/benchmark_model.py @@ -422,7 +422,6 @@ def benchmark_model( "seconds_to_run": time, "num_streams": num_streams, "benchmark_result": benchmark_result, - "fraction_of_supported_ops": getattr(model, "fraction_of_supported_ops", None), } # Export results diff --git a/src/deepsparse/benchmark/ort_engine.py b/src/deepsparse/benchmark/ort_engine.py index d2d61e83a1..d16b14578e 100644 --- a/src/deepsparse/benchmark/ort_engine.py +++ b/src/deepsparse/benchmark/ort_engine.py @@ -19,6 +19,8 @@ import numpy from deepsparse.utils import ( + get_input_names, + get_output_names, model_to_path, override_onnx_batch_size, override_onnx_input_shapes, @@ -100,6 +102,9 @@ def __init__( self._num_cores = num_cores self._input_shapes = input_shapes + self._input_names = get_input_names(self._model_path) + self._output_names = get_output_names(self._model_path) + if providers is None: providers = onnxruntime.get_available_providers() self._providers = providers @@ -209,34 +214,6 @@ def scheduler(self) -> None: """ return None - @property - def input_names(self) -> List[str]: - """ - :return: The ordered names of the inputs. - """ - return [node_arg.name for node_arg in self._eng_net.get_inputs()] - - @property - def input_shapes(self) -> List[Tuple]: - """ - :return: The ordered shapes of the inputs. - """ - return [tuple(node_arg.shape) for node_arg in self._eng_net.get_inputs()] - - @property - def output_names(self) -> List[str]: - """ - :return: The ordered names of the outputs. - """ - return [node_arg.name for node_arg in self._eng_net.get_outputs()] - - @property - def output_shapes(self) -> List[Tuple]: - """ - :return: The ordered shapes of the outputs. - """ - return [tuple(node_arg.shape) for node_arg in self._eng_net.get_outputs()] - @property def providers(self) -> List[str]: """ @@ -282,8 +259,8 @@ def run( """ if val_inp: self._validate_inputs(inp) - inputs_dict = {name: value for name, value in zip(self.input_names, inp)} - return self._eng_net.run(self.output_names, inputs_dict) + inputs_dict = {name: value for name, value in zip(self._input_names, inp)} + return self._eng_net.run(self._output_names, inputs_dict) def timed_run( self, inp: List[numpy.ndarray], val_inp: bool = False diff --git a/src/deepsparse/cpu.py b/src/deepsparse/cpu.py index 6f4144c876..cb3ed6e7f5 100644 --- a/src/deepsparse/cpu.py +++ b/src/deepsparse/cpu.py @@ -18,10 +18,8 @@ import json import os -import platform import subprocess import sys -from distutils.version import StrictVersion from typing import Any, Tuple @@ -41,7 +39,6 @@ VALID_VECTOR_EXTENSIONS = {"avx2", "avx512", "neon", "sve"} -MINIMUM_DARWIN_VERSION = "13.0.0" class _Memoize: @@ -143,55 +140,6 @@ def _parse_arch_bin() -> architecture: raise OSError(error_msg.format(ex)) -def allow_experimental_darwin() -> bool: - """ - Check if experimental Darwin support is allowed. - """ - try: - allow = int(os.getenv("NM_ALLOW_DARWIN", "0")) - except ValueError: - allow = False - return allow - - -def get_darwin_version() -> str: - """ - If we are running Darwin, get the current version. Otherwise return None. - """ - if sys.platform.startswith("darwin"): - return platform.mac_ver()[0] - return None - - -def check_darwin_support() -> bool: - """ - Check if the system is running Darwin and it meets the minimum version - requirements. - """ - if sys.platform.startswith("darwin") and allow_experimental_darwin(): - ver = get_darwin_version() - return StrictVersion(ver) >= StrictVersion(MINIMUM_DARWIN_VERSION) - return False - - -def platform_error_msg() -> str: - """ - Generate unsupported platform error message. - """ - if allow_experimental_darwin(): - darwin_str = f" or MacOS >= {MINIMUM_DARWIN_VERSION}" - else: - darwin_str = "" - - darwin_ver = get_darwin_version() - if darwin_ver: - current_os = f"MacOS {darwin_ver}" - else: - current_os = sys.platform - - return f"Neural Magic: Only Linux{darwin_str} is supported, not '{current_os}'." - - def cpu_architecture() -> architecture: """ Detect the CPU details on linux systems @@ -207,8 +155,10 @@ def cpu_architecture() -> architecture: :return: an instance of the architecture class """ - if not (sys.platform.startswith("linux") or check_darwin_support()): - raise OSError(platform_error_msg()) + if not sys.platform.startswith("linux"): + raise OSError( + "Neural Magic: Only Linux is supported, not '{}'.".format(sys.platform) + ) arch = _parse_arch_bin() isa_type_override = os.getenv("NM_ARCH", None) diff --git a/src/deepsparse/engine.py b/src/deepsparse/engine.py index 169b36c023..4cbcf0e86c 100644 --- a/src/deepsparse/engine.py +++ b/src/deepsparse/engine.py @@ -24,13 +24,8 @@ import numpy from tqdm.auto import tqdm -from deepsparse.analytics import deepsparse_analytics as _analytics from deepsparse.benchmark import BenchmarkResults -from deepsparse.utils import ( - generate_random_inputs, - model_to_path, - override_onnx_input_shapes, -) +from deepsparse.utils import model_to_path, override_onnx_input_shapes try: @@ -186,7 +181,6 @@ def __init__( scheduler: Scheduler = None, input_shapes: List[List[int]] = None, ): - _analytics.send_event("python__engine__init") self._model_path = model_to_path(model) self._batch_size = _validate_batch_size(batch_size) self._num_cores = _validate_num_cores(num_cores) @@ -303,34 +297,6 @@ def fraction_of_supported_ops(self) -> float: """ return round(self._eng_net.fraction_of_supported_ops(), 4) - @property - def input_names(self) -> List[str]: - """ - :return: The ordered names of the inputs. - """ - return self._eng_net.input_names() - - @property - def input_shapes(self) -> List[Tuple]: - """ - :return: The ordered shapes of the inputs. - """ - return self._eng_net.input_dims() - - @property - def output_names(self) -> List[str]: - """ - :return: The ordered names of the outputs. - """ - return self._eng_net.output_names() - - @property - def output_shapes(self) -> List[Tuple]: - """ - :return: The ordered shapes of the outputs. - """ - return self._eng_net.output_dims() - @property def cpu_avx_type(self) -> str: """ @@ -348,13 +314,6 @@ def cpu_vnni(self) -> bool: """ return self._cpu_vnni - def generate_random_inputs(self) -> List[numpy.ndarray]: - """ - Generate random data that matches the type and shape of the ONNX model - :return: List of random tensors - """ - return generate_random_inputs(self.model_path, self.batch_size) - def run( self, inp: List[numpy.ndarray], @@ -571,6 +530,47 @@ def benchmark_loader( return results + def analyze( + self, + inp: List[numpy.ndarray], + num_iterations: int = 20, + num_warmup_iterations: int = 5, + optimization_level: int = 1, + imposed_as: Optional[float] = None, + imposed_ks: Optional[float] = None, + ): + """ + Function to analyze a model's performance in the DeepSparse Engine. + + Note 1: Analysis is currently only supported on a single socket. + + :param inp: The list of inputs to pass to the engine for analyzing inference. + The expected order is the inputs order as defined in the ONNX graph. + :param num_iterations: The number of times to repeat execution of the model + while analyzing, default is 20 + :param num_warmup_iterations: The number of times to repeat execution of the model + before analyzing, default is 5 + :param optimization_level: The amount of graph optimizations to perform. + The current choices are either 0 (minimal) or 1 (all), default is 1 + :param imposed_as: Imposed activation sparsity, defaults to None. + Will force the activation sparsity from all ReLu layers in the graph + to match this desired sparsity level (percentage of 0's in the tensor). + Beneficial for seeing how AS affects the performance of the model. + :param imposed_ks: Imposed kernel sparsity, defaults to None. + Will force all prunable layers in the graph to have weights with + this desired sparsity level (percentage of 0's in the tensor). + Beneficial for seeing how pruning affects the performance of the model. + :return: the analysis structure containing the performance details of each layer + """ + return self._eng_net.benchmark( + inp, + num_iterations, + num_warmup_iterations, + optimization_level, + imposed_as, + imposed_ks, + ) + def _validate_inputs(self, inp: List[numpy.ndarray]): if isinstance(inp, str) or not isinstance(inp, List): raise ValueError("inp must be a list, given {}".format(type(inp))) @@ -603,115 +603,6 @@ def _properties_dict(self) -> Dict: } -class DebugAnalysisEngine(Engine): - """ - A subclass of Engine that supports debug analysis. - - :param model: Either a path to the model's onnx file, a SparseZoo model stub - prefixed by 'zoo:', a SparseZoo Model object, or a SparseZoo ONNX File - object that defines the neural network - :param batch_size: The batch size of the inputs to be used with the engine - :param num_cores: The number of physical cores to run the model on. If more - cores are requested than are available on a single socket, the engine - will try to distribute them evenly across as few sockets as possible. - :param num_streams: The max number of requests the model can handle - concurrently. - :param scheduler: The kind of scheduler to execute with. Pass None for the default. - :param input_shapes: The list of shapes to set the inputs to. Pass None to use model as-is. - :param num_iterations: The number of iterations to run benchmarking for. - Default is 20 - :param num_warmup_iterations: T number of iterations to warm up engine before - benchmarking. These executions will not be counted in the benchmark - results that are returned. Useful and recommended to bring - the system to a steady state. Default is 5 - :param include_inputs: If True, inputs from forward passes during benchmarking - will be added to the results. Default is False - :param include_outputs: If True, outputs from forward passes during benchmarking - will be added to the results. Default is False - :param show_progress: If True, will display a progress bar. Default is False - :param scheduler: The kind of scheduler to execute with. Pass None for the default. - """ - - def __init__( - self, - model: Union[str, "Model", "File"], - batch_size: int = 1, - num_cores: int = None, - scheduler: Scheduler = None, - input_shapes: List[List[int]] = None, - num_iterations: int = 20, - num_warmup_iterations: int = 5, - optimization_level: int = 1, - imposed_as: Optional[float] = None, - imposed_ks: Optional[float] = None, - ): - self._model_path = model_to_path(model) - self._batch_size = _validate_batch_size(batch_size) - self._num_cores = _validate_num_cores(num_cores) - self._scheduler = _validate_scheduler(scheduler) - self._input_shapes = input_shapes - self._cpu_avx_type = AVX_TYPE - self._cpu_vnni = VNNI - - num_streams = _validate_num_streams(None, self._num_cores) - if self._input_shapes: - with override_onnx_input_shapes( - self._model_path, self._input_shapes - ) as model_path: - self._eng_net = LIB.deepsparse_engine( - model_path, - self._batch_size, - self._num_cores, - num_streams, - self._scheduler.value, - None, - "external", - num_iterations, - num_warmup_iterations, - optimization_level, - imposed_as, - imposed_ks, - ) - else: - self._eng_net = LIB.deepsparse_engine( - self._model_path, - self._batch_size, - self._num_cores, - num_streams, - self._scheduler.value, - None, - "external", - num_iterations, - num_warmup_iterations, - optimization_level, - imposed_as, - imposed_ks, - ) - - def analyze( - self, - inp: List[numpy.ndarray], - val_inp: bool = True, - ) -> List[numpy.ndarray]: - """ - Function to analyze a model's performance in the DeepSparse Engine. - - Note 1: Analysis is currently only supported on a single socket. - - :param inp: The list of inputs to pass to the engine for analyzing inference. - The expected order is the inputs order as defined in the ONNX graph. - :param val_inp: Validate the input to the model to ensure numpy array inputs - are setup correctly for the DeepSparse Engine - :return: the analysis structure containing the performance details of each layer - """ - if val_inp: - self._validate_inputs(inp) - - [out, bench_info] = self._eng_net.benchmark_execute(inp) - - return bench_info - - class Context(object): """ Contexts can be used to run multiple instances of the MultiModelEngine with the same @@ -969,17 +860,19 @@ def model_debug_analysis( :param scheduler: The kind of scheduler to execute with. Pass None for the default. :return: the analysis structure containing the performance details of each layer """ - model = DebugAnalysisEngine( + model = compile_model( model=model, batch_size=batch_size, num_cores=num_cores, scheduler=scheduler, input_shapes=input_shapes, + ) + + return model.analyze( + inp, num_iterations=num_iterations, num_warmup_iterations=num_warmup_iterations, optimization_level=optimization_level, imposed_as=imposed_as, imposed_ks=imposed_ks, ) - - return model.analyze(inp) diff --git a/src/deepsparse/image_classification/README.md b/src/deepsparse/image_classification/README.md index 319d5eb0c9..1429ed8f5b 100644 --- a/src/deepsparse/image_classification/README.md +++ b/src/deepsparse/image_classification/README.md @@ -1,3 +1,199 @@ -# Image Classification Use Case +# Image Classification Inference Pipelines -[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/image-classification.md) + +[DeepSparse] Image Classification integration allows accelerated inference, +serving, and benchmarking of sparsified image classification models. +This integration allows for leveraging the DeepSparse Engine to run +sparsified image classification inference with GPU-class performance directly +on the CPU. + +The DeepSparse Engine takes advantage of sparsity within neural networks to +reduce compute as well as accelerate memory-bound workloads. +The Engine is particularly effective when leveraging sparsification methods +such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and +[quantization](https://arxiv.org/abs/1609.07061). These techniques result in +significantly more performant and smaller models with limited to no effect on +the baseline metrics. + +## Getting Started + +Before you start your adventure with the DeepSparse Engine, make sure that +your machine is compatible with our [hardware requirements]. + +### Installation + +```pip install deepsparse``` + +### Model Format + +By default, to deploy image classification models using the DeepSparse Engine, +the model should be supplied in the [ONNX] format. +This grants the Engine the flexibility to serve any model in a framework-agnostic +manner. + +Below we describe two possibilities to obtain the required ONNX model. + +#### Exporting the onnx file from the contents of a local checkpoint + +This pathway is relevant if you intend to deploy a model created using [SparseML] library. +For more information refer to the appropriate integration documentation in [SparseML]. + +1. The output of the [SparseML] training is saved to output directory `/{save_dir}` (e.g. `/trained_model`) +2. Depending on the chosen framework, the model files are saved to `model_path`=`/{save_dir}/{framework_name}/{model_tag}` (e.g `/trained_model/pytorch/resnet50/`) +3. To generate an onnx model, refer to the [script for image classification ONNX export](https://github.com/neuralmagic/sparseml/blob/main/src/sparseml/pytorch/image_classification/export.py). + +Example: +```bash +sparseml.image_classification.export_onnx \ + --arch-key resnet50 \ + --dataset imagenet \ + --dataset-path ~/datasets/ILSVRC2012 \ + --checkpoint-path ~/checkpoints/resnet50_checkpoint.pth +``` +This creates `model.onnx` file, in the parent directory of your `model_path` + +#### Directly using the SparseZoo stub + +Alternatively, you can skip the process of onnx model export by downloading all the required model data directly from Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). +Example: +```python +from sparsezoo import Model + +# you can lookup an appropriate model stub here: https://sparsezoo.neuralmagic.com/ +model_stub = "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none" +model = Model(model_stub) + +# directly download the model data to your local directory +model_path = model.path + +# the onnx model file is there, ready for deployment +import os +os.path.isfile(model.onnx_model.path) +>>>True +``` + + +## Deployment APIs + +DeepSparse provides both a python Pipeline API and an out-of-the-box model +server that can be used for end-to-end inference in either existing python +workflows or as an HTTP endpoint. Both options provide similar specifications +for configurations and support a variety of Image Classification models. + +### Python API + +Pipelines are the default interface for running the inference with the +DeepSparse Engine. + +Once a model is obtained, either through [SparseML] training or directly from [SparseZoo], +`deepsparse.Pipeline` can be used to easily facilitate end to end inference and deployment +of the sparsified image classification model. + +If no model is specified to the `Pipeline` for a given task, the `Pipeline` will automatically +select a pruned and quantized model for the task from the `SparseZoo` that can be used for accelerated +inference. Note that other models in the [SparseZoo] will have different tradeoffs between speed, size, +and accuracy. + +To learn about sparsification in more detail, refer to [SparseML docs](https://docs.neuralmagic.com/sparseml/) + +### HTTP Server + +As an alternative to Python API, the DeepSparse inference server allows you to +serve ONNX models and pipelines in HTTP. Both configuring and making requests +to the server follow the same parameters and schemas as the Pipelines enabling +simple deployment. Once launched, a `/docs` endpoint is created with full +endpoint descriptions and support for making sample requests. + +Example deployment using a 95% pruned resnet50 is given below +For full documentation on deploying sparse image classification models with the +DeepSparse Server, see the [documentation](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/server). + +##### Installation + +The deepsparse server requirements can be installed by specifying the `server` +extra dependency when installing DeepSparse. + +```bash +pip install deepsparse[server] +``` + +## Deployment Use Cases + +The following section includes example usage of the Pipeline and server APIs for +various image classification models. + +[List of Image Classification SparseZoo Models](https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=classification&page=1) + + +#### Python Pipeline + +```python +from deepsparse import Pipeline +cv_pipeline = Pipeline.create( + task='image_classification', + model_path='zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none', # Path to checkpoint or SparseZoo stub +) +input_image = "my_image.png" # path to input image +inference = cv_pipeline(images=input_image) +``` + +#### HTTP Server + +Spinning up: +```bash +deepsparse.server \ + task image_classification \ + --model_path "zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none" \ + --port 5543 +``` + +Making a request: +```python +import requests + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['goldfish.jpeg'] # just put the name of images in here +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +``` + +### Benchmarking + +The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. +Want to find out how fast our sparse ONNX models perform inference? +You can quickly do benchmarking tests on your own with a single CLI command! + +You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: +```bash +deepsparse.benchmark zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none +``` +Output: +```bash +Original Model Path: zoo:cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned95-none +Batch Size: 1 +Scenario: async +Throughput (items/sec): 299.2372 +Latency Mean (ms/batch): 16.6677 +Latency Median (ms/batch): 16.6748 +Latency Std (ms/batch): 0.1728 +Iterations: 2995 +``` + +To learn more about benchmarking, refer to the appropriate documentation. +Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! + +## Tutorials: +For a deeper dive into using image classification models within the Neural Magic +ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/): +- [CV Use Cases](https://neuralmagic.com/use-cases/#computervision) + +## Support +For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). + + +[DeepSparse]: https://github.com/neuralmagic/deepsparse +[hardware requirements]: https://docs.neuralmagic.com/deepsparse/source/hardware.html +[ONNX]: https://onnx.ai/ +[SparseML]: https://github.com/neuralmagic/sparseml +[SparseML Image Classification Documentation]: https://github.com/neuralmagic/sparseml/tree/main/src/sparseml/pytorch/image_classification/README_image_classification.md +[SparseZoo]: https://sparsezoo.neuralmagic.com/ \ No newline at end of file diff --git a/src/deepsparse/image_classification/__init__.py b/src/deepsparse/image_classification/__init__.py index cf62c40992..00ceb5828e 100644 --- a/src/deepsparse/image_classification/__init__.py +++ b/src/deepsparse/image_classification/__init__.py @@ -18,10 +18,6 @@ import warnings from collections import namedtuple -from deepsparse.analytics import deepsparse_analytics as _analytics - - -_analytics.send_event("python__image_classification__init") _LOGGER = _logging.getLogger(__name__) _Dependency = namedtuple("_Dependency", ["name", "import_name", "version", "necessary"]) diff --git a/src/deepsparse/open_pif_paf/__init__.py b/src/deepsparse/open_pif_paf/__init__.py index 78fe2add68..8d3ec2e88e 100644 --- a/src/deepsparse/open_pif_paf/__init__.py +++ b/src/deepsparse/open_pif_paf/__init__.py @@ -12,9 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # flake8: noqa -from deepsparse.analytics import deepsparse_analytics as _analytics - from .utils import * - - -_analytics.send_event("python__open_pif_paf__init") diff --git a/src/deepsparse/server/README.md b/src/deepsparse/server/README.md index 8e53c2162e..1f8a84ebb4 100644 --- a/src/deepsparse/server/README.md +++ b/src/deepsparse/server/README.md @@ -1,3 +1,155 @@ -# DeepSparse Server +## 🔌 DeepSparse Server + +```bash +pip install deepsparse[server] +``` + +The DeepSparse server allows you to serve models and pipelines for deployment in HTTP. The server runs on top of the popular FastAPI web framework and Uvicorn web server. +The server supports any task from deepsparse. Pipeline including NLP, image classification, and object detection tasks. +An updated list of available tasks can be found +[here](https://github.com/neuralmagic/deepsparse/blob/main/src/deepsparse/PIPELINES.md) + + - Run `deepsparse.server --help` to lookup the available CLI arguments. + +``` +Usage: deepsparse.server [OPTIONS] COMMAND [ARGS]... + + Start a DeepSparse inference server for serving the models and pipelines. + + 1. `deepsparse.server --config_file [OPTIONS] ` + + 2. `deepsparse.server task [OPTIONS] + + Examples for using the server: + + `deepsparse.server --config_file server-config.yaml` + + `deepsparse.server task question_answering --batch-size 2` + + `deepsparse.server task question_answering --host "0.0.0.0"` + + Example config.yaml for serving: + + \```yaml + num_cores: 2 + num_workers: 2 + endpoints: + - task: question_answering + route: /unpruned/predict + model: zoo:some/zoo/stub + name: question_answering_pipeline_1 + - task: question_answering + route: /pruned/predict + model: /path/to/local/model + name: question_answering_pipeline_2 + \``` + + Optionally, to manually specify the set of loggers, define a + dictionary that maps loggers' names to their initialization arguments: + + \```yaml + num_cores: 2 + num_workers: 2 + loggers: + prometheus: + port: 6100 + text_log_save_dir: /home/deepsparse-server/prometheus + text_log_save_freq: 30 + endpoints: + - task: question_answering + ... + ... + \``` + +Options: + --help Show this message and exit. + +Commands: + config Run the server using configuration from a .yaml file. + task Run the server using configuration with CLI options, which can... +``` + +### Single Model Inference + +Example CLI command for serving a single model for the **question answering** task: + +```bash +deepsparse.server \ + task question_answering \ + --model_path "zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni" +``` + +To make a request to your server, use the `requests` library and pass the request URL: + +```python +import requests + +url = "http://localhost:5543/predict" + +obj = { + "question": "Who is Mark?", + "context": "Mark is batman." +} + +response = requests.post(url, json=obj) +``` + +In addition, you can make a request with a `curl` command from terminal: + +```bash +curl -X POST \ + 'http://localhost:5543/predict' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "question": "Who is Mark?", + "context": "Mark is batman." +}' +``` +__ __ +### Multiple Model Inference +To serve multiple models you can build a `config.yaml` file. +In the sample YAML file below, we are defining two BERT models to be served by the `deepsparse.server` for the **question answering** task: + +```yaml +num_cores: 2 +num_workers: 2 +endpoints: + - task: question_answering + route: /unpruned/predict + model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/base-none + batch_size: 1 + - task: question_answering + route: /pruned/predict + model: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni + batch_size: 1 +``` +You can now run the server with the config file path using the `config` sub command: + +```bash +deepsparse.server config config.yaml +``` + +You can send requests to a specific model by appending the model's `alias` from the `config.yaml` to the end of the request url. For example, to call the second model, you can send a request to its configured route: + +```python +import requests + +url = "http://localhost:5543/pruned/predict" + +obj = { + "question": "Who is Mark?", + "context": "Mark is batman." +} + +response = requests.post(url, json=obj) +``` + +💡 **PRO TIP** 💡: While your server is running, you can always use the awesome swagger UI that's built into FastAPI to view your model's pipeline `POST` routes. +The UI also enables you to easily make sample requests to your server. +All you need is to add `/docs` at the end of your host URL: + + localhost:5543/docs + +![alt text](./img/swagger_ui.png) -[Checkout DeepSparse Server User Guide for usage details](../../../docs/user-guide/deepsparse-server.md) diff --git a/src/deepsparse/server/__init__.py b/src/deepsparse/server/__init__.py index 3ff5d0fb10..4e63b031c7 100644 --- a/src/deepsparse/server/__init__.py +++ b/src/deepsparse/server/__init__.py @@ -19,9 +19,4 @@ the DeepSparse Engine. """ -from deepsparse.analytics import deepsparse_analytics as _analytics - from .cli import main - - -_analytics.send_event("python__server__init") diff --git a/src/deepsparse/server/cli.py b/src/deepsparse/server/cli.py index 1b323e28e3..82aed430b0 100644 --- a/src/deepsparse/server/cli.py +++ b/src/deepsparse/server/cli.py @@ -199,7 +199,7 @@ def main( prometheus: port: 6100 text_log_save_dir: /home/deepsparse-server/prometheus - text_log_save_frequency: 30 + text_log_save_freq: 30 endpoints: - task: question_answering ... diff --git a/src/deepsparse/transformers/README.md b/src/deepsparse/transformers/README.md index dbf32ec1a4..7cc0439dcc 100644 --- a/src/deepsparse/transformers/README.md +++ b/src/deepsparse/transformers/README.md @@ -1,9 +1,324 @@ -# NLP Use Cases - -Checkout DeepSparse Use Cases for usage details: -- [Sentiment Analysis](../../../docs/use-cases/nlp/sentiment-analysis.md) -- [Text Classification](../../../docs/use-cases/nlp/text-classification.md) -- [Token Classification](../../../docs/use-cases/nlp/token-classification.md) -- [Question Answering](../../../docs/use-cases/nlp/question-answering.md) -- [Zero Shot Text Classification](../../../docs/use-cases/nlp/zero-shot-text-classification.md) -- [Embedding Extraction](../../../docs/use-cases/nlp/transformers-embedding-extraction.md) +# Hugging Face Transformer Inference Pipelines + + +DeepSparse allows accelerated inference, serving, and benchmarking of sparsified [Hugging Face Transformer](https://github.com/huggingface/transformers) models. +This integration allows for leveraging the DeepSparse Engine to run the sparsified transformer inference with GPU-class performance directly on the CPU. + +The DeepSparse Engine is taking advantage of sparsity within neural networks to +reduce compute required as well as accelerate memory-bound workloads. The engine is particularly effective when leveraging sparsification +methods such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and [quantization](https://arxiv.org/abs/1609.07061). +These techniques result in significantly more performant and smaller models with limited to no effect on the baseline metrics. + +This integration currently supports several fundamental NLP tasks: +- **Question Answering** - posing questions about a document +- **Sentiment Analysis** - assigning a sentiment to a piece of text +- **Text Classification** - assigning a label or class to a piece of text (e.g duplicate question pairing) +- **Token Classification** - attributing a label to each token in a sentence (e.g. Named Entity Recognition task) + +We are actively working on adding more use cases, stay tuned! + +## Getting Started + +Before you start your adventure with the DeepSparse Engine, make sure that your machine is +compatible with our [hardware requirements](https://docs.neuralmagic.com/deepsparse/source/hardware.html). + +### Installation + +```pip install deepsparse``` + +### Model Format +By default, to deploy the transformer using DeepSparse Engine it is required to supply the model in the ONNX format along with the HuggingFace supporting files. +This grants the engine the flexibility to serve any model in a framework-agnostic environment. + +The DeepSparse pipelines require the following files within a folder on the local server to properly load a Transformers model: +- `model.onnx`: The exported Transformers model in the [ONNX format](https://github.com/onnx/onnx). +- `tokenizer.json`: The [HuggingFace compatible tokenizer configuration](https://huggingface.co/docs/transformers/fast_tokenizers) used with the model. +- `config.json`: The [HuggingFace compatible configuration file](https://huggingface.co/docs/transformers/main_classes/configuration) used with the model. + +Below we describe two possibilities to obtain the required structure. + +#### SparseML Export +This pathway is relevant if you intend to deploy a model created using [SparseML](https://github.com/neuralmagic/sparseml) library. +For more information, refer to the appropriate [transformers integration documentation in SparseML](https://github.com/neuralmagic/sparseml/tree/main/src/sparseml/transformers). + +ONNX models can be exported using the `sparseml.transformers.export_onnx` tool: + +```bash +sparseml.transformers.export_onnx --task question-answering --model_path model_path +``` + +This creates `model.onnx` file, in the directory of your `model_path`(e.g. `/trained_model/model.onnx`). +The `tokenizer.json` and `config.json` are stored under the `model_path` folder as well, so a DeepSparse pipeline ca be directly instantiated by using that folder after export (e.g. `/trained_model/`). + +#### SparseZoo Stub +Alternatively, you can skip the process of the ONNX model export by using Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). The SparseZoo contains pre-sparsified models and SparseZoo stubs enable you to reference any model on the SparseZoo in a convenient and predictable way. +All of DeepSparse's pipelines and APIs can use a SparseZoo stub in place of a local folder. The Deployment APIs examples use SparseZoo stubs to highlight this pathway. + +## Deployment APIs + +DeepSparse provides both a Python Pipeline API and an out-of-the-box model server +that can be used for end-to-end inference in either existing python workflows or as an HTTP endpoint. +Both options provide similar specifications for configurations and support a variety of NLP transformers +tasks including question answering, text classification, sentiment analysis, and token classification. + +### Python Pipelines +Pipelines are the default interface for running inference with the DeepSparse Engine. + +Once a model is obtained, either through `SparseML` training or directly from `SparseZoo`, +`deepsparse.Pipeline` can be used to easily facilitate end to end inference and deployment +of the sparsified transformers model. + +If no model is specified to the `Pipeline` for a given task, the `Pipeline` will automatically +select a pruned and quantized model for the task from the `SparseZoo` that can be used for accelerated +inference. Note that other models in the SparseZoo will have different tradeoffs between speed, size, +and accuracy. + +### HTTP Server +As an alternative to Python API, the DeepSparse Server allows you to serve ONNX models and pipelines in HTTP. +Both configuring and making requests to the server follow the same parameters and schemas as the +Pipelines enabling simple deployment. Once launched, a `/docs` endpoint is created with full +endpoint descriptions and support for making sample requests. + +Example deployments using NLP transformer models are provided below. +For full documentation on deploying sparse transformer models with the DeepSparse Server, see the +[documentation](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/server). + +#### Installation +The DeepSparse Server requirements can be installed by specifying the `server` extra dependency when installing +DeepSparse. + +```bash +pip install deepsparse[server] +``` + +## Deployment Use Cases +The following section includes example usage of the Pipeline and server APIs for various NLP transformers tasks. + +### Question Answering +The question answering tasks accepts a `question` and a `context`. The pipeline will predict an answer +for the `question` as a substring of the `context`. The following examples use a pruned and quantized +question answering BERT model trained on the `SQuAD` dataset downloaded by default from the SparseZoo. + +[List of available SparseZoo Question Answering Models]( +https://sparsezoo.neuralmagic.com/?page=1&domain=nlp&sub_domain=question_answering) + +#### Python Pipeline + +```python +from deepsparse import Pipeline + +qa_pipeline = Pipeline.create(task="question-answering") +inference = qa_pipeline(question="What's my name?", context="My name is Snorlax") + +>> {'score': 0.9947717785835266, 'start': 11, 'end': 18, 'answer': 'Snorlax'} +``` + +#### HTTP Server +Spinning up: +```bash +deepsparse.server \ + task question-answering \ + --model_path "zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni" +``` + +Making a request: +```python +import requests + +url = "http://localhost:5543/predict" # Server's port default to 5543 + +obj = { + "question": "Who is Mark?", + "context": "Mark is batman." +} + +response = requests.post(url, json=obj) +response.text + +>> '{"score":0.9534820914268494,"start":8,"end":14,"answer":"batman"}' +``` + +### Sentiment Analysis +The sentiment analysis task takes in a sentence and classifies its sentiment. The following example +uses a pruned and quantized text sentiment analysis BERT model trained on the `sst2` dataset downloaded +from the SparseZoo. This `sst2` model classifies sentences as positive or negative. + +[List of available SparseZoo Sentiment Analysis Models]( +https://sparsezoo.neuralmagic.com/?domain=nlp&sub_domain=sentiment_analysis) + +#### Python Pipeline +```python +from deepsparse import Pipeline + +sa_pipeline = Pipeline.create(task="sentiment-analysis") + +inference = sa_pipeline("Snorlax loves my Tesla!") + +>> [{'label': 'LABEL_1', 'score': 0.9884248375892639}] # positive sentiment + +inference = sa_pipeline("Snorlax hates pineapple pizza!") + +>> [{'label': 'LABEL_0', 'score': 0.9981569051742554}] # negative sentiment +``` + +#### HTTP Server +Spinning up: +```bash +deepsparse.server \ + task sentiment-analysis \ + --model_path "zoo:nlp/sentiment_analysis/bert-base/pytorch/huggingface/sst2/12layer_pruned80_quant-none-vnni" +``` + +Making a request: +```python +import requests + +url = "http://localhost:5543/predict" # Server's port default to 5543 + +obj = {"sequences": "Snorlax loves my Tesla!"} + +response = requests.post(url, json=obj) +response.text + +>> '{"labels":["LABEL_1"],"scores":[0.9884248375892639]}' +``` + +### Text Classification +The text classification task supports binary, multi class, and regression predictions over +sentence inputs. The following example uses a pruned and quantized text classification +DistilBERT model trained on the `qqp` dataset downloaded from a SparseZoo stub. +The `qqp` dataset takes pairs of questions and predicts if they are a duplicate or not. + +[List of available SparseZoo Text Classification Models]( +https://sparsezoo.neuralmagic.com/?page=1&domain=nlp&sub_domain=text_classification) + +#### Python Pipeline +```python +from deepsparse import Pipeline + +tc_pipeline = Pipeline.create( + task="text-classification", + model_path="zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/qqp/pruned80_quant-none-vnni", +) + +# inference of duplicate question pair +inference = tc_pipeline( + sequences=[ + [ + "Which is the best gaming laptop under 40k?", + "Which is the best gaming laptop under 40,000 rs?", + ] + ] +) + +>> TextClassificationOutput(labels=['duplicate'], scores=[0.9947025775909424]) +``` + +#### HTTP Server +Spinning up: +```bash +deepsparse.server \ + task text-classification \ + --model_path "zoo:nlp/text_classification/distilbert-none/pytorch/huggingface/qqp/pruned80_quant-none-vnni" +``` + +Making a request: +```python +import requests + +url = "http://localhost:5543/predict" # Server's port default to 5543 + +obj = { + "sequences": [ + [ + "Which is the best gaming laptop under 40k?", + "Which is the best gaming laptop under 40,000 rs?", + ] + ] +} + +response = requests.post(url, json=obj) +response.text + +>> '{"labels": ["duplicate"], "scores": [0.9947025775909424]}' +``` + +#### Token Classification Pipeline +The token classification task takes in sequences as inputs and assigns a class to each token. +The following example uses a pruned and quantized token classification NER BERT model +trained on the `CoNLL` dataset downloaded from the SparseZoo. + +[List of available SparseZoo Token Classification Models]( +https://sparsezoo.neuralmagic.com/?page=1&domain=nlp&sub_domain=token_classification) + +#### Python Pipeline +```python +from deepsparse import Pipeline + +# default model is a pruned + quantized NER model trained on the CoNLL dataset +tc_pipeline = Pipeline.create(task="token-classification") +inference = tc_pipeline("Drive from California to Texas!") + +>> [{'entity': 'LABEL_0','word': 'drive', ...}, + {'entity': 'LABEL_0','word': 'from', ...}, + {'entity': 'LABEL_5','word': 'california', ...}, + {'entity': 'LABEL_0','word': 'to', ...}, + {'entity': 'LABEL_5','word': 'texas', ...}, + {'entity': 'LABEL_0','word': '!', ...}] +``` + +#### HTTP Server +Spinning up: +```bash +deepsparse.server \ + task token-classification \ + --model_path "zoo:nlp/token_classification/bert-base/pytorch/huggingface/conll2003/12layer_pruned80_quant-none-vnni" +``` + +Making a request: +```python +import requests + +url = "http://localhost:5543/predict" # Server's port default to 5543 + +obj = {"inputs": "Drive from California to Texas!"} + + +response = requests.post(url, json=obj) +response.text + +>> '{"predictions":[[{"entity":"LABEL_0","score":0.9998655915260315,"index":1,"word":"drive","start":0,"end":5,"is_grouped":false},{"entity":"LABEL_0","score":0.9998604655265808,"index":2,"word":"from","start":6,"end":10,"is_grouped":false},{"entity":"LABEL_5","score":0.9994636178016663,"index":3,"word":"california","start":11,"end":21,"is_grouped":false},{"entity":"LABEL_0","score":0.999838650226593,"index":4,"word":"to","start":22,"end":24,"is_grouped":false},{"entity":"LABEL_5","score":0.9994573593139648,"index":5,"word":"texas","start":25,"end":30,"is_grouped":false},{"entity":"LABEL_0","score":0.9998716711997986,"index":6,"word":"!","start":30,"end":31,"is_grouped":false}]]}' +``` + +## Benchmarking +The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. Want to find out how fast our sparse Hugging Face ONNX models perform inference? +You can quickly do benchmarking tests on your own with a single CLI command! + +You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: + +```bash +deepsparse.benchmark zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni + +>> Original Model Path: zoo:nlp/question_answering/bert-base/pytorch/huggingface/squad/12layer_pruned80_quant-none-vnni +>> Batch Size: 1 +>> Scenario: multistream +>> Throughput (items/sec): 76.3484 +>> Latency Mean (ms/batch): 157.1049 +>> Latency Median (ms/batch): 157.0088 +>> Latency Std (ms/batch): 1.4860 +>> Iterations: 768 +``` + +To learn more about benchmarking, refer to the appropriate documentation. +Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! + +## Tutorials: +For a deeper dive into using transformers within the Neural Magic ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/): +- [Token Classification: Named Entity Recognition](https://neuralmagic.com/use-cases/sparse-named-entity-recognition/) +- [Text Classification: Multi-Class](https://neuralmagic.com/use-cases/sparse-multi-class-text-classification/) +- [Text Classification: Binary](https://neuralmagic.com/use-cases/sparse-binary-text-classification/) +- [Text Classification: Sentiment Analysis](https://neuralmagic.com/use-cases/sparse-sentiment-analysis/) +- [Question Answering](https://neuralmagic.com/use-cases/sparse-question-answering/) + +## Support +For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). diff --git a/src/deepsparse/transformers/__init__.py b/src/deepsparse/transformers/__init__.py index 3e084cece9..b0f7ca22f6 100644 --- a/src/deepsparse/transformers/__init__.py +++ b/src/deepsparse/transformers/__init__.py @@ -22,10 +22,6 @@ import logging as _logging import pkg_resources -from deepsparse.analytics import deepsparse_analytics as _analytics - - -_analytics.send_event("python__transformers__init") _EXPECTED_VERSION = "4.23.1" diff --git a/src/deepsparse/utils/onnx.py b/src/deepsparse/utils/onnx.py index c571e62850..aec78e885a 100644 --- a/src/deepsparse/utils/onnx.py +++ b/src/deepsparse/utils/onnx.py @@ -21,7 +21,6 @@ import numpy import onnx -from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE from deepsparse.utils.extractor import Extractor @@ -36,6 +35,7 @@ sparsezoo_import_error = sparsezoo_err __all__ = [ + "ONNX_TENSOR_TYPE_MAP", "model_to_path", "get_external_inputs", "get_external_outputs", @@ -50,6 +50,23 @@ _LOGGER = logging.getLogger(__name__) +ONNX_TENSOR_TYPE_MAP = { + 1: numpy.float32, + 2: numpy.uint8, + 3: numpy.int8, + 4: numpy.uint16, + 5: numpy.int16, + 6: numpy.int32, + 7: numpy.int64, + 9: numpy.bool_, + 10: numpy.float16, + 11: numpy.float64, + 12: numpy.uint32, + 13: numpy.uint64, + 14: numpy.complex64, + 15: numpy.complex128, +} + def save_onnx(model: Model, model_path: str, external_data_file: str) -> bool: """ @@ -98,9 +115,9 @@ def translate_onnx_type_to_numpy(tensor_type: int): :param tensor_type: Integer representing a type in ONNX spec :return: Corresponding numpy type """ - if tensor_type not in TENSOR_TYPE_TO_NP_TYPE: + if tensor_type not in ONNX_TENSOR_TYPE_MAP: raise Exception("Unknown ONNX tensor type = {}".format(tensor_type)) - return TENSOR_TYPE_TO_NP_TYPE[tensor_type] + return ONNX_TENSOR_TYPE_MAP[tensor_type] def model_to_path(model: Union[str, Model, File]) -> str: diff --git a/src/deepsparse/yolact/README.md b/src/deepsparse/yolact/README.md index 78d77576db..bff000a13d 100644 --- a/src/deepsparse/yolact/README.md +++ b/src/deepsparse/yolact/README.md @@ -1,3 +1,186 @@ -# Instance Segmentation Use Case (YOLACT) +# YOLACT Inference Pipelines +DeepSparse allows accelerated inference, serving, and benchmarking of sparsified YOLACT models [YOLACT] ([original codebase](https://github.com/dbolya/yolact) and [paper](https://arxiv.org/abs/1904.02689)). +This integration allows for leveraging the DeepSparse Engine to run the sparsified YOLACT inference with GPU-class performance directly on the CPU. -[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/image-segmentation-yolact.md) +The DeepSparse Engine is taking advantage of sparsity within neural networks to +reduce compute required as well as accelerate memory-bound workloads. The engine is particularly effective when leveraging sparsification +methods such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and [quantization](https://arxiv.org/abs/1609.07061). +These techniques result in significantly more performant and smaller models with limited to no effect on the baseline metrics. + + +## Getting Started + +Before you start your adventure with the DeepSparse Engine, make sure that your machine is +compatible with our [hardware requirements](https://docs.neuralmagic.com/deepsparse/source/hardware.html). + +### Installation + +```pip install deepsparse``` + +### Model Format +By default, to deploy YOLACT using DeepSparse Engine it is required to supply the model in the ONNX format. +This grants the engine the flexibility to serve any model in a framework-agnostic environment. + +Below we describe two possibilities to obtain the required ONNX model. + +### Exporting the ONNX File From the Contents of a Local Directory +This pathway is relevant if you intend to deploy a model created using the [SparseML](https://github.com/neuralmagic/sparseml) library. +For more information refer to the [appropriate YOLACT integration documentation in SparseML](https://github.com/neuralmagic/sparseml/tree/main/integrations/dbolya-yolact) + +After training your model with `SparseML`, locate the `.pth` file for the model you'd like to export and run the `SparseML` integrated YOLACT ONNX export script below. + +```bash +sparseml.yolact.export_onnx --checkpoint PATH_TO_YOLACT_PTH_CHECKPOINT +``` +This creates `model.onnx` file, in the directory of your `weights` (e.g. `/weights/model.onnx`). +For additional options invoke the command-line callable with `--help` option like `sparseml.yolact.export_onnx --help` + +#### SparseZoo Stub +Alternatively, you can skip the process of the ONNX model export by using Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). The SparseZoo contains pre-sparsified models and SparseZoo stubs enable you to reference any model on the SparseZoo in a convenient and predictable way. +All of DeepSparse's pipelines and APIs can use a SparseZoo stub in place of a local folder. The Deployment APIs examples use SparseZoo stubs to highlight this pathway. +## Deployment APIs +DeepSparse provides both a Python Pipeline API and an out-of-the-box model server +that can be used for end-to-end inference in either existing Python workflows or as an HTTP endpoint. +Both options provide similar specifications for configurations and support annotation serving for all +YOLACT models. + +### Python Pipelines +Pipelines are the default interface for running inference with the DeepSparse Engine. + +Once a model is obtained, either through SparseML training or directly from SparseZoo, `deepsparse.Pipeline` can be used to easily facilitate end-to-end inference and deployment of the sparsified neural networks. + +If no model is specified to the Pipeline for a given task, the Pipeline will automatically select a pruned and quantized model for the task from the SparseZoo that can be used for accelerated inference. Note that other models in the SparseZoo will have different tradeoffs between speed, size, and accuracy. + +### DeepSparse Server +As an alternative to Python API, the DeepSparse Server allows you to serve ONNX models and pipelines in HTTP. +Both configuring and making requests to the server follow the same parameters and schemas as the +Pipelines enabling simple deployment. Once launched, a `/docs` endpoint is created with full +endpoint descriptions and support for making sample requests. + +An example of starting and requesting a DeepSparse Server for YOLACT is given below. + +#### Installation +The Deepsparse Server requirements can be installed by specifying the `server` extra dependency when installing +DeepSparse. + +```bash +pip install deepsparse[server] +``` + +## Deployment Example +The following example uses pipelines to run a pruned and quantized YOLACT model for inference, downloaded by default from the SparseZoo. +As input the pipeline ingests an image (or a list of images) and returns for each image the detection boxes, classification labels, probability scores and segmentation masks. + +[List of the YOLACT SparseZoo Models]( +https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=segmentation&page=1) + +If you don't have an image ready, pull a sample image down with + +```bash +wget -O thailand.jpg https://raw.githubusercontent.com/neuralmagic/deepsparse/main/src/deepsparse/yolact/sample_images/thailand.jpg +``` + +```python +from deepsparse.pipeline import Pipeline + +model_stub = "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" +images = ["thailand.jpg"] + +yolact_pipeline = Pipeline.create( + task="yolact", + model_path=model_stub, + class_names="coco", +) + +predictions = yolact_pipeline(images=images, confidence_threshold=0.2,nms_threshold = 0.5) +# predictions has attributes `boxes`, `classes`, `masks` and `scores` +predictions.classes[0] +>> ['elephant', 'elephant', 'person', ...] + +``` + +#### Annotate CLI +You can also use the annotate command to have the engine save an annotated photo on disk. +```bash +deepsparse.instance_segmentation.annotate --source thailand.jpg #Try --source 0 to annotate your live webcam feed +``` + +Running the above command will create an `annotation-results` folder and save the annotated image inside. + +

+original annotated +

+

+Image annotated with 82.8% sparse and quantized YOLACT +

+ +If a `--model_filepath` arg isn't provided, then `zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none` will be used by default. + + +#### HTTP Server +Spinning up: +```bash +deepsparse.server \ + task yolact \ + --model_path "zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none" +``` + +Making a request: +```python +import requests +import json + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['thailand.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) # dictionary of annotation results +boxes, classes, masks, scores = annotations["boxes"], annotations["classes"], annotations["masks"], annotations["scores"] +``` + +### Benchmarking +The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. Want to find out how fast our sparse YOLOv5 ONNX models perform inference? +You can quickly do benchmarking tests on your own with a single CLI command! + +You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: + +```bash +deepsparse.benchmark \ + zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none \ + --scenario sync +``` + +Output: + +```bash +2022-07-05 10:47:09 deepsparse.benchmark.benchmark_model INFO Thread pinning to cores enabled +DeepSparse Engine, Copyright 2021-present / Neuralmagic, Inc. version: 0.13.0.20220628 (51925ae2) (release) (optimized) (system=avx2, binary=avx2) +2022-07-05 10:47:23 deepsparse.benchmark.benchmark_model INFO num_streams default value chosen of 12. This requires tuning and may be sub-optimal +2022-07-05 10:47:23 deepsparse.benchmark.benchmark_model INFO deepsparse.engine.Engine: + onnx_file_path: /home/damian/.cache/sparsezoo/099e086b-1e84-450c-ab3b-90038c591554/model.onnx + batch_size: 1 + num_cores: 24 + num_streams: 0 + scheduler: Scheduler.default + cpu_avx_type: avx2 + cpu_vnni: False +2022-07-05 10:47:23 deepsparse.utils.onnx INFO Generating input 'input', type = float32, shape = [1, 3, 550, 550] +2022-07-05 10:47:23 deepsparse.benchmark.benchmark_model INFO Starting 'singlestream' performance measurements for 10 seconds +Original Model Path: zoo:cv/segmentation/yolact-darknet53/pytorch/dbolya/coco/pruned82_quant-none +Batch Size: 1 +Scenario: sync +Throughput (items/sec): 26.8650 +Latency Mean (ms/batch): 37.2062 +Latency Median (ms/batch): 37.2043 +Latency Std (ms/batch): 0.0741 +Iterations: 269 +``` + +To learn more about benchmarking, refer to the appropriate documentation. +Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! + +## Tutorials: +For a deeper dive into using YOLACT within the Neural Magic ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/use-cases/#computervision). + +## Support +For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). diff --git a/src/deepsparse/yolact/__init__.py b/src/deepsparse/yolact/__init__.py index bee4474d74..86aaaa5de4 100644 --- a/src/deepsparse/yolact/__init__.py +++ b/src/deepsparse/yolact/__init__.py @@ -18,10 +18,6 @@ import warnings from collections import namedtuple -from deepsparse.analytics import deepsparse_analytics as _analytics - - -_analytics.send_event("python__yolact__init") _LOGGER = _logging.getLogger(__name__) _Dependency = namedtuple("_Dependency", ["name", "version", "necessary", "import_name"]) diff --git a/src/deepsparse/yolo/README.md b/src/deepsparse/yolo/README.md index 4746086859..5d37fbd944 100644 --- a/src/deepsparse/yolo/README.md +++ b/src/deepsparse/yolo/README.md @@ -1,3 +1,169 @@ -# Object Detection Use Case (YOLOv5) +# YOLOv5 Inference Pipelines -[Checkout DeepSparse Use Cases for usage details](../../../docs/use-cases/cv/object-detection-yolov5.md) + +DeepSparse allows accelerated inference, serving, and benchmarking of sparsified [Ultralytics YOLOv5](https://github.com/ultralytics/yolo) models. +This integration allows for leveraging the DeepSparse Engine to run the sparsified YOLOv5 inference with GPU-class performance directly on the CPU. + +The DeepSparse Engine is taking advantage of sparsity within neural networks to +reduce compute required as well as accelerate memory-bound workloads. The engine is particularly effective when leveraging sparsification +methods such as [pruning](https://neuralmagic.com/blog/pruning-overview/) and [quantization](https://arxiv.org/abs/1609.07061). +These techniques result in significantly more performant and smaller models with limited to no effect on the baseline metrics. + +This integration currently supports the original YOLOv5 and updated V6.1 architectures. + +## Getting Started + +Before you start your adventure with the DeepSparse Engine, make sure that your machine is +compatible with our [hardware requirements](https://docs.neuralmagic.com/deepsparse/source/hardware.html). + +### Installation + +```pip install deepsparse[yolo]``` + +### Model Format +By default, to deploy YOLOv5 using DeepSparse Engine it is required to supply the model in the ONNX format. +This grants the engine the flexibility to serve any model in a framework-agnostic environment. + +Below we describe two possibilities to obtain the required ONNX model. + +### Exporting the ONNX File From the Contents of a Local Directory +This pathway is relevant if you intend to deploy a model created using the [SparseML](https://github.com/neuralmagic/sparseml) library. +For more information refer to the [appropriate YOLOv5 integration documentation in SparseML](https://github.com/neuralmagic/sparseml/tree/main/src/sparseml/yolov5). + +After training your model with `SparseML`, locate the `.pt` file for the model you'd like to export and run the `SparseML` integrated YOLOv5 ONNX export script below. + +```bash +sparseml.yolov5.export_onnx \ + --weights path/to/your/model \ + --dynamic #Allows for dynamic input shape +``` +This creates `model.onnx` file, in the directory of your `weights` (e.g. `runs/train/weights/model.onnx`). + +#### SparseZoo Stub +Alternatively, you can skip the process of the ONNX model export by using Neural Magic's [SparseZoo](https://sparsezoo.neuralmagic.com/). The SparseZoo contains pre-sparsified models and SparseZoo stubs enable you to reference any model on the SparseZoo in a convenient and predictable way. +All of DeepSparse's pipelines and APIs can use a SparseZoo stub in place of a local folder. The Deployment APIs examples use SparseZoo stubs to highlight this pathway. +## Deployment APIs + +DeepSparse provides both a Python Pipeline API and an out-of-the-box model server +that can be used for end-to-end inference in either existing Python workflows or as an HTTP endpoint. +Both options provide similar specifications for configurations and support annotation serving for all +YOLOv5 models. + +### Python Pipelines +Pipelines are the default interface for running inference with the DeepSparse Engine. + +Once a model is obtained, either through SparseML training or directly from SparseZoo, `deepsparse.Pipeline` can be used to easily facilitate end-to-end inference and deployment of the sparsified neural networks. + +If no model is specified to the Pipeline for a given task, the Pipeline will automatically select a pruned and quantized model for the task from the SparseZoo that can be used for accelerated inference. Note that other models in the SparseZoo will have different tradeoffs between speed, size, and accuracy. + +### DeepSparse Server +As an alternative to Python API, the DeepSparse Server allows you to serve ONNX models and pipelines in HTTP. +Both configuring and making requests to the server follow the same parameters and schemas as the +Pipelines enabling simple deployment. Once launched, a `/docs` endpoint is created with full +endpoint descriptions and support for making sample requests. + +An example of starting and requesting a DeepSparse Server for YOLOv5 is given below. + +#### Installation +The Deepsparse Server requirements can be installed by specifying the `server` extra dependency when installing +DeepSparse. + +```bash +pip install deepsparse[yolo,server] +``` + +## Deployment Example +The following example uses pipelines to run a pruned and quantized YOLOv5l model for inference, downloaded by default from the SparseZoo. As input the pipeline ingests a list of images and returns for each image the detection boxes in numeric form. + +[List of the YOLOv5 SparseZoo Models]( +https://sparsezoo.neuralmagic.com/?domain=cv&sub_domain=detection&page=1) + +If you don't have an image ready, pull a sample image down with + +```bash +wget -O basilica.jpg https://raw.githubusercontent.com/neuralmagic/deepsparse/main/src/deepsparse/yolo/sample_images/basilica.jpg +``` + +```python +from deepsparse import Pipeline + +model_stub = "zoo:cv/detection/yolov5-l/pytorch/ultralytics/coco/pruned-aggressive_98" +images = ["basilica.jpg"] + +yolo_pipeline = Pipeline.create( + task="yolo", + model_path=model_stub, +) + +pipeline_outputs = yolo_pipeline(images=images, iou_thres=0.6, conf_thres=0.001) +``` + +#### Annotate CLI +You can also use the annotate command to have the engine save an annotated photo on disk. +```bash +deepsparse.object_detection.annotate --source basilica.jpg #Try --source 0 to annotate your live webcam feed +``` + +Running the above command will create an `annotation-results` folder and save the annotated image inside. + +

+original annotated +

+

+Image annotated with 96% sparse YOLOv5s +

+ +If a `--model_filepath` arg isn't provided, then `zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned-aggressive_96` will be used by default. + + +#### HTTP Server +Spinning up: +```bash +deepsparse.server \ + task yolo \ + --model_path "zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94" +``` + +Making a request: +```python +import requests +import json + +url = 'http://0.0.0.0:5543/predict/from_files' +path = ['basilica.jpg'] # list of images for inference +files = [('request', open(img, 'rb')) for img in path] +resp = requests.post(url=url, files=files) +annotations = json.loads(resp.text) # dictionary of annotation results +bounding_boxes = annotations["boxes"] +labels = annotations["labels"] +``` + +### Benchmarking +The mission of Neural Magic is to enable GPU-class inference performance on commodity CPUs. Want to find out how fast our sparse YOLOv5 ONNX models perform inference? +You can quickly do benchmarking tests on your own with a single CLI command! + +You only need to provide the model path of a SparseZoo ONNX model or your own local ONNX model to get started: + +```bash +deepsparse.benchmark \ + zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 \ + --scenario sync + +>> Original Model Path: zoo:cv/detection/yolov5-s/pytorch/ultralytics/coco/pruned_quant-aggressive_94 +>> Batch Size: 1 +>> Scenario: sync +>> Throughput (items/sec): 74.0355 +>> Latency Mean (ms/batch): 13.4924 +>> Latency Median (ms/batch): 13.4177 +>> Latency Std (ms/batch): 0.2166 +>> Iterations: 741 +``` + +To learn more about benchmarking, refer to the appropriate documentation. +Also, check out our [Benchmarking tutorial](https://github.com/neuralmagic/deepsparse/tree/main/src/deepsparse/benchmark)! + +## Tutorials: +For a deeper dive into using YOLOv5 within the Neural Magic ecosystem, refer to the detailed tutorials on our [website](https://neuralmagic.com/use-cases/#computervision). + +## Support +For Neural Magic Support, sign up or log in to our [Deep Sparse Community Slack](https://join.slack.com/t/discuss-neuralmagic/shared_invite/zt-q1a1cnvo-YBoICSIw3L1dmQpjBeDurQ). Bugs, feature requests, or additional questions can also be posted to our [GitHub Issue Queue](https://github.com/neuralmagic/deepsparse/issues). diff --git a/src/deepsparse/yolo/__init__.py b/src/deepsparse/yolo/__init__.py index 28a2af36d0..135b18a839 100644 --- a/src/deepsparse/yolo/__init__.py +++ b/src/deepsparse/yolo/__init__.py @@ -14,11 +14,6 @@ # flake8: noqa -from deepsparse.analytics import deepsparse_analytics as _analytics - from .annotate import * from .pipelines import * from .schemas import * - - -_analytics.send_event("python__yolov5__init") diff --git a/src/deepsparse/yolo/pipelines.py b/src/deepsparse/yolo/pipelines.py index 935fc9a1d4..c3866433f3 100644 --- a/src/deepsparse/yolo/pipelines.py +++ b/src/deepsparse/yolo/pipelines.py @@ -163,12 +163,6 @@ class properties into an inference ready onnx file to be compiled by the model_path = model_to_path(self.model_path) if self._image_size is None: self._image_size = get_onnx_expected_image_shape(onnx.load(model_path)) - if self._image_size == (0, 0): - raise ValueError( - "The model does not have a static image size shape. " - "Specify the expected image size by passing the" - "`image_size` argument to the pipeline." - ) else: # override model input shape to given image size if isinstance(self._image_size, int): diff --git a/src/deepsparse/yolo/utils/utils.py b/src/deepsparse/yolo/utils/utils.py index baa4c18721..07e7b87ec2 100644 --- a/src/deepsparse/yolo/utils/utils.py +++ b/src/deepsparse/yolo/utils/utils.py @@ -359,8 +359,6 @@ def modify_yolo_onnx_input_shape( model_input = model.graph.input[0] initial_x, initial_y = get_onnx_expected_image_shape(model) - if initial_x == initial_y == 0: - initial_x, initial_y = image_shape if not (isinstance(initial_x, int) and isinstance(initial_y, int)): return model_path, None # model graph does not have static integer input shape diff --git a/src/deepsparse/yolov8/__init__.py b/src/deepsparse/yolov8/__init__.py index a55c36903d..9efc49cd88 100644 --- a/src/deepsparse/yolov8/__init__.py +++ b/src/deepsparse/yolov8/__init__.py @@ -14,13 +14,8 @@ # flake8: noqa -from deepsparse.analytics import deepsparse_analytics as _analytics - from .annotate import * from .pipelines import * from .schemas import * from .utils import * from .validation import * - - -_analytics.send_event("python__yolov8__init") diff --git a/src/deepsparse/yolov8/utils/validation/helpers.py b/src/deepsparse/yolov8/utils/validation/helpers.py index a951ae8f5d..0db35c462d 100644 --- a/src/deepsparse/yolov8/utils/validation/helpers.py +++ b/src/deepsparse/yolov8/utils/validation/helpers.py @@ -12,53 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. import argparse -import glob import os import warnings from typing import List, Optional, Union -import yaml - import torch from deepsparse.yolo import YOLOOutput as YOLODetOutput from deepsparse.yolov8.schemas import YOLOSegOutput -from ultralytics.yolo.data.utils import ROOT - - -__all__ = ["data_from_dataset_path", "schema_to_tensor", "check_coco128_segmentation"] - - -def data_from_dataset_path(data: str, dataset_path: str) -> str: - """ - Given a dataset name, fetch the yaml config for the dataset - from the Ultralytics dataset repo, overwrite its 'path' - attribute (dataset root dir) to point to the `dataset_path` - and finally save it to the current working directory. - This allows to create load data yaml config files that point - to the arbitrary directories on the disk. - - :param data: name of the dataset (e.g. "coco.yaml") - :param dataset_path: path to the dataset directory - :return: a path to the new yaml config file - (saved in the current working directory) - """ - ultralytics_dataset_path = glob.glob(os.path.join(ROOT, "**", data), recursive=True) - if len(ultralytics_dataset_path) != 1: - raise ValueError( - "Expected to find a single path to the " - f"dataset yaml file: {data}, but found {ultralytics_dataset_path}" - ) - ultralytics_dataset_path = ultralytics_dataset_path[0] - with open(ultralytics_dataset_path, "r") as f: - yaml_config = yaml.safe_load(f) - yaml_config["path"] = dataset_path - yaml_save_path = os.path.join(os.getcwd(), data) - # save the new dataset yaml file - with open(yaml_save_path, "w") as outfile: - yaml.dump(yaml_config, outfile, default_flow_style=False) - return yaml_save_path +__all__ = ["schema_to_tensor", "check_coco128_segmentation"] def schema_to_tensor( diff --git a/src/deepsparse/yolov8/validation.py b/src/deepsparse/yolov8/validation.py index 7412b4975c..cc8fd1fbaa 100644 --- a/src/deepsparse/yolov8/validation.py +++ b/src/deepsparse/yolov8/validation.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional - import click from deepsparse import Pipeline @@ -22,7 +20,6 @@ DeepSparseDetectionValidator, DeepSparseSegmentationValidator, check_coco128_segmentation, - data_from_dataset_path, ) from ultralytics.yolo.cfg import get_cfg from ultralytics.yolo.utils import DEFAULT_CFG @@ -66,6 +63,16 @@ show_default=True, help="Validation batch size", ) +@click.option( + "--stride", + type=int, + default=32, + show_default=True, + help="YOLOv8 can handle arbitrary sized images as long as " + "both sides are a multiple of 32. This is because the " + "maximum stride of the backbone is 32 and it is a fully " + "convolutional network.", +) @click.option( "--engine-type", default=DEEPSPARSE_ENGINE, @@ -88,21 +95,15 @@ show_default=True, help="A subtask of YOLOv8 to run. Default is `detection`.", ) -@click.option( - "--dataset-path", - type=str, - default=None, - help="Path to override default dataset path.", -) def main( dataset_yaml: str, model_path: str, batch_size: int, num_cores: int, engine_type: str, + stride: int, device: str, subtask: str, - dataset_path: Optional[str], ): pipeline = Pipeline.create( @@ -123,8 +124,6 @@ def main( f"Dataset yaml {dataset_yaml} is not supported. " f"Supported dataset configs are {SUPPORTED_DATASET_CONFIGS})" ) - if dataset_path is not None: - args.data = data_from_dataset_path(args.data, dataset_path) classes = {label: class_ for (label, class_) in enumerate(COCO_CLASSES)} if subtask == "detection":

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