Okay, welcome back! Because you recognize you’re going to be deploying this model through Docker in Lambda, that dictates how your inference pipeline needs to be structured.
It is advisable construct a “handler”. What’s that, exactly? It’s only a function that accepts the JSON object that’s passed to the Lambda, and it returns whatever your model’s results are, again in a JSON payload. So, every part your inference pipeline goes to do must be called inside this function.
Within the case of my project, I’ve got an entire codebase of feature engineering functions: mountains of stuff involving semantic embeddings, a bunch of aggregations, es, and more. I’ve consolidated them right into a FeatureEngineering
class, which has a bunch of personal methods but only one public one, feature_eng
. So ranging from the JSON that’s being passed to the model, that method can run all of the steps required to get the info from “raw” to “features”. I like establishing this fashion since it abstracts away a variety of complexity from the handler function itself. I can literally just call:
fe = FeatureEngineering(input=json_object)
processed_features = fe.feature_eng()
And I’m off to the races, my features come out clean and able to go.
Be advised: I actually have written exhaustive unit tests on all of the inner guts of this class because while it’s neat to put in writing it this fashion, I still should be extremely conscious of any changes that may occur under the hood. Write your unit tests! For those who make one small change, it’s possible you’ll not have the option to right away let you know’ve broken something within the pipeline until it’s already causing problems.
The second half is the inference work, and it is a separate class in my case. I’ve gone for a really similar approach, which just takes in a number of arguments.
ps = PredictionStage(features=processed_features)
predictions = ps.predict(
feature_file="feature_set.json",
model_file="classifier",
)
The category initialization accepts the results of the feature engineering class’s method, in order that handshake is clearly defined. Then the prediction method takes two items: the feature set (a JSON file listing all of the feature names) and the model object, in my case a CatBoost classifier I’ve already trained and saved. I’m using the native CatBoost save method, but whatever you employ and whatever model algorithm you employ is advantageous. The purpose is that this method abstracts away a bunch of underlying stuff, and neatly returns the predictions
object, which is what my Lambda goes to present you when it runs.
So, to recap, my “handler” function is actually just this:
def lambda_handler(json_object, _context):fe = FeatureEngineering(input=json_object)
processed_features = fe.feature_eng()
ps = PredictionStage(features=processed_features)
predictions = ps.predict(
feature_file="feature_set.json",
model_file="classifier",
)
return predictions.to_dict("records")
Nothing more to it! It is advisable to add some controls for malformed inputs, in order that in case your Lambda gets an empty JSON, or a listing, or another weird stuff it’s ready, but that’s not required. Do ensure your output is in JSON or similar format, nonetheless (here I’m giving back a dict).
That is all great, we’ve a Poetry project with a totally defined environment and all of the dependencies, in addition to the power to load the modules we create, etc. Good things. But now we want to translate that right into a Docker image that we will placed on AWS.
Here I’m showing you a skeleton of the dockerfile for this example. First, we’re pulling from AWS to get the proper base image for Lambda. Next, we want to establish the file structure that will probably be used contained in the Docker image. This will or might not be exactly like what you’ve got in your Poetry project — mine shouldn’t be, because I’ve got a bunch of additional junk here and there that isn’t crucial for the prod inference pipeline, including my training code. I just must put the inference stuff on this image, that’s all.
The start of the dockerfile
FROM public.ecr.aws/lambda/python:3.9ARG YOUR_ENV
ENV NLTK_DATA=/tmp
ENV HF_HOME=/tmp
On this project, anything you copy over goes to live in a /tmp
folder, so if you may have packages in your project which can be going to attempt to save data at any point, you’ll want to direct them to the proper place.
You furthermore may must ensure that Poetry gets installed right in your Docker image- that’s what’s going to make all of your rigorously curated dependencies work right. Here I’m setting the version and telling pip
to put in Poetry before we go any further.
ENV YOUR_ENV=${YOUR_ENV}
POETRY_VERSION=1.7.1
ENV SKIP_HACK=trueRUN pip install "poetry==$POETRY_VERSION"
The following issue is ensuring all of the files and folders your project uses locally get added to this recent image accurately — Docker copy will irritatingly flatten directories sometimes, so should you get this built and begin seeing “module not found” issues, check to ensure that isn’t happening to you. Hint: add RUN ls -R
to the dockerfile once it’s all copied to see what the directory is looking like. You’ll have the option to view those logs in Docker and it would reveal any issues.
Also, ensure you copy every part you wish! That features the Lambda file, your Poetry files, your feature list file, and your model. All of that is going to be needed unless you store these elsewhere, like on S3, and make the Lambda download them on the fly. (That’s a wonderfully reasonable strategy for developing something like this, but not what we’re doing today.)
WORKDIR ${LAMBDA_TASK_ROOT}COPY /poetry.lock ${LAMBDA_TASK_ROOT}
COPY /pyproject.toml ${LAMBDA_TASK_ROOT}
COPY /new_package/lambda_dir/lambda_function.py ${LAMBDA_TASK_ROOT}
COPY /new_package/preprocessing ${LAMBDA_TASK_ROOT}/new_package/preprocessing
COPY /new_package/tools ${LAMBDA_TASK_ROOT}/new_package/tools
COPY /new_package/modeling/feature_set.json ${LAMBDA_TASK_ROOT}/new_package
COPY /data/models/classifier ${LAMBDA_TASK_ROOT}/new_package
We’re almost done! The final thing it is best to do is definitely install your Poetry environment after which arrange your handler to run. There are a few vital flags here, including --no-dev
, which tells Poetry not so as to add any developer tools you may have in your environment, perhaps like pytest or black.
The top of the dockerfile
RUN poetry config virtualenvs.create false
RUN poetry install --no-devCMD [ "lambda_function.lambda_handler" ]
That’s it, you’ve got your dockerfile! Now it’s time to construct it.
- Be sure that Docker is installed and running in your computer. This will take a second but it surely won’t be too difficult.
- Go to the directory where your dockerfile is, which needs to be the the highest level of your project, and run
docker construct .
Let Docker do its thing after which when it’s accomplished the construct, it’ll stop returning messages. You’ll be able to see within the Docker application console if it’s built successfully. - Return to the terminal and run
docker image ls
and also you’ll see the brand new image you’ve just built, and it’ll have an ID number attached. - From the terminal once more, run
docker run -p 9000:8080 IMAGE ID NUMBER
together with your ID number from step 3 filled in. Now your Docker image will begin to run! - Open a brand new terminal (Docker is attached to your old window, just leave it there), and you’ll be able to pass something to your Lambda, now running via Docker. I personally prefer to put my inputs right into a JSON file, reminiscent of
lambda_cases.json
, and run them like so:
curl -d @lambda_cases.json http://localhost:9000/2015-03-31/functions/function/invocations
If the result on the terminal is the model’s predictions, you then’re able to rock. If not, take a look at the errors and see what is likely to be amiss. Odds are, you’ll must debug a little bit and work out some kinks before that is all running easily, but that’s all a part of the method.
The following stage will depend so much in your organization’s setup, and I’m not a devops expert, so I’ll must be a little bit bit vague. Our system uses the AWS Elastic Container Registry (ECR) to store the built Docker image and Lambda accesses it from there.
If you find yourself fully satisfied with the Docker image from the previous step, you’ll need to construct yet one more time, using the format below. The primary flag indicates the platform you’re using for Lambda. (Put a pin in that, it’s going to come back up again later.) The item after the -t flag is the trail to where your AWS ECR images go- fill in your correct account number, region, and project name.
docker construct . --platform=linux/arm64 -t accountnumber.dkr.ecr.us-east-1.amazonaws.com/your_lambda_project:latest
After this, it is best to authenticate to an Amazon ECR registry in your terminal, probably using the command aws ecr get-login-password
and using the suitable flags.
Finally, you’ll be able to push your recent Docker image as much as ECR:
docker push accountnumber.dkr.ecr.us-east-1.amazonaws.com/your_lambda_project:latest
For those who’ve authenticated accurately, this could only take a moment.
There’s yet one more step before you’re able to go, and that’s establishing the Lambda within the AWS UI. Go log in to your AWS account, and find the “Lambda” product.
Pop open the lefthand menu, and find “Functions”.
That is where you’ll go to seek out your specific project. If you may have not arrange a Lambda yet, hit “Create Function” and follow the instructions to create a brand new function based in your container image.
For those who’ve already created a function, go find that one. From there, all you’ll want to do is hit “Deploy Recent Image”. No matter whether it’s an entire recent function or simply a brand new image, ensure you choose the platform that matches what you probably did in your Docker construct! (Keep in mind that pin?)
The last task, and the explanation I’ve carried on explaining as much as this stage, is to check your image within the actual Lambda environment. This could turn up bugs you didn’t encounter in your local tests! Flip to the Test tab and create a brand new test by inputting a JSON body that reflects what your model goes to be seeing in production. Run the test, and ensure your model does what is meant.
If it really works, you then did it! You’ve deployed your model. Congratulations!
There are various possible hiccups that will show up here, nonetheless. But don’t panic, if you may have an error! There are answers.
- In case your Lambda runs out of memory, go to the Configurations tab and increase the memory.
- If the image didn’t work since it’s too large (10GB is the max), return to the Docker constructing stage and take a look at to chop down the scale of the contents. Don’t package up extremely large files if the model can do without them. At worst, it’s possible you’ll need to avoid wasting your model to S3 and have the function load it.
- If you may have trouble navigating AWS, you’re not the primary. Seek the advice of together with your IT or Devops team to get help. Don’t make a mistake that may cost your organization numerous money!
- If you may have one other issue not mentioned, please post a comment and I’ll do my best to advise.
Good luck, pleased modeling!