LLM Router Deployment Guide#
Solution Blueprints are provided as Helm Charts. The recommended approach to deploy them is to pipe the output of helm template to kubectl apply -f -.
We don’t recommend helm install, which by default uses a Secret to keep track of the related resources.
This does not work well with Enterprise clusters that often have limitations on the kinds of resources that
regular users are allowed to create.
This blueprint supports AMD Instinct (default) and AMD Radeon platforms. Unless otherwise specified, the commands below cover the default Instinct deployment. For deployment with Radeon, see:
Multi-platform Support#
The chart ships defaults for two platforms, selected with --set global.platform=<platform>: instinct (GPU, the default) and radeon (GPU). Each sets a matching AIM image and resource profile; inspect them with helm show values . --jsonpath '{.primary.platformDefaults}' and helm show values . --jsonpath '{.secondary.platformDefaults}'.
Helm note: Built and tested on Helm 3.17 or higher. On Helm v4, if the piped
kubectl applyis rejected, runhelm pull oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router --untarfirst and template the local./aimsb-llm-routerdirectory instead.
AMD Instinct (GPU, default)#
To deploy the blueprint, run the following command:
name="my-deployment"
namespace="my-namespace"
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set deployDemonstrationLLMs=true \
| kubectl apply -f - -n $namespace
AMD Radeon (GPU)#
To deploy the blueprint, run the following command:
name="my-deployment"
namespace="my-namespace"
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set deployDemonstrationLLMs=true \
--set global.platform=radeon \
| kubectl apply -f - -n $namespace
Deployment Configuration#
Set deployment variables:
name="my-deployment"
namespace="my-namespace"
Option 1: Demo Deployment with Self-Hosted LLMs#
This option deploys two demonstration LLM pods (requires at least 2 GPUs available on the cluster).
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set deployDemonstrationLLMs=true \
| kubectl apply -f - -n $namespace
Option 2: Deployment with Existing External LLMs#
This approach is suitable if you have deployed LLMs and want to reuse them. No matter where they are deployed, you can reuse them by following the instructions below. This is the main operational mode. You have several configuration approaches:
Understanding Routing Rules#
The routing system uses a configurable classification approach to direct requests to the most appropriate LLM. You can choose between two classification strategies in the UI:
Option 1: Task-Based Routing#
When Task-Based routing is selected, the system analyzes the user’s query to understand the intended task type. Common task classes include:
Code Generation- Writing new codeSummarization- Condensing long textsReasoning- Logical problem-solvingCreative Writing- Generating stories or marketing copyAnd many others…
Option 2: Complexity-Based Routing#
When Complexity-Based routing is selected, the system evaluates the complexity level of the request:
Easy- Simple, straightforward tasksMiddle- Moderately complex requestsHard- Complex reasoning or long-form generationTrivial- Very simple queries
Classifier Interpretation#
The classifier supports two classification approaches, selected via embedding.enabled:
Embedding-based Classification#
(values: embedding.enabled: true, default)
Uses the intfloat/multilingual-e5-large-instruct model via a vLLM-based embedding server (aim-base). Each class has a description field — the classifier computes semantic similarity between the incoming prompt and class descriptions, routing to the closest match. This approach is faster, deterministic, and recommended for production use.
LLM-based Classification#
(values: embedding.enabled: false)
Uses a configured LLM backend to classify the prompt. The LLM receives the conversation and the list of class names, and returns a structured JSON response with the chosen class. This approach requires no additional embedding service but adds an extra LLM inference call to every request.
Both approaches fall back to Unknown when the query doesn’t clearly match any defined class.
Approach A: Configure via values.yaml File#
Create or modify values.yaml:
models:
- name: primary
base_url: http://primary
api_key: "" # optional
api_key_secret_ref: {} # optional, e.g. { name: llm-api-keys, key: primary }
model_name: "" # optional
- name: secondary
base_url: http://secondary
api_key: "" # optional
api_key_secret_ref: {} # optional, e.g. { name: llm-api-keys, key: secondary }
model_name: "" # optional
routing:
rules:
task_router:
classifier_path: /classify
classes:
Code Generation:
backend: secondary
description: "Any request to create, write, generate, implement or provide code in any programming language, algorithm implementations, scripts, functions, or code examples."
Summarization:
backend: primary
description: "Tasks related to summarizing text, condensing articles, conversations, documents, providing key points or brief overviews."
Unknown:
backend: primary
description: "Requests that are completely unclear, off-topic, spam, or do not match ANY of the defined categories at all."
complexity_router:
classifier_path: /classify
classes:
Hard:
backend: secondary
description: "High-effort tasks: algorithms, complex math, deep multi-step reasoning, code writing, advanced technical topics."
Middle:
backend: secondary
description: "Medium effort tasks: short explanations, standard how-to questions, everyday problem solving, moderate knowledge recall."
Easy:
backend: primary
description: "Very low-effort interactions: greetings, simple yes/no questions, basic facts, casual chat."
Unknown:
backend: primary
description: "Requests that are completely unclear, off-topic, spam, or do not match ANY of the defined categories at all."
classifier:
llmBackend: secondary # used only when embedding.enabled: false
embedding:
enabled: true # true = embedding approach, false = LLM approach
Then deploy with:
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
-f values.yaml \
| kubectl apply -f - -n $namespace
Approach B: Configure via Command Line Parameters#
Set all parameters directly (example command):
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set embedding.enabled=false \
--set models[0].name=primary \
--set models[0].base_url=http://167.99.61.150:8000 \
--set models[1].name=secondary \
--set models[1].base_url=http://167.99.61.150:8000 \
--set models[2].name=third \
--set models[2].base_url=http://167.99.61.150:8000 \
--set models[3].name=four \
--set models[3].base_url=http://167.99.61.150:8000 \
--set routing.rules.task_router.classes.Code\ Generation=secondary \
--set routing.rules.task_router.classes.Code\ Review=secondary \
--set routing.rules.task_router.classes.Refactoring=secondary \
--set routing.rules.task_router.classes.Summarization=primary \
--set routing.rules.task_router.classes.Documentation=primary \
--set routing.rules.task_router.classes.Reasoning=third \
--set routing.rules.task_router.classes.Logical\ Analysis=third \
--set routing.rules.task_router.classes.Planning=third \
--set routing.rules.task_router.classes.Decision\ Making=third \
--set routing.rules.task_router.classes.Creative\ Writing=four \
--set routing.rules.task_router.classes.Brainstorming=four \
--set routing.rules.task_router.classes.Marketing\ Text=four \
--set routing.rules.task_router.classes.Unknown=primary \
--set routing.rules.complexity_router.classes.Hard=secondary \
--set routing.rules.complexity_router.classes.Middle=secondary \
--set routing.rules.complexity_router.classes.Easy=primary \
--set routing.rules.complexity_router.classes.Trivial=third \
--set routing.rules.complexity_router.classes.Unknown=primary \
| kubectl apply -f - -n $namespace
By default
embedding.enabled=true(embedding-based classification). Setembedding.enabled=falseto use LLM-based classification instead. When using LLM-based classification,classifier.llmBackendmust reference a valid model from themodelslist.
For each model, you can provide either api_key directly or api_key_secret_ref to read the key from a Kubernetes Secret.
If both are set, api_key is used.
apiKeySecretRef is also accepted as an alias for api_key_secret_ref.
Important notes about parameter base_url:
base_urlis only the base address of the model service, without any API path suffix Correct examples:http://167.99.61.150:8000http://llama3-70b-instructhttp://my-model.default.svc.cluster.localhttp://vllm-backend.llm-router.svc.cluster.local:8000
Do NOT add
/v1,/v1/chat/completions,/api,/openaietc. at the end The router automatically appends the correct path:$base_url/v1/chat/completionsThe service at this address must provide an OpenAI-compatible API (it should accept POST requests at
/v1/chat/completions)About the port
If the model service listens on the default http port 80 → you can omit the port entirely Example:
http://my-model-serviceIf it uses a non-standard port (most often 8000 for vLLM, llama.cpp, Ollama with custom port, etc.) → you must specify the port Example:
http://my-model-service:8000The most common case inside Kubernetes: when models are running in the same cluster → use the Kubernetes service name (without external IP)
Approach C: Configure with API Keys and Model Names (Optional)#
If your LLMs require authentication, or you need to specify specific models (example command):
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set embedding.enabled=false \
--set models[0].name=primary \
--set models[0].base_url=https://router.huggingface.co \
--set models[0].api_key=exampleapikey \
--set models[0].model_name=meta-llama/Llama-3.1-8B-Instruct:novita \
--set models[1].name=secondary \
--set models[1].base_url=https://router.huggingface.co \
--set models[1].api_key=exampleapikey \
--set models[1].model_name=meta-llama/Llama-3.1-8B-Instruct:novita \
--set routing.rules.task_router.classes.Code\ Generation=secondary \
--set routing.rules.task_router.classes.Summarization=primary \
--set routing.rules.task_router.classes.Unknown=primary \
--set routing.rules.complexity_router.classes.Hard=secondary \
--set routing.rules.complexity_router.classes.Easy=primary \
--set routing.rules.complexity_router.classes.Unknown=primary \
| kubectl apply -f - -n $namespace
By default
embedding.enabled=true(embedding-based classification). Setembedding.enabled=falseto use LLM-based classification instead. When using LLM-based classification,classifier.llmBackendmust reference a valid model from themodelslist.
Default AIM images and GPU compatibility#
When deployDemonstrationLLMs=true, the chart deploys two default AIMs:
primary.image=amdenterpriseai/aim-meta-llama-llama-3-1-8b-instruct:0.11.1secondary.image=amdenterpriseai/aim-meta-llama-llama-3-3-70b-instruct:0.11.1
On newer GPUs, these images may not be the best match and can fail to start or run sub-optimally.
To choose newer AIMs or deploy different LLMs, override primary.image and/or secondary.image to compatible images. See the catalog of available AIMs for options.
Example:
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set deployDemonstrationLLMs=true \
--set primary.image=amdenterpriseai/aim-meta-llama-llama-3-1-8b-instruct:<NEWER_TAG> \
--set secondary.image=amdenterpriseai/aim-meta-llama-llama-3-3-70b-instruct:<NEWER_TAG> \
| kubectl apply -f - -n $namespace
Connecting to the Service#
After deployment, check pod status:
kubectl get pods -n $namespace
Wait until all pods show ready status 1/1.
Option 1: Port Forwarding#
To access the web interface, set up port forwarding:
kubectl port-forward services/$name-aimsb-llm-router-ui 8080:8008 -n $namespace
The UI will then be available at http://localhost:8080.
Option 2: HTTPRoute (Gateway Access)#
If your cluster has a Gateway API compatible gateway (e.g., Kubernetes Gateway, Istio, etc.), you can enable HTTPRoute creation to route traffic through the gateway.
Prerequisites:
A Gateway named
httpsmust exist in theenvoy-gateway-systemnamespace (or configure a different gateway).The Gateway must be properly configured with listeners.
Enabling HTTPRoute:
Use --set http_route.enabled=true in the helm template command to enable HTTPRoute creation:
name="my-deployment"
namespace="my-namespace"
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set http_route.enabled=true \
| kubectl apply -f - -n $namespace
Obtaining the URL:
The URL to access the blueprint via HTTPRoute is formed by the chart name, release name, and the gateway’s hostname. Use this command to produce the URL by querying the hostname from the cluster:
echo "https://aimsb-llm-router-$name$(kubectl get gtw -A -o jsonpath='{.items[*].spec.listeners[?(@.name=="https")].hostname}' | tr -d \*)/"
Clean Up#
When you are finished, remove the deployed resources:
helm template $name oci://registry-1.docker.io/amdenterpriseai/aimsb-llm-router \
--set deployDemonstrationLLMs=true \
| kubectl delete -f - -n $namespace
Important Notes#
All model names referenced in
routingandclassifiersections must exist in themodelslistThe
api_keyandmodel_nameparameters are optionalWhen using existing LLMs, ensure your LLM endpoints are accessible from the cluster
For demo deployment with self-hosted LLMs, ensure at least 2 GPUs are available on the cluster
Regardless of how you configure the model list, you can specify any number of models.