Deploy Hugging Face Models on Serverless GPU
Tags: huggingface, serverless, gpu, machinelearning
Published: 2023-05-28 10:58:31

**Hugging Face** is a platform and community that focuses on making artificial intelligence and data science more accessible. It aims to make AI knowledge and resources more widely available by promoting open source contributions. As AI technologies become more widely used, it’s important for advancements to be made in the field. Hugging Face provides a space for AI and data science professionals to connect and share their work.

Hugging Face provides state of the art machine learning models for different tasks. It has a vast number of pre-trained models in categories such as:

- Computer Vision (Image Segmentation, Image Classification, Image Generation etc)
- Natural Language Processing (Text Classification, Summarization, Generation, Translation etc)
- Audio (Speech Recognition, Text to Speech etc)
- and much [more](https://huggingface.co/models)

Hugging Face's models are easy to use. For example, to do a text classification task, you can use the [transformers](https://huggingface.co/docs/transformers/index) library (which is part of Hugging Face), to load a pre-trained model and then use it to classify a text.

An example for text classification using `transformers`:
```python
from transformers import pipeline

sentiment_analysis = pipeline(
    "sentiment-analysis", model="siebert/sentiment-roberta-large-english"
)

print(sentiment_analysis("I like Transformers"))
# [{'label': 'POSITIVE', 'score': 0.9987214207649231}]

print(sentiment_analysis("I hate React"))
# [{'label': 'NEGATIVE', 'score': 0.9993581175804138}]
``` 

We can see how easy it is to do a text classification task in just 3 lines of code using the transformers library.

Similarly, in the same way we find many models on Hugging Face with their corresponding model card. 

One of the challenges of using Hugging Face models is that they can be computationally expensive to deploy as most of them require GPU's. This is because they are often large and complex, and require a lot of computing power to run. GPUs can be very expensive. For example, the hourly rate of an NVIDIA A10G instance on **AWS** is $1.30 for a 24 GB memory. This equates to about $2496 for a period of one month. This show how you can easily _burn money on AWS_ 😅, for an ML model which hardly gets 1000-2000 requests in a month.

![An Image saying that there is a solution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yp95iy92452vjm9rrfxc.png)

# Enter Serverless GPU's

Serverless GPUs are a type of cloud computing service that provides access to powerful GPUs on demand. This means that you only pay for the time that you use the GPUs, which can save you a significant amount of money if you only need to use them occasionally.

- **Pay-as-You-Go Pricing**: Any Serverless architectures follow a pay-as-you-go pricing model, so you only pay for the actual resource consumption during model inference.
- **Ease of use**: Serverless GPUs are very easy to use. You don't need to worry about managing or maintaining the hardware, which can save you a lot of time and hassle.
- **Scalability**: Serverless GPUs can be scaled up or down as needed, which makes them ideal for applications that have fluctuating workloads.

![A meme showing using servers vs serverless](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hhgoogrtdeylezl67hsi.png)

There are a number of serverless GPU providers out there, such as [Banana](https://www.banana.dev/), [Replicate](https://replicate.com/), [Beam](https://www.beam.cloud/), [Modal](https://modal.com/) and many more.

I would recommend checking out all the websites, before deploying your application. All of the ones which I have mentioned do have a free limit.

Going to the previous calculation of the costs, for a 16 CPU model, with a 32 GB RAM on an A10G instance the price on Beam was just $0.00155419/second (Almost all of the providers do have an identical pricing). So let's say your **API** gets 1500 requests in a month and lets say an average inference time to be 1 min (60 seconds). So the cost incurred are: 0.00155419 * 60 * 1500 = $140. Almost an 18x reduction😎 in cost as compared to using AWS.

For this tutorial, I am going to use [Beam](https://www.beam.cloud/) to deploy [dolly-v2-7b](https://www.databricks.com/blog/2023/03/24/hello-dolly-democratizing-magic-chatgpt-open-models.html), an open source large language model from [Databricks](https://www.databricks.com/), which responds similar to [ChatGPT](https://openai.com/blog/chatgpt).

Of Course feel free to use any ML model with your choice of serverless GPU provider.

# Deployment

## Prerequisites

- An account on [Beam](https://www.beam.cloud/login)
- An API Key from [Dashboard](https://www.beam.cloud/dashboard/settings/api-keys)
- Install [Beam CLI](https://docs.beam.cloud/getting-started/quickstart) by running:
```bash
curl https://raw.githubusercontent.com/slai-labs/get-beam/main/get-beam.sh -sSfL | sh
```
- Configure Beam by entering
```bash
beam configure
```
- Install Beam SDK
```bash
pip install beam-sdk
```

Now you’re ready to start using Beam to deploy your ML models.

As I said I will be deploying [dolly-v2-7b](https://huggingface.co/databricks/dolly-v2-7b) from Databricks. The code to run it is provided on Hugging Face. So copying it into a file named as `run.py`
```python
import torch
from instruct_pipeline import InstructionTextGenerationPipeline
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("databricks/dolly-v2-7b", padding_side="left")
model = AutoModelForCausalLM.from_pretrained(
    "databricks/dolly-v2-7b", device_map="auto", torch_dtype=torch.bfloat16
)

generate_text = InstructionTextGenerationPipeline(model=model, tokenizer=tokenizer)
```

There is one major problem with this code. In the instantiation of `AutoModelForCausalLM` and `AutoTokenizer` a cache directory is not used. So this will lead to download of the model files every time it gets invoked. Of Course this will not happen if you run the code on your own device because the cache directory by default will be somewhere inside your hard disk. But in the serverless world, everything is stateless, the model files downloaded once will not persist in the consecutive runs.

Digging through beam docs, we see an option of [Shared Volumes](https://docs.beam.cloud/data/shared-volumes), which means now model files can be persisted between consecutive runs and the it can be mounted in the same way as a normal hard disk whenever the model runs.

So for now lets pass a cache_dir as "./mpt_weights". The modified code is:

```python
import torch
from instruct_pipeline import InstructionTextGenerationPipeline
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(
    "databricks/dolly-v2-7b", padding_side="left", cache_dir=cache_path
)
model = AutoModelForCausalLM.from_pretrained(
    "databricks/dolly-v2-7b",
    device_map="auto",
    torch_dtype=torch.bfloat16,
    cache_dir=cache_path,
)

generate_text = InstructionTextGenerationPipeline(model=model, tokenizer=tokenizer)
```
To define the configuration of the system where our ML model runs, we need to write a [Beam app definition](https://docs.beam.cloud/getting-started/quickstart).

I have mostly copied this code from Beam Docs. Creating a new file named `app.py` and adding code as:
```python
import beam

app = beam.App(
    name="mpt-7b-chat",
    cpu=16,
    memory="32Gi",
    gpu="A10G",
    python_packages=[
        "accelerate>=0.16.0,<1",
        "transformers[torch]>=4.28.1,<5",
        "torch>=1.13.1,<2",
    ],
)

app.Trigger.RestAPI(
    inputs={"prompt": beam.Types.String()},
    outputs={"response": beam.Types.String()},
    handler="run.py:generate_text",
    keep_warm_seconds=60
)

app.Mount.SharedVolume(name="mpt_weights", path="./mpt_weights")
```
The above code creates a Beam application that uses 16 CPUs, 32GB of memory, and an A10G GPU. It also defines all the pip packages that would be required during the model run. The application is triggered by a REST API call and generates text in response to a prompt. The application is also loaded with models when it starts.

Now let's modify `run.py` one last time before we deploy it. We need to return the response from the `generate_text` function which we will create in `run.py` so that it can be returned back to the REST API.

```python
import torch
from instruct_pipeline import InstructionTextGenerationPipeline
from transformers import AutoModelForCausalLM, AutoTokenizer

cache_path = "./mpt_weights"

def generate_text(**inputs):
    tokenizer = AutoTokenizer.from_pretrained(
        "databricks/dolly-v2-3b", padding_side="left", cache_dir=cache_path
    )
    model = AutoModelForCausalLM.from_pretrained(
        "databricks/dolly-v2-3b",
        device_map="auto",
        torch_dtype=torch.bfloat16,
        cache_dir=cache_path,
    )
    generate_text = InstructionTextGenerationPipeline(model=model, tokenizer=tokenizer)
    prompt = inputs["prompt"]
    response = generate_text(prompt)

    return {"response": response[0]["generated_text"]}
```
Please don't get intimidated by this code. I haven't modified much from the one on [Hugging Face](https://huggingface.co/databricks/dolly-v2-7b#:~:text=model%20and%20tokenizer%3A-,import%20torch,-from%20instruct_pipeline%20import). I would highly suggest you first refer to respective [docs](https://docs.beam.cloud/introduction) before deploying anything on cloud that a random dude suggests on the internet.

Also one more thing we need `instruct_pipeline.py` for dolly-v2-3b as mentioned on Hugging Face. So copy paste the code from [here](https://huggingface.co/databricks/dolly-v2-3b/blob/main/instruct_pipeline.py).

So now we have 3 files, namely `app.py`, `run.py` and `instruct_pipeline.py` (may not be the same in your case).

Now deploy your application by entering:
```bash
beam deploy app.py
```

This will deploy your ML model as serverless REST API which you can use from your Frontend. Obviosly you can deploy this model as an asynchronous [webhook](https://docs.beam.cloud/triggers/webhook) instead of a REST API if your model inference takes a long time.

You can get the `CURL` command to call your app from the Beam app dashboard. Testing the app I deployed, 

![An Image showing ML inference on Serverless GPU](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4oqn877dxi3w1jzf7rf.png)

I got a response in under 8 seconds which is not bad for an Open source version of ChatGPT :)

# Cons

Everything seems good till now with serverless but let's weigh in on the cons.

- **Cold starts**: When a serverless GPU is not in use, it is shut down. This means that the first time you use it, it will need to be booted up, which can take a few seconds to minutes depending on your model size. This can be a problem if you are running a real-time application.
- **Cost**: Serverless GPUs can be more expensive than traditional GPU-based solutions, especially if you are running a long-running application that almost stays active throughout the day.

# Conclusion

In conclusion, we have seen how to deploy Hugging Face models on serverless GPUs. This can be a great way to get the performance benefits of GPUs without having to worry about the underlying hardware, when not in use.

In case if you still have any questions regarding this post or want to discuss something with me feel free to connect on [LinkedIn](https://www.linkedin.com/in/dhanushreddy29/) or [Twitter](https://twitter.com/dhanushreddy291).

> If you run an organization and want me to write for you, please connect with me on my Socials 🙃