- Blog
- 07.09.2024
- Data Fundamentals, Product
Step-by-step guide to deploying a RAG Model in Snowpark Container Service (SPCS) with Matillion Data Productivity Cloud

Deploying large language models at scale presents various complexities. Features like Snowpark Container Services (SPCS) aims to mitigate this issue by providing a robust platform for managing containerized applications.
This article guides you through the process of deploying a Retrieval-Augmented Generation (RAG) model using SPCS, taking advantage of 2x Nvidia A10G GPUs within the GPU_NV_M instance family.
We will cover setting up essential Snowflake objects such as roles, compute pools, warehouses, and databases, as well as walk through creating and uploading a Docker image to Snowflake.
Additionally, we will explore the creation of custom User-Defined Functions (UDFs) to facilitate seamless interaction with the RAG model, thereby extending Snowflake's functionalities.
Finally, you'll learn how to integrate this RAG model into your data pipelines within the Data Productivity Cloud, enhancing both operational efficiency and data-driven decision-making.
Setting Up Snowpark Container Service using SQL
To get started, we must establish the necessary Snowflake objects, including roles, compute pools, warehouses, and databases. The "llama-2-7b-chat.Q6_K.gguf" quantized llama2 model, available for download from Hugging Face, will be used.
Note this will require the ACCOUNTADMIN role.
USE ROLE ACCOUNTADMIN;
CREATE ROLE test_role;
GRANT ROLE test_role TO USER <<Your Username>>;
-- Grant necessary privileges --
GRANT BIND SERVICE ENDPOINT ON ACCOUNT TO ROLE test_role;
-- Create compute pool --
CREATE COMPUTE POOL GPU_NV_M_pool
MIN_NODES = 1
MAX_NODES = 1
INSTANCE_FAMILY = GPU_NV_M;
-- Create warehouse --
CREATE OR REPLACE WAREHOUSE spcs_wh WITH
WAREHOUSE_SIZE='X-SMALL'
AUTO_SUSPEND = 180
AUTO_RESUME = true
INITIALLY_SUSPENDED = false;
-- Grant privileges --
GRANT USAGE ON COMPUTE POOL GPU_NV_M_pool TO ROLE test_role;
GRANT MONITOR ON COMPUTE POOL GPU_NV_M_pool TO ROLE test_role;
GRANT ALL ON WAREHOUSE spcs_wh TO ROLE test_role;
-- Create database --
CREATE DATABASE tutorial_db;
GRANT OWNERSHIP ON DATABASE tutorial_db TO ROLE test_role;
-- Create External Access Integration (to download Embedding model)
CREATE OR REPLACE NETWORK RULE HF_EGRESS_RULE
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('huggingface.co','cdn-lfs-us-1.huggingface.co','cdn-lfs.huggingface.co');
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION RAG_ACCESS_INTEGRATION
ALLOWED_NETWORK_RULES = (HF_EGRESS_RULE)
ENABLED = true;
USE ROLE test_role;
USE DATABASE tutorial_db;
USE WAREHOUSE spcs_wh;
CREATE SCHEMA IF NOT EXISTS data_schema;
CREATE IMAGE REPOSITORY IF NOT EXISTS llama2_rag_repository;
CREATE STAGE IF NOT EXISTS tutorial_stage
DIRECTORY = ( ENABLE = true );
Prepare to build the Docker Image
To prepare your environment: open your terminal or command prompt and navigate to the directory where all the necessary files are located.
Ensure that you have
- The Dockerfile for Llama2
- requirements.txt
- llama2.yaml
- llama2_service_gpu.py
- The folder containing your locally saved FAISS index vector embedding
Additionally, make sure that the Docker daemon is active in your environment to facilitate the image building process.
The Dockerfile for Llama2
# Use the NVIDIA CUDA base image FROM nvcr.io/nvidia/pytorch:23.06-py3 # Set the working directory in the container WORKDIR /app # Set environment variable for cuBLAS ENV LLAMA_CUBLAS=1 # Set environment variable for llama -cpp-python installation ENV CMAKE_ARGS="-DLLAMA_CUBLAS=on" # Copy the necessary files into the container COPY llama2_service_gpu.py /app/llama2_service_gpu.py COPY llama-2-7b-chat.Q6_K.gguf /app/llama-2-7b-chat.Q6_K.gguf COPY templates/ /app/templates/ COPY faiss_AiDoc/ /app/faiss_AiDoc/ COPY requirements.txt /app/requirements.txt # Install Python dependencies RUN pip3 install --upgrade pip && pip3 install -r requirements.txt # Expose port 8080 outside of the container (if needed) EXPOSE 8080 # Run llama2_service_gpu.py when the container launches CMD ["python3", "./llama2_service_gpu.py"]
requirements.txt
accelerate langchain langchain -community Flask llama -cpp-python transformers InstructorEmbedding sentence_transformers huggingface faiss-gpu
llama2.yaml
spec:
containers:
- name: llama
image: /tutorial_db/data_schema/llama2_rag _repository /llama2_gpu:llama2
env:
SYNC_DIR: /tmp/sync
SERVER_PORT: 8080
volumeMounts:
- name: sync
mountPath: /tmp/sync
- name: shm
mountPath: /dev/shm
resources:
requests:
nvidia.com/gpu: 3
cpu: 1
limits:
nvidia.com/gpu: 3
endpoints:
- name: llamaendpoint
port: 8080
public: true
volumes:
- name: sync
source: local
# size: local
- name: shm
source: local
lama2_service_gpu.py
# Standard library imports
#!/usr/bin/env python
import os
import sys
import logging
# Third-party imports
from flask import Flask, jsonify, request, make_response, render_template
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
from langchain.chains import LLMChain
from langchain_community.llms import LlamaCpp
from llama_cpp import Llama
from langchain.prompts import PromptTemplate
from langchain_community.embeddings import HuggingFaceInstructEmbeddings
from langchain_community.vectorstores import FAISS
# Set up environment variables
SERVICE_HOST = os.getenv('SERVER_HOST', '0.0.0.0')
SERVICE_PORT = os.getenv('SERVER_PORT', 8080)
# Set up logging
def get_logger(logger_name):
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
handler.setFormatter(logging.Formatter('%(name)s [%(asctime)s] [%(levelname)s] %(message)s'))
logger.addHandler(handler)
return logger
# Configure the logger globally
logger = get_logger('llama-service')
# Initialize Flask app
app = Flask(__name__)
@app.get("/healthcheck")
def readiness_probe():
# Added check to see if model and DB are loaded
if model is not None and db is not None:
return "Model and database are ready!"
return "Waiting for model and database initialization..."
# Initialize and load the Llama model and FAISS database
model = None
db = None
def load_model():
global model, db
if model is None or db is None:
logger.info("Initializing the Llama model and FAISS database...") # Log initialization
# Initialize the Llama model
try:
logger.info("test1")
template = """Question: {question} \n\nAnswer:"""
prompt = PromptTemplate(template=template, input_variables=["question"])
# Callbacks support token-wise streaming
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])
n_gpu_layers = 40
n_batch = 512
model_path = "llama-2-7b-chat.Q6_K.gguf"
logger.info("test2")
logger.info("Initializing LlamaCpp...")
llm = LlamaCpp(
model_path=model_path,
n_gpu_layers=n_gpu_layers,
n_ctx=4095,
n_batch=n_batch,
top_p=1,
callback_manager=callback_manager,
temperature = 0.4,
max_tokens = 2095,
n_parts=1,
verbose=True
)
model = LLMChain(prompt=prompt, llm=llm)
logger.info("Llama model initialized successfully.") # Log successful initialization
except Exception as e:
logger.error(f"Error during Llama model initialization: {e}")
logger.error(e, exc_info=True) # Log the full exception traceback
sys.exit(1) # Exit the script
# Initialize the FAISS database
try:
hf_embedding = HuggingFaceInstructEmbeddings()
db_path = "faiss_AiDoc/"
db = FAISS.load_local(db_path, embeddings=hf_embedding)
logger.info("FAISS database loaded successfully.")
except Exception as e:
logger.error("Error during FAISS database initialization:")
logger.error(e, exc_info=True) # Log the full exception traceback
sys.exit(1) # Exit the script
else:
logger.info("Llama model and FAISS database already initialized.") # Log if already initialized
return model, db
@app.post("/llama")
def llama():
message = request.json
logger.debug(f'Received request: {message}')
try:
input_rows = message.get('data', [])
if not input_rows:
logger.info('Received empty message or no data key present')
return make_response(jsonify({"data": []}), 200)
if not all(isinstance(row, list) and len(row) >= 2 for row in input_rows):
logger.error('Invalid input format')
return make_response(jsonify({"error": "Invalid input format"}), 400)
logger.info(f'Received {len(input_rows)} rows')
output_rows = []
for row in input_rows:
try:
response = get_llama_response(row[1])
output_rows.append([row[0], response])
except Exception as e:
logger.error(f'Error processing row {row[0]}: {e}')
output_rows.append([row[0], "Error processing input"])
logger.info(f'Produced {len(output_rows)} rows')
response = make_response(jsonify({"data": output_rows}), 200)
response.headers['Content-type'] = 'application/json'
logger.debug(f'Sending response: {response.get_json()}')
return response
except Exception as e:
logger.error(f'Error in /llama endpoint: {e}')
return make_response(jsonify({"error": "Internal Server Error"}), 500)
@app.route("/ui", methods=["GET", "POST"])
def ui():
if request.method == "POST":
input_text = request.form.get("input")
return render_template("basic_ui.html", llama_input=input_text, llama_reponse=get_llama_response(input_text))
return render_template("basic_ui.html")
def prompt_response(input):
global model, db
query = input
search = db.similarity_search(query, k=5)
template = '''Context: {context}
Based on the Context, please answer the following question:
Question: {question}
Provide an answer based on the context only, without using general knowledge. The answer should be derived directly from the context provided.
Please correct any grammatical errors for improved readability.
If the context does not contain relevant information to answer the question, state that the answer is not available in the given context.
Please include the source title of the information as a reference of how you arrive at your answer. '''
prompt = PromptTemplate(input_variables=["context", "question"], template=template)
final_prompt = prompt.format(question=query, context=search)
response = model.run(final_prompt)
return response
def process_input(input):
processed_answer = prompt_response(input)
return processed_answer
def get_llama_response(input):
reply = process_input(input)
return f'{reply}'
if __name__ == '__main__':
try:
model, db = load_model()
app.run(host=SERVICE_HOST, port=int(SERVICE_PORT), debug=False)
except OSError as e:
if e.errno == 98:
logger.error("Address already in use. Please ensure the port is free or specify a different port.")
else:
raise
The folder containing your locally saved FAISS index vector embedding
This article assumes you have saved your FAISS index vector embedding to the path "faiss_AiDoc//". This subdirectory contains the precomputed vector embeddings of your RAG source material.
Build and upload the Docker Image
Execute the following command to build the Docker image. This command instructs Docker to build an image using the specified Dockerfile and tag it with a name and version.
docker build --rm --platform linux/amd64 -t llama2_gpu:llama2
If you prefer, you can replace `llama2_gpu:llama2` with the name and tag you want to give to your own Docker image. Note the name is used below.
Once the image is successfully built, tag it with the Snowflake container repository URL using the following command:
docker tag llama2_gpu:llama2 <<your Snowflake container repository URL>>
Authenticate Docker with the Snowflake container repository by running the following command. The command will prompt you to enter your Snowflake username and password.
docker login myorg-myacct.registry.snowflakecomputing.com -u <<Username>>
Push the tagged image to your Snowflake container repository using the following command:
docker push <<your Snowflake container repository URL>> /llama2_gpu:llama2
This command uploads your Docker image to the specified repository in your Snowflake account.
Creating the Service
Snowpark Container Services make it easy to handle containerized apps in Snowflake. After preparing your application and uploading its image to your Snowflake repository, you can initiate it using services.
To establish a service for this application, you can either
- upload the ‘llama2.yaml’ file to the staging area and run Option 1 SQL Code Snippet, or
- run Option 2 SQL Code Snippet directly
Option 1 SQL Code Snippet
Using SQL Code Snippet with a YAML File that you upload to your staging area.
CREATE SERVICE llama2_rag_service
IN COMPUTE POOL GPU_NV_M_pool FROM @oae_llama2_stage_gpu
SPEC='llama2.yaml'
MIN_INSTANCES=1
MAX_INSTANCES=1
EXTERNAL_ACCESS_INTEGRATIONS = ( RAG_ACCESS_INTEGRATION );
Option 2: Using SQL Code Snippet Directly
CREATE SERVICE llama2_rag_service
IN COMPUTE POOL GPU_NV_M_pool
FROM SPECIFICATION $$
spec:
containers:
- name: llama
image: /tutorial_db/data_schema/ llama2_rag _repository /llama2_gpu:llama2
env:
SYNC_DIR: /tmp/sync
SERVER_PORT: 8080
volumeMounts:
- name: sync
mountPath: /tmp/sync
- name: shm
mountPath: /dev/shm
resources:
requests:
nvidia.com/gpu: 2
cpu: 1
limits:
nvidia.com/gpu: 2
endpoints:
- name: llamaendpoint
port: 8080
public: true
volumes:
- name: sync
source: local
- name: shm
source: local
$$
MIN_INSTANCES=1
MAX_INSTANCES=1
EXTERNAL_ACCESS_INTEGRATIONS = ( RAG_ACCESS_INTEGRATION );
Creating and using a User Defined Function (UDF)
Snowflake offers the flexibility to create custom User-Defined Functions (UDFs) tailored to specific requirements for RAG models. The UDF empowers users to ask questions and seamlessly obtain model-generated responses within Snowflake.
Snowflake UDFs extend the platform's functionalities beyond its built-in features. The llama2_rag function shown below serves as a link between Snowflake and the RAG model, facilitating the retrieval of insightful responses.
CREATE OR REPLACE FUNCTION llama2_rag (text varchar) RETURNS varchar SERVICE= llama2_rag_service ENDPOINT=llamaendpoint AS '/llama';
Using the Snowflake UDF to perform a RAG query
This is simply a SQL interface, for example:
SELECT llama2_rag('<<your question>>');
The llama2_rag UDF harnesses the ‘llama2_ rag_service’ and ‘llamaendpoint’ endpoint to seamlessly communicate with the RAG model. The SQL statement receives model-generated responses directly within the Snowflake environment.
Integrating SPCS UDFs with a Data Productivity Cloud Pipeline
Integrating the Retrieval Augmented Generation (RAG) Model into your Data Productivity Cloud ecosystem demands a seamless interaction with the Snowpark Container Services.
Ensuring that your Snowflake setup is correctly configured is paramount for successful integration. This includes aligning your role, default warehouse, default database, and default schema with the Snowflake object setup designated for SPCS usage. Such configuration guarantees that your data productivity cloud pipeline possesses the requisite permissions to access and interact with the deployed model.
Here’s a step-by-step example of how you might structure a pipeline to access and utilize the new model within your data productivity cloud framework.
The Table Input "Employee_Policy_Questions" contains a list of domain-specific questions - in this case concerning company policies - held in a database table.
The Calculator Component "Faiss_LLAMA2" calls the previously created UDF, passing in the questions and receiving the answers back from the FAISS data and the LLAMA2 model. They analyze the input text and generate insightful responses.
The output is stored in a new column named Output_llama2, which is saved to a newly created output table by the final Rewrite Table component.
Summary
In conclusion, deploying a RAG model in Snowpark Container Service involves several key steps to ensure seamless integration and functionality.
- First, setting up Snowflake objects such as roles, compute pools, warehouses, and databases is essential, ensuring compatibility with the SPCS configuration.
- Building and uploading the Docker image containing the model and its dependencies
- Followed by creating a Service in Snowflake to initiate the containerised application
- Leveraging Snowflake's User Defined Functions (UDFs) for convenient querying within the Snowflake environment.
This has been an example of how to access a RAG model deployed in Snowpark Container Services using the data productivity cloud.
Enjoyed reading part two? Don't miss out of part one here!
If you would like to try this technology on your own data, with your own RAG model, follow this link for a free trial of the Matillion Data Productivity Cloud
Alex Egaji
Data Scientist
Featured Resources
Big Data London 2025: Key Takeaways and Maia Highlights
There’s no doubt about it – Maia dominated at Big Data London. Over the two-day event, word spread quickly about Maia’s ...
Learn more BlogSay Hello to Ask Matillion, Your New AI Assistant for Product Answers
We’re excited to introduce a powerful new addition to the Matillion experience: Ask Matillion.
Learn more BlogRethinking Data Pipeline Pricing
Discover how value-based data pipeline pricing improves ROI, controls costs, and scales data processing without billing surprises.
Learn more
Share: