Repository: kubernetes-sigs/gateway-api-inference-extension

Issue:
Conformance: Add test for `x-gateway-destination-endpoint-served`
recently the EPP protocol was enhanced with additional required field.
Gateways should report back using metadata field which endpoint served the request. 
More information here:
https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/docs/proposals/004-endpoint-picker-protocol#destination-endpoint-served

in order to pass the conformance test, the Gateways need to implement it (from what I know in GKE it's already implemented).
cc: @danehans @keithmattix @Xunzhuo @delavet 

Hints from issue comments:
@zetxqx: /assign

Solution patch:
diff --git a/.github/workflows/crd-validation.yml b/.github/workflows/crd-validation.yml
new file mode 100644
index 0000000..f43e4b7
--- /dev/null
+++ b/.github/workflows/crd-validation.yml
@@ -0,0 +1,51 @@
+name: CRD Validation
+
+on:
+  pull_request:
+    types: [opened, edited, synchronize, reopened]
+
+permissions:
+  contents: read
+
+jobs:
+  crd-validation:
+    name: CRD Validation Check
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          persist-credentials: false
+          fetch-depth: 0
+
+      - name: Set up Go
+        uses: actions/setup-go@v6
+        with:
+          go-version: "1.25"
+          cache: true
+
+      - name: Install crdify
+        run: |
+          go install sigs.k8s.io/crdify@latest
+
+      - name: Run CRD Validation Check
+        run: |
+          git fetch origin ${{ github.base_ref }}:upstream_base
+          BASE_SHA=$(git rev-parse upstream_base)
+
+          FAILED=0
+          for crd in config/crd/bases/*.yaml; do
+            if ! crdify "git://${BASE_SHA}?path=$crd" "git://HEAD?path=$crd"; then
+              echo "❌ Incompatible change detected in $crd"
+              ((FAILED++))
+            else
+              echo "✅ $crd is valid"
+            fi
+          done
+
+          if [ "$FAILED" -gt 0 ]; then
+            echo "::error::Validation failed! Found $FAILED incompatible CRD change(s)."
+            exit 1
+          fi
+
+          echo "All CRDs are compatible."
diff --git a/benchmarking/prefix-cache-aware/high-cache-values.yaml b/benchmarking/prefix-cache-aware/high-cache-values.yaml
index c385b73..da84940 100644
--- a/benchmarking/prefix-cache-aware/high-cache-values.yaml
+++ b/benchmarking/prefix-cache-aware/high-cache-values.yaml
@@ -23,6 +23,12 @@ logLevel: INFO
 # NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/dataset.json.
 gcsPath: ""
 
+# An S3 bucket path that points to the dataset file.
+# The file will be copied from this path to the local file system
+# at /dataset/s3-dataset.json for use during the run.
+# NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/s3-dataset.json.
+s3Path: ""
+
 # hfToken optionally creates a secret with the specified token.
 # Can be set using helm install --set hftoken=<token>
 hfToken: ""
diff --git a/benchmarking/prefix-cache-aware/low-cache-values.yaml b/benchmarking/prefix-cache-aware/low-cache-values.yaml
index f7b960c..ac44735 100644
--- a/benchmarking/prefix-cache-aware/low-cache-values.yaml
+++ b/benchmarking/prefix-cache-aware/low-cache-values.yaml
@@ -23,6 +23,12 @@ logLevel: INFO
 # NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/dataset.json.
 gcsPath: ""
 
+# An S3 bucket path that points to the dataset file.
+# The file will be copied from this path to the local file system
+# at /dataset/s3-dataset.json for use during the run.
+# NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/s3-dataset.json.
+s3Path: ""
+
 # hfToken optionally creates a secret with the specified token.
 # Can be set using helm install --set hftoken=<token>
 hfToken: ""
diff --git a/benchmarking/single-workload/decode-heavy-values.yaml b/benchmarking/single-workload/decode-heavy-values.yaml
new file mode 100644
index 0000000..a5811e0
--- /dev/null
+++ b/benchmarking/single-workload/decode-heavy-values.yaml
@@ -0,0 +1,76 @@
+# Decode Heavy Configuration
+job:
+  image:
+    repository: quay.io/inference-perf/inference-perf
+    tag: "0.2.0" # Defaults to .Chart.AppVersion
+  serviceAccountName: ""
+  nodeSelector: {}
+  # Example resources:
+  # resources:
+  #   requests:
+  #     cpu: "1"
+  #     memory: "4Gi"
+  #   limits:
+  #     cpu: "2"
+  #     memory: "8Gi"
+  resources: {}
+
+logLevel: INFO
+
+# A GCS bucket path that points to the dataset file.
+# The file will be copied from this path to the local file system
+# at /dataset/dataset.json for use during the run.
+# NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/dataset.json.
+gcsPath: ""
+
+# An S3 bucket path that points to the dataset file.
+# The file will be copied from this path to the local file system
+# at /dataset/s3-dataset.json for use during the run.
+# NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/s3-dataset.json.
+s3Path: ""
+
+# hfToken optionally creates a secret with the specified token.
+# Can be set using helm install --set hftoken=<token>
+hfToken: ""
+
+config:
+  load:
+    type: constant
+    interval: 15
+    stages:
+    - rate: 200
+      duration: 60
+    - rate: 210
+      duration: 60
+    - rate: 220
+      duration: 60
+    worker_max_concurrency: 1000
+  api:
+    type: completion
+    streaming: true
+  server:
+    type: vllm
+    model_name: meta-llama/Llama-3.1-8B-Instruct
+    base_url: http://0.0.0.0:8000
+    ignore_eos: true
+  tokenizer:
+    pretrained_model_name_or_path: meta-llama/Llama-3.1-8B-Instruct
+  data:
+    type: infinity_instruct
+    path: ""
+    input_distribution:
+      max: 1024
+    output_distribution:
+      max: 1024
+  metrics:
+    type: prometheus
+    prometheus:
+      google_managed: true
+  report:
+    request_lifecycle:
+      summary: true
+      per_stage: true
+      per_request: true
+    prometheus:
+      summary: true
+      per_stage: true
diff --git a/benchmarking/single-workload/prefill-heavy-values.yaml b/benchmarking/single-workload/prefill-heavy-values.yaml
new file mode 100644
index 0000000..4ab4470
--- /dev/null
+++ b/benchmarking/single-workload/prefill-heavy-values.yaml
@@ -0,0 +1,77 @@
+# Prefill Heavy Configuration
+job:
+  image:
+    repository: quay.io/inference-perf/inference-perf
+    tag: "0.2.0" # Defaults to .Chart.AppVersion
+  serviceAccountName: ""
+  nodeSelector: {}
+  # Example resources:
+  # resources:
+  #   requests:
+  #     cpu: "1"
+  #     memory: "4Gi"
+  #   limits:
+  #     cpu: "2"
+  #     memory: "8Gi"
+  resources: {}
+
+logLevel: INFO
+
+# A GCS bucket path that points to the dataset file.
+# The file will be copied from this path to the local file system
+# at /dataset/dataset.json for use during the run.
+# NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/dataset.json.
+gcsPath: ""
+
+# An S3 bucket path that points to the dataset file.
+# The file will be copied from this path to the local file system
+# at /dataset/s3-dataset.json for use during the run.
+# NOTE: For this dataset to be used, config.data.path must also be explicitly set to /dataset/s3-dataset.json.
+s3Path: ""
+
+# hfToken optionally creates a secret with the specified token.
+# Can be set using helm install --set hftoken=<token>
+hfToken: ""
+
+config:
+  load:
+    type: constant
+    interval: 15
+    stages:
+    - rate: 300
+      duration: 30
+    - rate: 310
+      duration: 30
+    - rate: 320
+      duration: 30
+    - rate: 330
+      duration: 30
+  api:
+    type: completion
+    streaming: true
+  server:
+    type: vllm
+    model_name: meta-llama/Llama-3.1-8B-Instruct
+    base_url: http://0.0.0.0:8000
+    ignore_eos: true
+  tokenizer:
+    pretrained_model_name_or_path: meta-llama/Llama-3.1-8B-Instruct
+  data:
+    type: billsum_conversations
+    path: ""
+    input_distribution:
+      max: 1024
+    output_distribution:
+      max: 1024
+  metrics:
+    type: prometheus
+    prometheus:
+      google_managed: true
+  report:
+    request_lifecycle:
+      summary: true
+      per_stage: true
+      per_request: true
+    prometheus:
+      summary: true
+      per_stage: true
diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go
index 2f5c1e3..24a59eb 100644
--- a/cmd/epp/runner/runner.go
+++ b/cmd/epp/runner/runner.go
@@ -25,7 +25,10 @@ import (
 	"net/http"
 	"net/http/pprof"
 	"os"
+	"regexp"
 	"runtime"
+	"strconv"
+	"strings"
 	"sync/atomic"
 
 	"github.com/go-logr/logr"
@@ -34,16 +37,18 @@ import (
 	"go.uber.org/zap/zapcore"
 	"google.golang.org/grpc"
 	healthPb "google.golang.org/grpc/health/grpc_health_v1"
+	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/rest"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
 	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
-
 	configapi "sigs.k8s.io/gateway-api-inference-extension/apix/config/v1alpha1"
 	"sigs.k8s.io/gateway-api-inference-extension/internal/runnable"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
@@ -111,6 +116,8 @@ var (
 	poolName            = flag.String("pool-name", runserver.DefaultPoolName, "Name of the InferencePool this Endpoint Picker is associated with.")
 	poolGroup           = flag.String("pool-group", runserver.DefaultPoolGroup, "group of the InferencePool this Endpoint Picker is associated with.")
 	poolNamespace       = flag.String("pool-namespace", "", "Namespace of the InferencePool this Endpoint Picker is associated with.")
+	endpointSelector    = flag.String("endpoint-selector", "", "selector to filter model server pods on, only key=value paris is supported. Format: a comma-separated list of key value paris,  e.g., 'app=vllm-llama3-8b-instruct,env=prod'.")
+	endpointTargetPorts = flag.String("endpoint-target-ports", "", "target ports of model server pods. Format: a comma-separated list of numbers, e.g., '3000,3001,3002'")
 	logVerbosity        = flag.Int("v", logging.DEFAULT, "number for the log level verbosity")
 	secureServing       = flag.Bool("secure-serving", runserver.DefaultSecureServing, "Enables secure serving. Defaults to true.")
 	healthChecking      = flag.Bool("health-checking", runserver.DefaultHealthChecking, "Enables health checking")
@@ -231,16 +238,26 @@ func (r *Runner) Run(ctx context.Context) error {
 	if err != nil {
 		return err
 	}
-	datastore := datastore.NewDatastore(ctx, epf, int32(*modelServerMetricsPort))
 
-	eppConfig, err := r.parseConfigurationPhaseTwo(ctx, rawConfig, datastore)
+	gknn, err := extractGKNN(*poolName, *poolGroup, *poolNamespace, *endpointSelector)
+	if err != nil {
+		setupLog.Error(err, "Failed to extract GKNN")
+		return err
+	}
+	disableK8sCrdReconcile := *endpointSelector != ""
+	ds, err := setupDatastore(setupLog, ctx, epf, int32(*modelServerMetricsPort), disableK8sCrdReconcile, *poolName, *poolNamespace, *endpointSelector, *endpointTargetPorts)
+	if err != nil {
+		setupLog.Error(err, "Failed to setup datastore")
+		return err
+	}
+	eppConfig, err := r.parseConfigurationPhaseTwo(ctx, rawConfig, ds)
 	if err != nil {
 		setupLog.Error(err, "Failed to parse configuration")
 		return err
 	}
 
 	// --- Setup Metrics Server ---
-	r.customCollectors = append(r.customCollectors, collectors.NewInferencePoolMetricsCollector(datastore))
+	r.customCollectors = append(r.customCollectors, collectors.NewInferencePoolMetricsCollector(ds))
 	metrics.Register(r.customCollectors...)
 	metrics.RecordInferenceExtensionInfo(version.CommitSHA, version.BuildRef)
 	// Register metrics handler.
@@ -259,34 +276,10 @@ func (r *Runner) Run(ctx context.Context) error {
 		}(),
 	}
 
-	// Determine pool namespace: if --pool-namespace is non-empty, use it; else NAMESPACE env var; else default
-	resolvePoolNamespace := func() string {
-		if *poolNamespace != "" {
-			return *poolNamespace
-		}
-		if nsEnv := os.Getenv("NAMESPACE"); nsEnv != "" {
-			return nsEnv
-		}
-		return runserver.DefaultPoolNamespace
-	}
-	resolvedPoolNamespace := resolvePoolNamespace()
-	poolNamespacedName := types.NamespacedName{
-		Name:      *poolName,
-		Namespace: resolvedPoolNamespace,
-	}
-	poolGroupKind := schema.GroupKind{
-		Group: *poolGroup,
-		Kind:  "InferencePool",
-	}
-	poolGKNN := common.GKNN{
-		NamespacedName: poolNamespacedName,
-		GroupKind:      poolGroupKind,
-	}
-
 	isLeader := &atomic.Bool{}
 	isLeader.Store(false)
 
-	mgr, err := runserver.NewDefaultManager(poolGKNN, cfg, metricsServerOptions, *haEnableLeaderElection)
+	mgr, err := runserver.NewDefaultManager(disableK8sCrdReconcile, *gknn, cfg, metricsServerOptions, *haEnableLeaderElection)
 	if err != nil {
 		setupLog.Error(err, "Failed to create controller manager")
 		return err
@@ -353,14 +346,18 @@ func (r *Runner) Run(ctx context.Context) error {
 		admissionController = requestcontrol.NewLegacyAdmissionController(saturationDetector)
 	}
 
-	director := requestcontrol.NewDirectorWithConfig(datastore, scheduler, admissionController, r.requestControlConfig)
+	director := requestcontrol.NewDirectorWithConfig(
+		ds,
+		scheduler,
+		admissionController,
+		r.requestControlConfig)
 
 	// --- Setup ExtProc Server Runner ---
 	serverRunner := &runserver.ExtProcServerRunner{
 		GrpcPort:                         *grpcPort,
-		PoolNamespacedName:               poolNamespacedName,
-		PoolGKNN:                         poolGKNN,
-		Datastore:                        datastore,
+		GKNN:                             *gknn,
+		Datastore:                        ds,
+		DisableK8sCrdReconcile:           disableK8sCrdReconcile,
 		SecureServing:                    *secureServing,
 		HealthChecking:                   *healthChecking,
 		CertPath:                         *certPath,
@@ -377,7 +374,7 @@ func (r *Runner) Run(ctx context.Context) error {
 
 	// --- Add Runnables to Manager ---
 	// Register health server.
-	if err := registerHealthServer(mgr, ctrl.Log.WithName("health"), datastore, *grpcHealthPort, isLeader, *haEnableLeaderElection); err != nil {
+	if err := registerHealthServer(mgr, ctrl.Log.WithName("health"), ds, *grpcHealthPort, isLeader, *haEnableLeaderElection); err != nil {
 		return err
 	}
 
@@ -397,6 +394,28 @@ func (r *Runner) Run(ctx context.Context) error {
 	return nil
 }
 
+func setupDatastore(setupLog logr.Logger, ctx context.Context, epFactory datalayer.EndpointFactory, modelServerMetricsPort int32, disableK8sCrdReconcile bool, namespace, name, endpointSelector, endpointTargetPorts string) (datastore.Datastore, error) {
+	if !disableK8sCrdReconcile {
+		return datastore.NewDatastore(ctx, epFactory, modelServerMetricsPort), nil
+	} else {
+		endpointPool := datalayer.NewEndpointPool(namespace, name)
+		labelsMap, err := labels.ConvertSelectorToLabelsMap(endpointSelector)
+		if err != nil {
+			setupLog.Error(err, "Failed to parse flag %q with error: %w", "endpoint-selector", err)
+			return nil, err
+		}
+		endpointPool.Selector = labelsMap
+		endpointPool.TargetPorts, err = strToUniqueIntSlice(endpointTargetPorts)
+		if err != nil {
+			setupLog.Error(err, "Failed to parse flag %q with error: %w", "endpoint-target-ports", err)
+			return nil, err
+		}
+
+		endpointPoolOption := datastore.WithEndpointPool(endpointPool)
+		return datastore.NewDatastore(ctx, epFactory, modelServerMetricsPort, endpointPoolOption), nil
+	}
+}
+
 // registerInTreePlugins registers the factory functions of all known plugins
 func (r *Runner) registerInTreePlugins() {
 	plugins.Register(prefix.PrefixCachePluginType, prefix.PrefixCachePluginFactory)
@@ -635,9 +654,19 @@ func registerHealthServer(mgr manager.Manager, logger logr.Logger, ds datastore.
 }
 
 func validateFlags() error {
-	if *poolName == "" {
-		return fmt.Errorf("required %q flag not set", "poolName")
+	if (*poolName != "" && *endpointSelector != "") || (*poolName == "" && *endpointSelector == "") {
+		return errors.New("either pool-name or endpoint-selector must be set")
 	}
+	if *endpointSelector != "" {
+		targetPortsList, err := strToUniqueIntSlice(*endpointTargetPorts)
+		if err != nil {
+			return fmt.Errorf("unexpected value for %q flag with error %w", "endpoint-target-ports", err)
+		}
+		if len(targetPortsList) == 0 || len(targetPortsList) > 8 {
+			return fmt.Errorf("flag %q should have length from 1 to 8", "endpoint-target-ports")
+		}
+	}
+
 	if *configText != "" && *configFile != "" {
 		return fmt.Errorf("both the %q and %q flags can not be set at the same time", "configText", "configFile")
 	}
@@ -648,6 +677,34 @@ func validateFlags() error {
 	return nil
 }
 
+func strToUniqueIntSlice(s string) ([]int, error) {
+	seen := sets.NewInt()
+	var intList []int
+
+	if s == "" {
+		return intList, nil
+	}
+
+	strList := strings.Split(s, ",")
+
+	for _, str := range strList {
+		trimmedStr := strings.TrimSpace(str)
+		if trimmedStr == "" {
+			continue
+		}
+		portInt, err := strconv.Atoi(trimmedStr)
+		if err != nil {
+			return nil, fmt.Errorf("invalid number: '%s' is not an integer", trimmedStr)
+		}
+
+		if _, ok := seen[portInt]; !ok {
+			seen[portInt] = struct{}{}
+			intList = append(intList, portInt)
+		}
+	}
+	return intList, nil
+}
+
 func verifyMetricMapping(mapping backendmetrics.MetricMapping, logger logr.Logger) {
 	if mapping.TotalQueuedRequests == nil {
 		logger.Info("Not scraping metric: TotalQueuedRequests")
@@ -683,3 +740,62 @@ func setupPprofHandlers(mgr ctrl.Manager) error {
 	}
 	return nil
 }
+
+func extractDeploymentName(podName string) (string, error) {
+	regex := regexp.MustCompile(`^(.+)-[a-z0-9]+-[a-z0-9]+$`)
+
+	matches := regex.FindStringSubmatch(podName)
+	if len(matches) == 2 {
+		return matches[1], nil
+	}
+	return "", fmt.Errorf("failed to parse deployment name from pod name %s", podName)
+}
+
+func extractGKNN(poolName, poolGroup, poolNamespace, endpointSelector string) (*common.GKNN, error) {
+	if poolName != "" {
+		// Determine pool namespace: if --pool-namespace is non-empty, use it; else NAMESPACE env var; else default
+		resolvedPoolNamespace := resolvePoolNamespace(poolNamespace)
+		poolNamespacedName := types.NamespacedName{
+			Name:      poolName,
+			Namespace: resolvedPoolNamespace,
+		}
+		poolGroupKind := schema.GroupKind{
+			Group: poolGroup,
+			Kind:  "InferencePool",
+		}
+		return &common.GKNN{
+			NamespacedName: poolNamespacedName,
+			GroupKind:      poolGroupKind,
+		}, nil
+	}
+
+	if endpointSelector != "" {
+		// Determine EPP namespace: NAMESPACE env var; else default
+		resolvedPoolNamespace := resolvePoolNamespace(poolNamespace)
+		// Determine EPP name: POD_NAME env var
+		eppPodNameEnv := os.Getenv("POD_NAME")
+		if eppPodNameEnv == "" {
+			return nil, errors.New("failed to get environment variable POD_NAME")
+
+		}
+		eppName, err := extractDeploymentName(eppPodNameEnv)
+		if err != nil {
+			return nil, err
+		}
+		return &common.GKNN{
+			NamespacedName: types.NamespacedName{Namespace: resolvedPoolNamespace, Name: eppName},
+			GroupKind:      schema.GroupKind{Kind: "Deployment", Group: "apps"},
+		}, nil
+	}
+	return nil, errors.New("can't construct gknn as both pool-name and endpoint-selector are missing")
+}
+
+func resolvePoolNamespace(poolNamespace string) string {
+	if poolNamespace != "" {
+		return poolNamespace
+	}
+	if nsEnv := os.Getenv("NAMESPACE"); nsEnv != "" {
+		return nsEnv
+	}
+	return runserver.DefaultPoolNamespace
+}
diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md
index da4a1a9..cf48fca 100644
--- a/config/charts/inferencepool/README.md
+++ b/config/charts/inferencepool/README.md
@@ -225,16 +225,6 @@ The following table list the configurable parameters of the chart.
 
 This section should document any Gateway provider specific values configurations.
 
-#### GKE
-
-These are the options available to you with `provider.name` set to `gke`:
-
-| **Parameter Name**                          | **Description**                                                                                                        |
-|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
-| `gke.monitoringSecret.name`                 | The name of the monitoring secret to be used. Defaults to `inference-gateway-sa-metrics-reader-secret`.                |
-| `gke.monitoringSecret.namespace`            | The namespace that the monitoring secret lives in. Defaults to `default`.                                              |
-
-
 #### Istio
 
 These are the options available to you with `provider.name` set to `istio`:
diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml
index 5b3634c..be6a39e 100644
--- a/config/charts/inferencepool/templates/epp-deployment.yaml
+++ b/config/charts/inferencepool/templates/epp-deployment.yaml
@@ -147,6 +147,10 @@ spec:
           valueFrom:
             fieldRef:
               fieldPath: metadata.namespace
+        - name: POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
         {{- if .Values.inferenceExtension.tracing.enabled }}
         - name: OTEL_SERVICE_NAME
           value: "gateway-api-inference-extension"
diff --git a/config/manifests/vllm/sim-deployment.yaml b/config/manifests/vllm/sim-deployment.yaml
index 2415c10..be2388c 100644
--- a/config/manifests/vllm/sim-deployment.yaml
+++ b/config/manifests/vllm/sim-deployment.yaml
@@ -14,7 +14,7 @@ spec:
     spec:
       containers:
       - name: vllm-sim
-        image: ghcr.io/llm-d/llm-d-inference-sim:v0.5.0
+        image: ghcr.io/llm-d/llm-d-inference-sim:v0.6.1
         imagePullPolicy: Always
         args:
         - --model
diff --git a/conformance/resources/base.yaml b/conformance/resources/base.yaml
index 3b4933c..87de80a 100644
--- a/conformance/resources/base.yaml
+++ b/conformance/resources/base.yaml
@@ -200,7 +200,7 @@ spec:
       terminationGracePeriodSeconds: 130
       containers:
       - name: epp
-        image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v20251105-cbb8928
+        image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v20251119-2aaf2a6
         imagePullPolicy: Always
         args:
         - --pool-name
@@ -298,7 +298,7 @@ spec:
       terminationGracePeriodSeconds: 130
       containers:
       - name: epp
-        image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v20251105-cbb8928
+        image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v20251119-2aaf2a6
         imagePullPolicy: Always
         args:
         - --pool-name
@@ -507,7 +507,7 @@ spec:
       terminationGracePeriodSeconds: 130
       containers:
       - name: epp
-        image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v20251105-cbb8928
+        image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v20251119-2aaf2a6
         imagePullPolicy: Always
         args:
         - --pool-name
@@ -560,6 +560,7 @@ data:
     kind: EndpointPickerConfig
     plugins:
     - type: header-based-testing-filter
+    - type: destination-endpoint-served-verifier
     schedulingProfiles:
     - name: conformance-profile
       plugins:
diff --git a/mkdocs.yml b/mkdocs.yml
index 9fca3cb..d0998c6 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -84,6 +84,8 @@ nav:
     - Benchmark: performance/benchmark/index.md
     - Advanced Benchmarking Configs: 
       - Prefix Cache Aware: performance/benchmark/advanced-configs/prefix-cache-aware.md
+      - Decode Heavy Workload: performance/benchmark/advanced-configs/decode-heavy-workload.md
+      - Prefill Heavy Workload: performance/benchmark/advanced-configs/prefill-heavy-workload.md
     - Regression Testing: performance/regression-testing/index.md
   - Reference:
     - v1 API Reference: reference/spec.md
diff --git a/pkg/epp/controller/inferenceobjective_reconciler.go b/pkg/epp/controller/inferenceobjective_reconciler.go
index c8ac5a6..49ddf70 100644
--- a/pkg/epp/controller/inferenceobjective_reconciler.go
+++ b/pkg/epp/controller/inferenceobjective_reconciler.go
@@ -21,12 +21,12 @@ import (
 	"fmt"
 
 	"k8s.io/apimachinery/pkg/api/errors"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
-
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
@@ -55,7 +55,7 @@ func (c *InferenceObjectiveReconciler) Reconcile(ctx context.Context, req ctrl.R
 	}
 
 	if notFound || !infObjective.DeletionTimestamp.IsZero() || infObjective.Spec.PoolRef.Name != v1alpha2.ObjectName(c.PoolGKNN.Name) || infObjective.Spec.PoolRef.Group != v1alpha2.Group(c.PoolGKNN.Group) {
-		// InferenceObjective object got deleted or changed the referenced pool.
+		// InferenceObjective object got deleted or changed the referenced inferencePool.
 		c.Datastore.ObjectiveDelete(req.NamespacedName)
 		return ctrl.Result{}, nil
 	}
diff --git a/pkg/epp/controller/inferencepool_reconciler.go b/pkg/epp/controller/inferencepool_reconciler.go
index 3b52de0..400ce43 100644
--- a/pkg/epp/controller/inferencepool_reconciler.go
+++ b/pkg/epp/controller/inferencepool_reconciler.go
@@ -21,20 +21,22 @@ import (
 	"fmt"
 
 	"k8s.io/apimachinery/pkg/api/errors"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/log"
-
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
+	pooltuil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 )
 
 // InferencePoolReconciler utilizes the controller runtime to reconcile Instance Gateway resources
 // This implementation is just used for reading & maintaining data sync. The Gateway implementation
-// will have the proper controller that will create/manage objects on behalf of the server pool.
+// will have the proper controller that will create/manage objects on behalf of the server inferencePool.
 type InferencePoolReconciler struct {
 	client.Reader
 	Datastore datastore.Datastore
@@ -75,25 +77,17 @@ func (c *InferencePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 		c.Datastore.Clear()
 		return ctrl.Result{}, nil
 	}
-
-	// 4. Convert the fetched object to the canonical v1.InferencePool.
-	v1infPool := &v1.InferencePool{}
-
+	var endpointPool *datalayer.EndpointPool
 	switch pool := obj.(type) {
 	case *v1.InferencePool:
-		// If it's already a v1 object, just use it.
-		v1infPool = pool
+		endpointPool = pooltuil.InferencePoolToEndpointPool(pool)
 	case *v1alpha2.InferencePool:
-		var err error
-		err = pool.ConvertTo(v1infPool)
-		if err != nil {
-			return ctrl.Result{}, fmt.Errorf("failed to convert XInferencePool to InferencePool - %w", err)
-		}
+		endpointPool = pooltuil.AlphaInferencePoolToEndpointPool(pool)
 	default:
 		return ctrl.Result{}, fmt.Errorf("unsupported API group: %s", c.PoolGKNN.Group)
 	}
 
-	if err := c.Datastore.PoolSet(ctx, c.Reader, v1infPool); err != nil {
+	if err := c.Datastore.PoolSet(ctx, c.Reader, endpointPool); err != nil {
 		return ctrl.Result{}, fmt.Errorf("failed to update datastore - %w", err)
 	}
 
diff --git a/pkg/epp/controller/pod_reconciler.go b/pkg/epp/controller/pod_reconciler.go
index b3a78ef..06be5d7 100644
--- a/pkg/epp/controller/pod_reconciler.go
+++ b/pkg/epp/controller/pod_reconciler.go
@@ -23,12 +23,12 @@ import (
 	"github.com/go-logr/logr"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/event"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
-
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 	podutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pod"
diff --git a/pkg/epp/datalayer/endpoint_pool.go b/pkg/epp/datalayer/endpoint_pool.go
new file mode 100644
index 0000000..05d91d4
--- /dev/null
+++ b/pkg/epp/datalayer/endpoint_pool.go
@@ -0,0 +1,34 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+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.
+*/
+
+package datalayer
+
+type EndpointPool struct {
+	Selector    map[string]string
+	TargetPorts []int
+	Namespace   string
+	Name        string
+}
+
+// NewEndpointPool creates and returns a new empty instance of EndpointPool.
+func NewEndpointPool(namespace string, name string) *EndpointPool {
+	return &EndpointPool{
+		Selector:    make(map[string]string),
+		TargetPorts: []int{},
+		Namespace:   namespace,
+		Name:        name,
+	}
+}
diff --git a/pkg/epp/datalayer/factory.go b/pkg/epp/datalayer/factory.go
index 58da604..3a81763 100644
--- a/pkg/epp/datalayer/factory.go
+++ b/pkg/epp/datalayer/factory.go
@@ -23,8 +23,6 @@ import (
 
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/log"
-
-	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 )
 
 const (
@@ -40,7 +38,7 @@ const (
 //   - Global metrics logging uses PoolGet solely for error return and PodList to enumerate
 //     all endpoints for metrics summarization.
 type PoolInfo interface {
-	PoolGet() (*v1.InferencePool, error)
+	PoolGet() (*EndpointPool, error)
 	PodList(func(Endpoint) bool) []Endpoint
 }
 
diff --git a/pkg/epp/datastore/datastore.go b/pkg/epp/datastore/datastore.go
index 5d6ff75..2ab2e98 100644
--- a/pkg/epp/datastore/datastore.go
+++ b/pkg/epp/datastore/datastore.go
@@ -28,10 +28,9 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
+
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/log"
-
-	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
@@ -49,8 +48,8 @@ type Datastore interface {
 	// PoolSet sets the given pool in datastore. If the given pool has different label selector than the previous pool
 	// that was stored, the function triggers a resync of the pods to keep the datastore updated. If the given pool
 	// is nil, this call triggers the datastore.Clear() function.
-	PoolSet(ctx context.Context, reader client.Reader, pool *v1.InferencePool) error
-	PoolGet() (*v1.InferencePool, error)
+	PoolSet(ctx context.Context, reader client.Reader, endpointPool *datalayer.EndpointPool) error
+	PoolGet() (*datalayer.EndpointPool, error)
 	PoolHasSynced() bool
 	PoolLabelsMatch(podLabels map[string]string) bool
 
@@ -69,15 +68,23 @@ type Datastore interface {
 	Clear()
 }
 
-func NewDatastore(parentCtx context.Context, epFactory datalayer.EndpointFactory, modelServerMetricsPort int32) Datastore {
+func NewDatastore(parentCtx context.Context, epFactory datalayer.EndpointFactory, modelServerMetricsPort int32, opts ...DatastoreOption) Datastore {
+	// Initialize with defaults
 	store := &datastore{
 		parentCtx:              parentCtx,
 		poolAndObjectivesMu:    sync.RWMutex{},
+		pool:                   nil,
 		objectives:             make(map[string]*v1alpha2.InferenceObjective),
 		pods:                   &sync.Map{},
 		modelServerMetricsPort: modelServerMetricsPort,
 		epf:                    epFactory,
 	}
+
+	// Apply options
+	for _, opt := range opts {
+		opt(store)
+	}
+
 	return store
 }
 
@@ -86,8 +93,8 @@ type datastore struct {
 	parentCtx context.Context
 	// poolAndObjectivesMu is used to synchronize access to pool and the objectives map.
 	poolAndObjectivesMu sync.RWMutex
-	pool                *v1.InferencePool
-	// key: InferenceObjective name, value: *InferenceObjective
+	pool                *datalayer.EndpointPool
+	// key: InferenceObjective.Spec.ModelName, value: *InferenceObjective
 	objectives map[string]*v1alpha2.InferenceObjective
 	// key: types.NamespacedName, value: backendmetrics.PodMetrics
 	pods *sync.Map
@@ -110,9 +117,9 @@ func (ds *datastore) Clear() {
 	ds.pods.Clear()
 }
 
-// /// InferencePool APIs ///
-func (ds *datastore) PoolSet(ctx context.Context, reader client.Reader, pool *v1.InferencePool) error {
-	if pool == nil {
+// /// Pool APIs ///
+func (ds *datastore) PoolSet(ctx context.Context, reader client.Reader, endpointPool *datalayer.EndpointPool) error {
+	if endpointPool == nil {
 		ds.Clear()
 		return nil
 	}
@@ -120,10 +127,10 @@ func (ds *datastore) PoolSet(ctx context.Context, reader client.Reader, pool *v1
 	ds.poolAndObjectivesMu.Lock()
 	defer ds.poolAndObjectivesMu.Unlock()
 
-	oldPool := ds.pool
-	ds.pool = pool
-	if oldPool == nil || !reflect.DeepEqual(pool.Spec.Selector, oldPool.Spec.Selector) {
-		logger.V(logutil.DEFAULT).Info("Updating inference pool endpoints", "selector", pool.Spec.Selector)
+	oldEndpointPool := ds.pool
+	ds.pool = endpointPool
+	if oldEndpointPool == nil || !reflect.DeepEqual(oldEndpointPool.Selector, endpointPool.Selector) {
+		logger.V(logutil.DEFAULT).Info("Updating endpoints", "selector", endpointPool.Selector)
 		// A full resync is required to address two cases:
 		// 1) At startup, the pod events may get processed before the pool is synced with the datastore,
 		//    and hence they will not be added to the store since pool selector is not known yet
@@ -138,7 +145,7 @@ func (ds *datastore) PoolSet(ctx context.Context, reader client.Reader, pool *v1
 	return nil
 }
 
-func (ds *datastore) PoolGet() (*v1.InferencePool, error) {
+func (ds *datastore) PoolGet() (*datalayer.EndpointPool, error) {
 	ds.poolAndObjectivesMu.RLock()
 	defer ds.poolAndObjectivesMu.RUnlock()
 	if !ds.PoolHasSynced() {
@@ -159,7 +166,7 @@ func (ds *datastore) PoolLabelsMatch(podLabels map[string]string) bool {
 	if ds.pool == nil {
 		return false
 	}
-	poolSelector := selectorFromInferencePoolSelector(ds.pool.Spec.Selector.MatchLabels)
+	poolSelector := labels.SelectorFromSet(ds.pool.Selector)
 	podSet := labels.Set(podLabels)
 	return poolSelector.Matches(podSet)
 }
@@ -225,14 +232,14 @@ func (ds *datastore) PodUpdateOrAddIfNotExist(pod *corev1.Pod) bool {
 	}
 
 	modelServerMetricsPort := 0
-	if len(ds.pool.Spec.TargetPorts) == 1 {
+	if len(ds.pool.TargetPorts) == 1 {
 		modelServerMetricsPort = int(ds.modelServerMetricsPort)
 	}
 	pods := []*datalayer.PodInfo{}
-	for idx, port := range ds.pool.Spec.TargetPorts {
+	for idx, port := range ds.pool.TargetPorts {
 		metricsPort := modelServerMetricsPort
 		if metricsPort == 0 {
-			metricsPort = int(port.Number)
+			metricsPort = port
 		}
 		pods = append(pods,
 			&datalayer.PodInfo{
@@ -242,7 +249,7 @@ func (ds *datastore) PodUpdateOrAddIfNotExist(pod *corev1.Pod) bool {
 				},
 				PodName:     pod.Name,
 				Address:     pod.Status.PodIP,
-				Port:        strconv.Itoa(int(port.Number)),
+				Port:        strconv.Itoa(port),
 				MetricsHost: net.JoinHostPort(pod.Status.PodIP, strconv.Itoa(metricsPort)),
 				Labels:      labels,
 			})
@@ -280,7 +287,7 @@ func (ds *datastore) podResyncAll(ctx context.Context, reader client.Reader) err
 	logger := log.FromContext(ctx)
 	podList := &corev1.PodList{}
 	if err := reader.List(ctx, podList, &client.ListOptions{
-		LabelSelector: selectorFromInferencePoolSelector(ds.pool.Spec.Selector.MatchLabels),
+		LabelSelector: labels.SelectorFromSet(ds.pool.Selector),
 		Namespace:     ds.pool.Namespace,
 	}); err != nil {
 		return fmt.Errorf("failed to list pods - %w", err)
@@ -313,14 +320,10 @@ func (ds *datastore) podResyncAll(ctx context.Context, reader client.Reader) err
 	return nil
 }
 
-func selectorFromInferencePoolSelector(selector map[v1.LabelKey]v1.LabelValue) labels.Selector {
-	return labels.SelectorFromSet(stripLabelKeyAliasFromLabelMap(selector))
-}
+type DatastoreOption func(*datastore)
 
-func stripLabelKeyAliasFromLabelMap(labels map[v1.LabelKey]v1.LabelValue) map[string]string {
-	outMap := make(map[string]string)
-	for k, v := range labels {
-		outMap[string(k)] = string(v)
+func WithEndpointPool(pool *datalayer.EndpointPool) DatastoreOption {
+	return func(d *datastore) {
+		d.pool = pool
 	}
-	return outMap
 }
diff --git a/pkg/epp/handlers/server.go b/pkg/epp/handlers/server.go
index 5296d49..a67a1ea 100644
--- a/pkg/epp/handlers/server.go
+++ b/pkg/epp/handlers/server.go
@@ -29,10 +29,10 @@ import (
 	"github.com/google/uuid"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
-	"sigs.k8s.io/controller-runtime/pkg/log"
 
-	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
+	"sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
 	schedulingtypes "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 	errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error"
@@ -61,7 +61,7 @@ type Director interface {
 }
 
 type Datastore interface {
-	PoolGet() (*v1.InferencePool, error)
+	PoolGet() (*datalayer.EndpointPool, error)
 }
 
 // Server implements the Envoy external processing server.
diff --git a/pkg/epp/metrics/metrics.go b/pkg/epp/metrics/metrics.go
index 24743cc..4958c59 100644
--- a/pkg/epp/metrics/metrics.go
+++ b/pkg/epp/metrics/metrics.go
@@ -23,9 +23,9 @@ import (
 
 	"github.com/prometheus/client_golang/prometheus"
 	compbasemetrics "k8s.io/component-base/metrics"
+
 	"sigs.k8s.io/controller-runtime/pkg/log"
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
-
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 	metricsutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/metrics"
 )
diff --git a/pkg/epp/requestcontrol/director.go b/pkg/epp/requestcontrol/director.go
index ecd52d9..c4f4f1c 100644
--- a/pkg/epp/requestcontrol/director.go
+++ b/pkg/epp/requestcontrol/director.go
@@ -27,8 +27,6 @@ import (
 	"time"
 
 	"sigs.k8s.io/controller-runtime/pkg/log"
-
-	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
@@ -49,7 +47,7 @@ const (
 
 // Datastore defines the interface required by the Director.
 type Datastore interface {
-	PoolGet() (*v1.InferencePool, error)
+	PoolGet() (*datalayer.EndpointPool, error)
 	ObjectiveGet(objectiveName string) *v1alpha2.InferenceObjective
 	PodList(predicate func(backendmetrics.PodMetrics) bool) []backendmetrics.PodMetrics
 }
diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go
index d30bc13..2947785 100644
--- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go
+++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go
@@ -67,15 +67,19 @@ const (
 )
 
 var DefaultConfig = Config{
-	DefaultBlockSize:       DefaultBlockSize,
+	AutoTune:               true,
+	BlockSize:              DefaultBlockSize,
 	MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
 	LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 }
 
 type Config struct {
+	// If set to true, the plugin will automatically adjust the configuration based on various
+	// metrics from the model servers.
+	AutoTune bool `json:"autoTune"`
 	// The input prompt is broken into sizes of BlockSize to calculate block hashes . Requests
 	// with length shorter than the block size will be ignored.
-	DefaultBlockSize int `json:"blockSize"`
+	BlockSize int `json:"blockSize"`
 	// MaxPrefixBlocksToMatch is the maximum number of prefix blocks to match. Input beyond this limit will
 	// be ignored.
 	MaxPrefixBlocksToMatch int `json:"maxPrefixBlocksToMatch"`
@@ -148,11 +152,7 @@ var (
 
 // PrefixCachePluginFactory defines the factory function for Prefix plugin.
 func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, handle plugins.Handle) (plugins.Plugin, error) {
-	parameters := Config{
-		DefaultBlockSize:       DefaultBlockSize,
-		MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
-		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
-	}
+	parameters := DefaultConfig
 
 	if rawParameters != nil {
 		if err := json.Unmarshal(rawParameters, &parameters); err != nil {
@@ -167,40 +167,32 @@ func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, handle
 
 // New initializes a new prefix Plugin and returns its pointer.
 func New(ctx context.Context, config Config) *Plugin {
-	capacity := config.LRUCapacityPerServer
-	if capacity <= 0 {
-		capacity = DefaultLRUCapacityPerServer
+	if config.LRUCapacityPerServer <= 0 {
+		config.LRUCapacityPerServer = DefaultLRUCapacityPerServer
 		log.FromContext(ctx).V(logutil.DEFAULT).Info(
 			"LRUCapacityPerServer is not positive, using default value",
 			"defaultCapacity", DefaultLRUCapacityPerServer,
 		)
 	}
 
-	blockSize := config.DefaultBlockSize
-	if blockSize <= 0 {
-		blockSize = DefaultBlockSize
-		log.FromContext(ctx).V(logutil.DEFAULT).Info("DefaultBlockSize is not positive, using default value",
+	if config.BlockSize <= 0 {
+		config.BlockSize = DefaultBlockSize
+		log.FromContext(ctx).V(logutil.DEFAULT).Info("BlockSize is not positive, using default value",
 			"default", DefaultBlockSize)
 	}
 
-	maxPrefixBlocks := config.MaxPrefixBlocksToMatch
-	if maxPrefixBlocks <= 0 {
-		maxPrefixBlocks = DefaultMaxPrefixBlocks
+	if config.MaxPrefixBlocksToMatch <= 0 {
+		config.MaxPrefixBlocksToMatch = DefaultMaxPrefixBlocks
 		log.FromContext(ctx).V(logutil.DEFAULT).Info("MaxPrefixBlocksToMatch is not positive, using default value",
 			"default", DefaultMaxPrefixBlocks)
 	}
 
-	validConfig := Config{
-		DefaultBlockSize:       blockSize,
-		MaxPrefixBlocksToMatch: maxPrefixBlocks,
-		LRUCapacityPerServer:   capacity,
-	}
-
+	log.FromContext(ctx).V(logutil.DEFAULT).Info("PrefixCachePlugin initialized", "config", config)
 	return &Plugin{
 		typedName:   plugins.TypedName{Type: PrefixCachePluginType, Name: PrefixCachePluginType},
-		config:      validConfig,
+		config:      config,
 		pluginState: plugins.NewPluginState(ctx),
-		indexer:     newIndexer(ctx, capacity),
+		indexer:     newIndexer(ctx, config.LRUCapacityPerServer),
 	}
 }
 
@@ -218,7 +210,7 @@ func (p *Plugin) WithName(name string) *Plugin {
 // Score returns the scoring result for the given list of pods based on context.
 func (p *Plugin) Score(ctx context.Context, cycleState *types.CycleState, request *types.LLMRequest, pods []types.Pod) map[types.Pod]float64 {
 	// pre score step, hashing prompt and find longest prefix match.
-	hashes := hashPrompt(ctx, request, getBlockSize(pods, p.config.DefaultBlockSize), p.config.MaxPrefixBlocksToMatch)
+	hashes := hashPrompt(ctx, request, getBlockSize(pods, p.config), p.config.MaxPrefixBlocksToMatch)
 	state := &SchedulingContextState{
 		PrefixHashes:       hashes,
 		PrefixCacheServers: p.matchLongestPrefix(ctx, hashes),
@@ -248,8 +240,12 @@ func (p *Plugin) Score(ctx context.Context, cycleState *types.CycleState, reques
 // PreRequest records in the plugin cache the result of the scheduling selection.
 func (p *Plugin) PreRequest(ctx context.Context, request *types.LLMRequest, schedulingResult *types.SchedulingResult) {
 	primaryProfileResult := schedulingResult.ProfileResults[schedulingResult.PrimaryProfileName]
-	targetPod := primaryProfileResult.TargetPods[0].GetPod() // get the first pod of the primary profile
-	gpuBlocks := primaryProfileResult.TargetPods[0].GetMetrics().CacheNumGPUBlocks
+	targetPod := primaryProfileResult.TargetPods[0] // get the first pod of the primary profile
+
+	gpuBlocks := p.config.LRUCapacityPerServer
+	if p.config.AutoTune && targetPod.GetMetrics().CacheNumGPUBlocks > 0 {
+		gpuBlocks = targetPod.GetMetrics().CacheNumGPUBlocks
+	}
 
 	state, err := plugins.ReadPluginStateKey[*SchedulingContextState](p.pluginState, request.RequestId, plugins.StateKey(p.TypedName().String()))
 	p.pluginState.Delete(request.RequestId) // delete the state explicitly after completing using it
@@ -265,16 +261,16 @@ func (p *Plugin) PreRequest(ctx context.Context, request *types.LLMRequest, sche
 	p.wg.Add(1)
 	go func() {
 		p.indexer.Add(state.PrefixHashes, Server{
-			ServerID(targetPod.NamespacedName),
+			ServerID(targetPod.GetPod().NamespacedName),
 			gpuBlocks,
 		})
 		p.wg.Done()
 	}()
 
 	total := len(state.PrefixHashes)
-	matchLen := state.PrefixCacheServers[ServerID(targetPod.NamespacedName)]
+	matchLen := state.PrefixCacheServers[ServerID(targetPod.GetPod().NamespacedName)]
 
-	blockSize := getBlockSize(primaryProfileResult.TargetPods, p.config.DefaultBlockSize)
+	blockSize := getBlockSize(primaryProfileResult.TargetPods, p.config)
 	metrics.RecordPrefixCacheMatch(matchLen*blockSize, total*blockSize)
 }
 
@@ -388,9 +384,14 @@ func getUserInputBytes(request *types.LLMRequest) ([]byte, error) {
 	return json.Marshal(request.Body.ChatCompletions.Messages)
 }
 
-func getBlockSize(pods []types.Pod, defaultBlockSize int) int {
+func getBlockSize(pods []types.Pod, config Config) int {
+	if !config.AutoTune {
+		return config.BlockSize
+	}
+
+	// Fallback to BlockSize if no metrics are available.
 	if len(pods) == 0 {
-		return defaultBlockSize
+		return config.BlockSize
 	}
 
 	// Since all PODs originate from the same inference pool, they are considered to have identical configurations.
@@ -401,5 +402,5 @@ func getBlockSize(pods []types.Pod, defaultBlockSize int) int {
 			return cacheBlockSize * averageCharactersPerToken
 		}
 	}
-	return defaultBlockSize
+	return config.BlockSize
 }
diff --git a/pkg/epp/server/controller_manager.go b/pkg/epp/server/controller_manager.go
index 47e4f12..c82b0bc 100644
--- a/pkg/epp/server/controller_manager.go
+++ b/pkg/epp/server/controller_manager.go
@@ -25,12 +25,12 @@ import (
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/cache"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
 	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
-
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
@@ -45,7 +45,7 @@ func init() {
 }
 
 // defaultManagerOptions returns the default options used to create the manager.
-func defaultManagerOptions(gknn common.GKNN, metricsServerOptions metricsserver.Options) (ctrl.Options, error) {
+func defaultManagerOptions(disableK8sCrdReconcile bool, gknn common.GKNN, metricsServerOptions metricsserver.Options) (ctrl.Options, error) {
 	opt := ctrl.Options{
 		Scheme: scheme,
 		Cache: cache.Options{
@@ -55,38 +55,38 @@ func defaultManagerOptions(gknn common.GKNN, metricsServerOptions metricsserver.
 						gknn.Namespace: {},
 					},
 				},
-				&v1alpha2.InferenceObjective{}: {
-					Namespaces: map[string]cache.Config{
-						gknn.Namespace: {},
-					},
-				},
 			},
 		},
 		Metrics: metricsServerOptions,
 	}
-	switch gknn.Group {
-	case v1alpha2.GroupName:
-		opt.Cache.ByObject[&v1alpha2.InferencePool{}] = cache.ByObject{
-			Namespaces: map[string]cache.Config{gknn.Namespace: {FieldSelector: fields.SelectorFromSet(fields.Set{
-				"metadata.name": gknn.Name,
-			})}},
-		}
-	case v1.GroupName:
-		opt.Cache.ByObject[&v1.InferencePool{}] = cache.ByObject{
-			Namespaces: map[string]cache.Config{gknn.Namespace: {FieldSelector: fields.SelectorFromSet(fields.Set{
-				"metadata.name": gknn.Name,
-			})}},
+	if !disableK8sCrdReconcile {
+		opt.Cache.ByObject[&v1alpha2.InferenceObjective{}] = cache.ByObject{Namespaces: map[string]cache.Config{
+			gknn.Namespace: {},
+		}}
+		switch gknn.Group {
+		case v1alpha2.GroupName:
+			opt.Cache.ByObject[&v1alpha2.InferencePool{}] = cache.ByObject{
+				Namespaces: map[string]cache.Config{gknn.Namespace: {FieldSelector: fields.SelectorFromSet(fields.Set{
+					"metadata.name": gknn.Name,
+				})}},
+			}
+		case v1.GroupName:
+			opt.Cache.ByObject[&v1.InferencePool{}] = cache.ByObject{
+				Namespaces: map[string]cache.Config{gknn.Namespace: {FieldSelector: fields.SelectorFromSet(fields.Set{
+					"metadata.name": gknn.Name,
+				})}},
+			}
+		default:
+			return ctrl.Options{}, fmt.Errorf("unknown group: %s", gknn.Group)
 		}
-	default:
-		return ctrl.Options{}, fmt.Errorf("unknown group: %s", gknn.Group)
 	}
 
 	return opt, nil
 }
 
 // NewDefaultManager creates a new controller manager with default configuration.
-func NewDefaultManager(gknn common.GKNN, restConfig *rest.Config, metricsServerOptions metricsserver.Options, leaderElectionEnabled bool) (ctrl.Manager, error) {
-	opt, err := defaultManagerOptions(gknn, metricsServerOptions)
+func NewDefaultManager(disableK8sCrdReconcile bool, gknn common.GKNN, restConfig *rest.Config, metricsServerOptions metricsserver.Options, leaderElectionEnabled bool) (ctrl.Manager, error) {
+	opt, err := defaultManagerOptions(disableK8sCrdReconcile, gknn, metricsServerOptions)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create controller manager options: %v", err)
 	}
diff --git a/pkg/epp/server/runserver.go b/pkg/epp/server/runserver.go
index c303717..e43d849 100644
--- a/pkg/epp/server/runserver.go
+++ b/pkg/epp/server/runserver.go
@@ -30,9 +30,9 @@ import (
 	healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
-
 	"sigs.k8s.io/gateway-api-inference-extension/internal/runnable"
 	tlsutil "sigs.k8s.io/gateway-api-inference-extension/internal/tls"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
@@ -48,8 +48,8 @@ import (
 // ExtProcServerRunner provides methods to manage an external process server.
 type ExtProcServerRunner struct {
 	GrpcPort                         int
-	PoolNamespacedName               types.NamespacedName
-	PoolGKNN                         common.GKNN
+	GKNN                             common.GKNN
+	DisableK8sCrdReconcile           bool
 	Datastore                        datastore.Datastore
 	SecureServing                    bool
 	HealthChecking                   bool
@@ -91,7 +91,7 @@ const (
 // NewDefaultExtProcServerRunner creates a runner with default values.
 // Note: Dependencies like Datastore, Scheduler, SD need to be set separately.
 func NewDefaultExtProcServerRunner() *ExtProcServerRunner {
-	poolGKNN := common.GKNN{
+	gknn := common.GKNN{
 		NamespacedName: types.NamespacedName{Name: DefaultPoolName, Namespace: DefaultPoolNamespace},
 		GroupKind: schema.GroupKind{
 			Group: DefaultPoolGroup,
@@ -100,8 +100,8 @@ func NewDefaultExtProcServerRunner() *ExtProcServerRunner {
 	}
 	return &ExtProcServerRunner{
 		GrpcPort:                         DefaultGrpcPort,
-		PoolNamespacedName:               types.NamespacedName{Name: DefaultPoolName, Namespace: DefaultPoolNamespace},
-		PoolGKNN:                         poolGKNN,
+		GKNN:                             gknn,
+		DisableK8sCrdReconcile:           false,
 		SecureServing:                    DefaultSecureServing,
 		HealthChecking:                   DefaultHealthChecking,
 		RefreshPrometheusMetricsInterval: DefaultRefreshPrometheusMetricsInterval,
@@ -113,20 +113,22 @@ func NewDefaultExtProcServerRunner() *ExtProcServerRunner {
 // SetupWithManager sets up the runner with the given manager.
 func (r *ExtProcServerRunner) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
 	// Create the controllers and register them with the manager
-	if err := (&controller.InferencePoolReconciler{
-		Datastore: r.Datastore,
-		Reader:    mgr.GetClient(),
-		PoolGKNN:  r.PoolGKNN,
-	}).SetupWithManager(mgr); err != nil {
-		return fmt.Errorf("failed setting up InferencePoolReconciler: %w", err)
-	}
+	if !r.DisableK8sCrdReconcile {
+		if err := (&controller.InferencePoolReconciler{
+			Datastore: r.Datastore,
+			Reader:    mgr.GetClient(),
+			PoolGKNN:  r.GKNN,
+		}).SetupWithManager(mgr); err != nil {
+			return fmt.Errorf("failed setting up InferencePoolReconciler: %w", err)
+		}
 
-	if err := (&controller.InferenceObjectiveReconciler{
-		Datastore: r.Datastore,
-		Reader:    mgr.GetClient(),
-		PoolGKNN:  r.PoolGKNN,
-	}).SetupWithManager(ctx, mgr); err != nil {
-		return fmt.Errorf("failed setting up InferenceObjectiveReconciler: %w", err)
+		if err := (&controller.InferenceObjectiveReconciler{
+			Datastore: r.Datastore,
+			Reader:    mgr.GetClient(),
+			PoolGKNN:  r.GKNN,
+		}).SetupWithManager(ctx, mgr); err != nil {
+			return fmt.Errorf("failed setting up InferenceObjectiveReconciler: %w", err)
+		}
 	}
 
 	if err := (&controller.PodReconciler{
diff --git a/pkg/epp/util/pool/pool.go b/pkg/epp/util/pool/pool.go
new file mode 100644
index 0000000..67fd9fe
--- /dev/null
+++ b/pkg/epp/util/pool/pool.go
@@ -0,0 +1,89 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+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.
+*/
+
+package pool
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
+	v1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
+)
+
+func InferencePoolToEndpointPool(inferencePool *v1.InferencePool) *datalayer.EndpointPool {
+	if inferencePool == nil {
+		return nil
+	}
+	targetPorts := make([]int, 0, len(inferencePool.Spec.TargetPorts))
+	for _, p := range inferencePool.Spec.TargetPorts {
+		targetPorts = append(targetPorts, int(p.Number))
+
+	}
+	selector := make(map[string]string, len(inferencePool.Spec.Selector.MatchLabels))
+	for k, v := range inferencePool.Spec.Selector.MatchLabels {
+		selector[string(k)] = string(v)
+	}
+	endpointPool := &datalayer.EndpointPool{
+		Selector:    selector,
+		TargetPorts: targetPorts,
+		Namespace:   inferencePool.Namespace,
+		Name:        inferencePool.Name,
+	}
+	return endpointPool
+}
+
+func AlphaInferencePoolToEndpointPool(inferencePool *v1alpha2.InferencePool) *datalayer.EndpointPool {
+	targetPorts := []int{int(inferencePool.Spec.TargetPortNumber)}
+	selector := make(map[string]string, len(inferencePool.Spec.Selector))
+	for k, v := range inferencePool.Spec.Selector {
+		selector[string(k)] = string(v)
+	}
+
+	endpointPool := &datalayer.EndpointPool{
+		TargetPorts: targetPorts,
+		Selector:    selector,
+		Namespace:   inferencePool.Namespace,
+		Name:        inferencePool.Name,
+	}
+	return endpointPool
+}
+
+func EndpointPoolToInferencePool(endpointPool *datalayer.EndpointPool) *v1.InferencePool {
+	targetPorts := make([]v1.Port, 0, len(endpointPool.TargetPorts))
+	for _, p := range endpointPool.TargetPorts {
+		targetPorts = append(targetPorts, v1.Port{Number: v1.PortNumber(p)})
+	}
+	labels := make(map[v1.LabelKey]v1.LabelValue, len(endpointPool.Selector))
+	for k, v := range endpointPool.Selector {
+		labels[v1.LabelKey(k)] = v1.LabelValue(v)
+	}
+
+	inferencePool := &v1.InferencePool{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "inference.networking.k8s.io/v1",
+			Kind:       "InferencePool",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      endpointPool.Name,
+			Namespace: endpointPool.Namespace,
+		},
+		Spec: v1.InferencePoolSpec{
+			Selector:    v1.LabelSelector{MatchLabels: labels},
+			TargetPorts: targetPorts,
+		},
+	}
+	return inferencePool
+}
diff --git a/site-src/performance/benchmark/advanced-configs/decode-heavy-workload.md b/site-src/performance/benchmark/advanced-configs/decode-heavy-workload.md
new file mode 100644
index 0000000..adec5bb
--- /dev/null
+++ b/site-src/performance/benchmark/advanced-configs/decode-heavy-workload.md
@@ -0,0 +1,97 @@
+# Decode Heavy Workload Benchmarking
+This guide shows how to deploy a decode-heavy benchmarking config using inference-perf.
+
+## Prerequisites
+
+Before you begin, ensure you have the following:
+
+*   **Helm 3+**: [Installation Guide](https://helm.sh/docs/intro/install/)
+*   **Kubernetes Cluster**: Access to a Kubernetes cluster
+*   **Hugging Face Token Secret**: A Hugging Face token to pull models.
+*   **Gateway Deployed**: Your inference server/gateway must be deployed and accessible within the cluster.
+
+Follow [benchmarking guide](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/#benchmark) for more information on how to set up gateway and how to validate benchmark results.
+
+## Infinity Instruct Dataset Configuration
+
+The chart uses the `infinity_instruct` [dataset type](https://huggingface.co/datasets/BAAI/Infinity-Instruct). 
+
+>NOTE: Currently, we need to download and supply the dataset for inference-perf to ingest. Currently using helm, we can supply the dataset by uploading to a gcs or s3 bucket. Otherwise, you can follow inference perf guides to run locally with a local dataset file path.
+
+## Deployment
+
+### 1. Check out the repo.
+
+```bash
+git clone https://github.com/kubernetes-sigs/gateway-api-inference-extension
+cd gateway-api-inference-extension/benchmarking/single-workload
+```
+
+### 2. Get the target IP. 
+
+  The examples below shows how to get the IP of a gateway or a k8s service.
+
+  ```bash
+  # Get gateway IP
+  GW_IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}')
+  # Get LoadBalancer k8s service IP
+  SVC_IP=$(kubectl get service/vllm-llama3-8b-instruct -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
+
+  echo $GW_IP
+  echo $SVC_IP
+  ```
+
+### 3. Deploying the Decode Heavy Configuration
+
+This configuration is optimized for scenarios where a high cache hit rate is expected. It uses the `decode-heavy-values.yaml` file.
+
+=== "Google Cloud Storage (GCS)"
+    Use the `gcsPath` field to provide your dataset stored on GCS. The dataset will be downloaded from the bucket and stored locally on the cluster at `/dataset/gcs-dataset.json`. 
+    ```bash
+    export IP='<YOUR_IP>'
+    export PORT='<YOUR_PORT>'
+    export HF_TOKEN='<YOUR_HUGGINGFACE_TOKEN>'
+    helm install decode-heavy ../inference-perf -f decode-heavy-values.yaml \
+      --set hfToken=${HF_TOKEN} \
+      --set "config.server.base_url=http://${IP}:${PORT}" \
+      --set "config.data.path=/dataset/gcs-dataset.json" \
+      --set "gcsPath=<PATH TO DATASET FILE ON GCS BUCKET>"
+    ```
+    **Parameters to customize:**
+    
+    *   `decode-heavy`: A unique name for this deployment.
+    *   `hfTokenSecret.name`: The name of your Kubernetes Secret containing the Hugging Face token (default: `hf-token`).
+    *   `hfTokenSecret.key`: The key in your Kubernetes Secret pointing to the Hugging Face token (default: `token`).
+    *   `config.server.base_url`: The base URL (IP and port) of your inference server for the high-cache scenario.
+    *   `gcsPath`: The path to the downloaded dataset file hosted on your gcs bucket. 
+
+=== "Simple Storage Service (S3)"
+    Use the `s3Path` field to provide your dataset stored on S3. The dataset will be downloaded from the bucket and stored locally on the cluster at `/dataset/s3-dataset.json`. 
+    ```bash
+    export IP='<YOUR_IP>'
+    export PORT='<YOUR_PORT>'
+    export HF_TOKEN='<YOUR_HUGGINGFACE_TOKEN>'
+    helm install decode-heavy ../inference-perf -f decode-heavy-values.yaml \
+      --set hfToken=${HF_TOKEN} \
+      --set "config.server.base_url=http://${IP}:${PORT}" \
+      --set "config.data.path=/dataset/s3-dataset.json" \
+      --set "s3Path=<PATH TO DATASET FILE ON S3 BUCKET>"
+    ```
+    **Parameters to customize:**
+    
+    *   `decode-heavy`: A unique name for this deployment.
+    *   `hfTokenSecret.name`: The name of your Kubernetes Secret containing the Hugging Face token (default: `hf-token`).
+    *   `hfTokenSecret.key`: The key in your Kubernetes Secret pointing to the Hugging Face token (default: `token`).
+    *   `config.server.base_url`: The base URL (IP and port) of your inference server for the high-cache scenario.
+    *   `s3Path`: The path to the downloaded dataset file hosted on your s3 bucket. 
+
+## Clean Up
+
+To uninstall the deployed charts:
+
+```bash
+helm uninstall decode-heavy
+```
+
+## Post Benchmark Analysis
+Follow the benchmarking guide instructions to [compare benchmark results](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/#analyze-the-results).
diff --git a/site-src/performance/benchmark/advanced-configs/prefill-heavy-workload.md b/site-src/performance/benchmark/advanced-configs/prefill-heavy-workload.md
new file mode 100644
index 0000000..d31f99f
--- /dev/null
+++ b/site-src/performance/benchmark/advanced-configs/prefill-heavy-workload.md
@@ -0,0 +1,98 @@
+# Prefill Heavy Workload Benchmarking
+This guide shows how to deploy a prefill-heavy benchmarking config using inference-perf.
+
+## Prerequisites
+
+Before you begin, ensure you have the following:
+
+*   **Helm 3+**: [Installation Guide](https://helm.sh/docs/intro/install/)
+*   **Kubernetes Cluster**: Access to a Kubernetes cluster
+*   **Hugging Face Token Secret**: A Hugging Face token to pull models.
+*   **Gateway Deployed**: Your inference server/gateway must be deployed and accessible within the cluster.
+
+Follow [benchmarking guide](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/#benchmark) for more information on how to set up gateway and how to validate benchmark results.
+
+## Infinity Instruct Dataset Configuration
+
+The chart uses the `infinity_instruct` [dataset type](https://huggingface.co/datasets/BAAI/Infinity-Instruct). 
+
+>NOTE: Currently, we need to download and supply the dataset for inference-perf to ingest. Currently using helm, we can supply the dataset by uploading to a gcs or s3 bucket. Otherwise, you can follow inference perf guides to run locally with a local dataset file path.
+
+## Deployment
+
+### 1. Check out the repo.
+
+```bash
+git clone https://github.com/kubernetes-sigs/gateway-api-inference-extension
+cd gateway-api-inference-extension/benchmarking/single-workload
+```
+
+### 2. Get the target IP. 
+
+  The examples below shows how to get the IP of a gateway or a k8s service.
+
+  ```bash
+  # Get gateway IP
+  GW_IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}')
+  # Get LoadBalancer k8s service IP
+  SVC_IP=$(kubectl get service/vllm-llama3-8b-instruct -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
+
+  echo $GW_IP
+  echo $SVC_IP
+  ```
+
+### 3. Deploying the Prefill Heavy Configuration
+
+This configuration is optimized for scenarios where a high cache hit rate is expected. It uses the `prefill-heavy-values.yaml` file.
+
+=== "Google Cloud Storage (GCS)"
+    Use the `gcsPath` field to provide your dataset stored on GCS. The dataset will be downloaded from the bucket and stored locally on the cluster at `/dataset/gcs-dataset.json`. 
+    ```bash
+    export IP='<YOUR_IP>'
+    export PORT='<YOUR_PORT>'
+    export HF_TOKEN='<YOUR_HUGGINGFACE_TOKEN>'
+    helm install prefill-heavy ../inference-perf -f prefill-heavy-values.yaml \
+      --set hfToken=${HF_TOKEN} \
+      --set "config.server.base_url=http://${IP}:${PORT}" \
+      --set "config.data.path=/dataset/gcs-dataset.json" \
+      --set "gcsPath=<PATH TO DATASET FILE ON GCS BUCKET>"
+    ```
+    **Parameters to customize:**
+    
+    *   `prefill-heavy`: A unique name for this deployment.
+    *   `hfTokenSecret.name`: The name of your Kubernetes Secret containing the Hugging Face token (default: `hf-token`).
+    *   `hfTokenSecret.key`: The key in your Kubernetes Secret pointing to the Hugging Face token (default: `token`).
+    *   `config.server.base_url`: The base URL (IP and port) of your inference server for the high-cache scenario.
+    *   `gcsPath`: The path to the downloaded dataset file hosted on your gcs bucket. 
+
+=== "Simple Storage Service (S3)"
+    Use the `s3Path` field to provide your dataset stored on S3. The dataset will be downloaded from the bucket and stored locally on the cluster at `/dataset/s3-dataset.json`. 
+    ```bash
+    export IP='<YOUR_IP>'
+    export PORT='<YOUR_PORT>'
+    export HF_TOKEN='<YOUR_HUGGINGFACE_TOKEN>'
+    helm install prefill-heavy ../inference-perf -f prefill-heavy-values.yaml \
+      --set hfToken=${HF_TOKEN} \
+      --set "config.server.base_url=http://${IP}:${PORT}" \
+      --set "config.data.path=/dataset/s3-dataset.json" \
+      --set "s3Path=<PATH TO DATASET FILE ON S3 BUCKET>"
+    ```
+
+    **Parameters to customize:**
+    
+    *   `prefill-heavy`: A unique name for this deployment.
+    *   `hfTokenSecret.name`: The name of your Kubernetes Secret containing the Hugging Face token (default: `hf-token`).
+    *   `hfTokenSecret.key`: The key in your Kubernetes Secret pointing to the Hugging Face token (default: `token`).
+    *   `config.server.base_url`: The base URL (IP and port) of your inference server for the high-cache scenario.
+    *   `s3Path`: The path to the downloaded dataset file hosted on your s3 bucket. 
+
+## Clean Up
+
+To uninstall the deployed charts:
+
+```bash
+helm uninstall prefill-heavy
+```
+
+## Post Benchmark Analysis
+Follow the benchmarking guide instructions to [compare benchmark results](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/#analyze-the-results).
diff --git a/site-src/performance/benchmark/index.md b/site-src/performance/benchmark/index.md
index 3c4fdfb..c57b338 100644
--- a/site-src/performance/benchmark/index.md
+++ b/site-src/performance/benchmark/index.md
@@ -173,9 +173,11 @@ detailed list of configuration knobs.
 
 The following is a list of advanced configurations available.
 
-| Guides | Config | Directory | Config(s)
+| Guide | Directory | Config(s)
 | :--- | :--- | :--- | :--- |
 | [Prefix Cache Aware Guide](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/advanced-configs/prefix-cache-aware/#prefix-cache-aware-benchmarking) | `prefix-cache-aware` | `benchamrking/prefix-cache-aware`  | `high-cache-values.yaml` `low-cache-values.yaml` |
+| [Decode Heavy Guide](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/advanced-configs/decode-heavy) | `benchamrking/single-workload`  | `decode-heavy-values.yaml` |
+| [Prefill Heavy Guide](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/advanced-configs/prefill-heavy) | `benchamrking/single-workload`  | `prefill-heavy-values.yaml` |
 
 ## Analyze the results
 



Test patch:
diff --git a/conformance/tests/gateway_destination_endpoint_served.go b/conformance/tests/gateway_destination_endpoint_served.go
new file mode 100644
index 0000000..862abf4
--- /dev/null
+++ b/conformance/tests/gateway_destination_endpoint_served.go
@@ -0,0 +1,109 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+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.
+*/
+
+package tests
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"k8s.io/apimachinery/pkg/types"
+	gwhttp "sigs.k8s.io/gateway-api/conformance/utils/http"
+	"sigs.k8s.io/gateway-api/conformance/utils/suite"
+	"sigs.k8s.io/gateway-api/pkg/features"
+
+	"sigs.k8s.io/gateway-api-inference-extension/conformance/resources"
+	k8sutils "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/kubernetes"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/requestcontrol/plugins/test"
+	testscheduling "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test"
+)
+
+func init() {
+	ConformanceTests = append(ConformanceTests, GatewayDestinationEndpointServed)
+}
+
+var GatewayDestinationEndpointServed = suite.ConformanceTest{
+	ShortName:   "GatewayDestinationEndpointServed",
+	Description: "A conformance test to verify that the gateway correctly reports the endpoint that served the request.",
+	Manifests:   []string{"tests/gateway_destination_endpoint_served.yaml"},
+	Features: []features.FeatureName{
+		features.FeatureName("SupportInferencePool"),
+		features.SupportGateway,
+	},
+	Test: func(t *testing.T, s *suite.ConformanceTestSuite) {
+		const (
+			hostname = "primary.example.com"
+			path     = "/destination-endpoint-served-test"
+		)
+
+		httpRouteNN := types.NamespacedName{Name: "httproute-for-destination-endpoint-served", Namespace: resources.AppBackendNamespace}
+		gatewayNN := resources.PrimaryGatewayNN
+		poolNN := resources.PrimaryInferencePoolNN
+		backendPodLabels := map[string]string{"app": resources.PrimaryModelServerAppLabel}
+
+		t.Log("Verifying HTTPRoute and InferencePool are accepted and the Gateway has an address.")
+		k8sutils.HTTPRouteMustBeAcceptedAndResolved(t, s.Client, s.TimeoutConfig, httpRouteNN, gatewayNN)
+		k8sutils.InferencePoolMustBeAcceptedByParent(t, s.Client, poolNN, gatewayNN)
+		gwAddr := k8sutils.GetGatewayEndpoint(t, s.Client, s.TimeoutConfig, gatewayNN)
+
+		t.Logf("Fetching backend pods with labels: %v", backendPodLabels)
+		pods, err := k8sutils.GetPodsWithLabel(t, s.Client, resources.AppBackendNamespace, backendPodLabels, s.TimeoutConfig)
+		require.NoError(t, err, "Failed to get backend pods")
+		require.Len(t, pods, resources.ModelServerPodReplicas, "Expected to find %d backend pods, but found %d.", resources.ModelServerPodReplicas, len(pods))
+
+		podIPs := make([]string, len(pods))
+		podNames := make([]string, len(pods))
+		for i, pod := range pods {
+			podIPs[i] = pod.Status.PodIP
+			podNames[i] = pod.Name
+		}
+
+		requestBody := `{
+            "model": "conformance-fake-model",
+            "prompt": "Write as if you were a critic: San Francisco"
+        }`
+		t.Run("Request is served by the selected backend pod", func(t *testing.T) {
+			for i := 0; i < len(pods); i++ {
+				gwhttp.MakeRequestAndExpectEventuallyConsistentResponse(
+					t,
+					s.RoundTripper,
+					s.TimeoutConfig,
+					gwAddr,
+					gwhttp.ExpectedResponse{
+						Request: gwhttp.Request{
+							Host:   hostname,
+							Path:   path,
+							Method: http.MethodPost,
+							Body:   requestBody,
+							Headers: map[string]string{
+								testscheduling.HeaderTestEppEndPointSelectionKey: podIPs[i],
+							},
+						},
+						Response: gwhttp.Response{
+							StatusCodes: []int{http.StatusOK},
+							Headers: map[string]string{
+								test.ConformanceTestResultHeader: podIPs[i] + ":3000", // The echo server's port is 3000.
+							},
+						},
+						Backend:   podNames[i],
+						Namespace: resources.AppBackendNamespace,
+					},
+				)
+			}
+		})
+	},
+}
diff --git a/conformance/tests/gateway_destination_endpoint_served.yaml b/conformance/tests/gateway_destination_endpoint_served.yaml
new file mode 100644
index 0000000..0b15da1
--- /dev/null
+++ b/conformance/tests/gateway_destination_endpoint_served.yaml
@@ -0,0 +1,23 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: httproute-for-destination-endpoint-served
+  namespace: inference-conformance-app-backend
+spec:
+  parentRefs:
+  - group: gateway.networking.k8s.io
+    kind: Gateway
+    name: conformance-primary
+    namespace: inference-conformance-infra
+    sectionName: http
+  hostnames:
+  - "primary.example.com"
+  rules:
+  - backendRefs:
+    - group: inference.networking.k8s.io
+      kind: InferencePool
+      name: primary-inference-pool
+    matches:
+    - path:
+        type: PathPrefix
+        value: /destination-endpoint-served-test
diff --git a/pkg/epp/backend/metrics/pod_metrics_test.go b/pkg/epp/backend/metrics/pod_metrics_test.go
index b0297cd..8a5561c 100644
--- a/pkg/epp/backend/metrics/pod_metrics_test.go
+++ b/pkg/epp/backend/metrics/pod_metrics_test.go
@@ -25,7 +25,6 @@ import (
 	"github.com/stretchr/testify/assert"
 	"k8s.io/apimachinery/pkg/types"
 
-	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 )
 
@@ -86,8 +85,8 @@ func TestMetricsRefresh(t *testing.T) {
 
 type fakeDataStore struct{}
 
-func (f *fakeDataStore) PoolGet() (*v1.InferencePool, error) {
-	return &v1.InferencePool{Spec: v1.InferencePoolSpec{TargetPorts: []v1.Port{{Number: 8000}}}}, nil
+func (f *fakeDataStore) PoolGet() (*datalayer.EndpointPool, error) {
+	return &datalayer.EndpointPool{}, nil
 }
 
 func (f *fakeDataStore) PodList(func(PodMetrics) bool) []PodMetrics {
diff --git a/pkg/epp/controller/inferenceobjective_reconciler_test.go b/pkg/epp/controller/inferenceobjective_reconciler_test.go
index 4ceff5d..f8d48ec 100644
--- a/pkg/epp/controller/inferenceobjective_reconciler_test.go
+++ b/pkg/epp/controller/inferenceobjective_reconciler_test.go
@@ -27,25 +27,26 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
+	poolutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 	utiltest "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 )
 
 var (
-	pool          = utiltest.MakeInferencePool("test-pool1").Namespace("ns1").ObjRef()
+	inferencePool = utiltest.MakeInferencePool("test-pool1").Namespace("ns1").ObjRef()
 	infObjective1 = utiltest.MakeInferenceObjective("model1").
-			Namespace(pool.Namespace).
+			Namespace(inferencePool.Namespace).
 			Priority(1).
 			CreationTimestamp(metav1.Unix(1000, 0)).
-			PoolName(pool.Name).
+			PoolName(inferencePool.Name).
 			PoolGroup("inference.networking.k8s.io").ObjRef()
 	infObjective1Pool2 = utiltest.MakeInferenceObjective(infObjective1.Name).
 				Namespace(infObjective1.Namespace).
@@ -57,24 +58,24 @@ var (
 				Namespace(infObjective1.Namespace).
 				Priority(2).
 				CreationTimestamp(metav1.Unix(1003, 0)).
-				PoolName(pool.Name).
+				PoolName(inferencePool.Name).
 				PoolGroup("inference.networking.k8s.io").ObjRef()
 	infObjective1Deleted = utiltest.MakeInferenceObjective(infObjective1.Name).
 				Namespace(infObjective1.Namespace).
 				CreationTimestamp(metav1.Unix(1004, 0)).
 				DeletionTimestamp().
-				PoolName(pool.Name).
+				PoolName(inferencePool.Name).
 				PoolGroup("inference.networking.k8s.io").ObjRef()
 	infObjective1DiffGroup = utiltest.MakeInferenceObjective(infObjective1.Name).
-				Namespace(pool.Namespace).
+				Namespace(inferencePool.Namespace).
 				Priority(1).
 				CreationTimestamp(metav1.Unix(1005, 0)).
-				PoolName(pool.Name).
+				PoolName(inferencePool.Name).
 				PoolGroup("inference.networking.x-k8s.io").ObjRef()
 	infObjective2 = utiltest.MakeInferenceObjective("model2").
-			Namespace(pool.Namespace).
+			Namespace(inferencePool.Namespace).
 			CreationTimestamp(metav1.Unix(1000, 0)).
-			PoolName(pool.Name).
+			PoolName(inferencePool.Name).
 			PoolGroup("inference.networking.k8s.io").ObjRef()
 )
 
@@ -120,7 +121,7 @@ func TestInferenceObjectiveReconciler(t *testing.T) {
 		{
 			name:               "Objective not found, no matching existing objective to delete",
 			objectivessInStore: []*v1alpha2.InferenceObjective{infObjective1},
-			incomingReq:        &types.NamespacedName{Name: "non-existent-objective", Namespace: pool.Namespace},
+			incomingReq:        &types.NamespacedName{Name: "non-existent-objective", Namespace: inferencePool.Namespace},
 			wantObjectives:     []*v1alpha2.InferenceObjective{infObjective1},
 		},
 		{
@@ -130,13 +131,13 @@ func TestInferenceObjectiveReconciler(t *testing.T) {
 			wantObjectives:     []*v1alpha2.InferenceObjective{infObjective1, infObjective2},
 		},
 		{
-			name:               "Objective deleted due to group mismatch for the inference pool",
+			name:               "Objective deleted due to group mismatch for the inference inferencePool",
 			objectivessInStore: []*v1alpha2.InferenceObjective{infObjective1},
 			objective:          infObjective1DiffGroup,
 			wantObjectives:     []*v1alpha2.InferenceObjective{},
 		},
 		{
-			name:           "Objective ignored due to group mismatch for the inference pool",
+			name:           "Objective ignored due to group mismatch for the inference inferencePool",
 			objective:      infObjective1DiffGroup,
 			wantObjectives: []*v1alpha2.InferenceObjective{},
 		},
@@ -164,13 +165,14 @@ func TestInferenceObjectiveReconciler(t *testing.T) {
 			for _, m := range test.objectivessInStore {
 				ds.ObjectiveSet(m)
 			}
-			_ = ds.PoolSet(context.Background(), fakeClient, pool)
+			endpointPool := poolutil.InferencePoolToEndpointPool(inferencePool)
+			_ = ds.PoolSet(context.Background(), fakeClient, endpointPool)
 			reconciler := &InferenceObjectiveReconciler{
 				Reader:    fakeClient,
 				Datastore: ds,
 				PoolGKNN: common.GKNN{
-					NamespacedName: types.NamespacedName{Name: pool.Name, Namespace: pool.Namespace},
-					GroupKind:      schema.GroupKind{Group: pool.GroupVersionKind().Group, Kind: pool.GroupVersionKind().Kind},
+					NamespacedName: types.NamespacedName{Name: inferencePool.Name, Namespace: inferencePool.Namespace},
+					GroupKind:      schema.GroupKind{Group: inferencePool.GroupVersionKind().Group, Kind: inferencePool.GroupVersionKind().Kind},
 				},
 			}
 			if test.incomingReq == nil {
@@ -190,8 +192,7 @@ func TestInferenceObjectiveReconciler(t *testing.T) {
 			if len(test.wantObjectives) != len(ds.ObjectiveGetAll()) {
 				t.Errorf("Unexpected; want: %d, got:%d", len(test.wantObjectives), len(ds.ObjectiveGetAll()))
 			}
-
-			if diff := diffStore(ds, diffStoreParams{wantPool: pool, wantObjectives: test.wantObjectives}); diff != "" {
+			if diff := diffStore(ds, diffStoreParams{wantPool: endpointPool, wantObjectives: test.wantObjectives}); diff != "" {
 				t.Errorf("Unexpected diff (+got/-want): %s", diff)
 			}
 
diff --git a/pkg/epp/controller/inferencepool_reconciler_test.go b/pkg/epp/controller/inferencepool_reconciler_test.go
index a2bce12..0fdecb6 100644
--- a/pkg/epp/controller/inferencepool_reconciler_test.go
+++ b/pkg/epp/controller/inferencepool_reconciler_test.go
@@ -24,20 +24,21 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 	utiltest "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 )
 
@@ -121,49 +122,51 @@ func TestInferencePoolReconciler(t *testing.T) {
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
 	}
-	if diff := diffStore(ds, diffStoreParams{wantPool: pool1, wantPods: []string{"pod1-rank-0", "pod2-rank-0"}}); diff != "" {
+	endpointPool1 := pool.InferencePoolToEndpointPool(pool1)
+	if diff := diffStore(ds, diffStoreParams{wantPool: endpointPool1, wantPods: []string{"pod1-rank-0", "pod2-rank-0"}}); diff != "" {
 		t.Errorf("Unexpected diff (+got/-want): %s", diff)
 	}
 
 	newPool1 := &v1.InferencePool{}
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
-		t.Errorf("Unexpected pool get error: %v", err)
+		t.Errorf("Unexpected inferencePool get error: %v", err)
 	}
 	newPool1.Spec.Selector = v1.LabelSelector{
 		MatchLabels: map[v1.LabelKey]v1.LabelValue{"app": "vllm_v2"},
 	}
 	if err := fakeClient.Update(ctx, newPool1, &client.UpdateOptions{}); err != nil {
-		t.Errorf("Unexpected pool update error: %v", err)
+		t.Errorf("Unexpected inferencePool update error: %v", err)
 	}
-
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
 	}
-	if diff := diffStore(ds, diffStoreParams{wantPool: newPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
+	newEndpointPool1 := pool.InferencePoolToEndpointPool(newPool1)
+	if diff := diffStore(ds, diffStoreParams{wantPool: newEndpointPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
 		t.Errorf("Unexpected diff (+got/-want): %s", diff)
 	}
 
-	// Step 3: update the pool port
+	// Step 3: update the inferencePool port
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
-		t.Errorf("Unexpected pool get error: %v", err)
+		t.Errorf("Unexpected inferencePool get error: %v", err)
 	}
 	newPool1.Spec.TargetPorts = []v1.Port{{Number: 9090}}
 	if err := fakeClient.Update(ctx, newPool1, &client.UpdateOptions{}); err != nil {
-		t.Errorf("Unexpected pool update error: %v", err)
+		t.Errorf("Unexpected inferencePool update error: %v", err)
 	}
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
 	}
-	if diff := diffStore(ds, diffStoreParams{wantPool: newPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
+	newEndpointPool1 = pool.InferencePoolToEndpointPool(newPool1)
+	if diff := diffStore(ds, diffStoreParams{wantPool: newEndpointPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
 		t.Errorf("Unexpected diff (+got/-want): %s", diff)
 	}
 
-	// Step 4: delete the pool to trigger a datastore clear
+	// Step 4: delete the inferencePool to trigger a datastore clear
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
-		t.Errorf("Unexpected pool get error: %v", err)
+		t.Errorf("Unexpected inferencePool get error: %v", err)
 	}
 	if err := fakeClient.Delete(ctx, newPool1, &client.DeleteOptions{}); err != nil {
-		t.Errorf("Unexpected pool delete error: %v", err)
+		t.Errorf("Unexpected inferencePool delete error: %v", err)
 	}
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
@@ -174,17 +177,15 @@ func TestInferencePoolReconciler(t *testing.T) {
 }
 
 type diffStoreParams struct {
-	wantPool       *v1.InferencePool
+	wantPool       *datalayer.EndpointPool
 	wantPods       []string
 	wantObjectives []*v1alpha2.InferenceObjective
 }
 
 func diffStore(datastore datastore.Datastore, params diffStoreParams) string {
 	gotPool, _ := datastore.PoolGet()
-	// controller-runtime fake client may not populate TypeMeta (APIVersion/Kind).
-	// Ignore it when comparing pools.
-	if diff := cmp.Diff(params.wantPool, gotPool, cmpopts.IgnoreTypes(metav1.TypeMeta{})); diff != "" {
-		return "pool:" + diff
+	if diff := cmp.Diff(params.wantPool, gotPool); diff != "" {
+		return "inferencePool:" + diff
 	}
 
 	// Default wantPods if not set because PodGetAll returns an empty slice when empty.
@@ -268,79 +269,73 @@ func TestXInferencePoolReconciler(t *testing.T) {
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
 	}
-	if diff := xDiffStore(t, ds, xDiffStoreParams{wantPool: pool1, wantPods: []string{"pod1-rank-0", "pod2-rank-0"}}); diff != "" {
+	endpointPool1 := pool.AlphaInferencePoolToEndpointPool(pool1)
+	if diff := xDiffStore(ds, xDiffStoreParams{wantPool: endpointPool1, wantPods: []string{"pod1-rank-0", "pod2-rank-0"}}); diff != "" {
 		t.Errorf("Unexpected diff (+got/-want): %s", diff)
 	}
 
 	newPool1 := &v1alpha2.InferencePool{}
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
-		t.Errorf("Unexpected pool get error: %v", err)
+		t.Errorf("Unexpected inferencePool get error: %v", err)
 	}
 	newPool1.Spec.Selector = map[v1alpha2.LabelKey]v1alpha2.LabelValue{"app": "vllm_v2"}
 	if err := fakeClient.Update(ctx, newPool1, &client.UpdateOptions{}); err != nil {
-		t.Errorf("Unexpected pool update error: %v", err)
+		t.Errorf("Unexpected inferencePool update error: %v", err)
 	}
 
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
 	}
-	if diff := xDiffStore(t, ds, xDiffStoreParams{wantPool: newPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
+	newEndpointPool1 := pool.AlphaInferencePoolToEndpointPool(newPool1)
+	if diff := xDiffStore(ds, xDiffStoreParams{wantPool: newEndpointPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
 		t.Errorf("Unexpected diff (+got/-want): %s", diff)
 	}
 
-	// Step 3: update the pool port
+	// Step 3: update the inferencePool port
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
-		t.Errorf("Unexpected pool get error: %v", err)
+		t.Errorf("Unexpected inferencePool get error: %v", err)
 	}
 	newPool1.Spec.TargetPortNumber = 9090
 	if err := fakeClient.Update(ctx, newPool1, &client.UpdateOptions{}); err != nil {
-		t.Errorf("Unexpected pool update error: %v", err)
+		t.Errorf("Unexpected inferencePool update error: %v", err)
 	}
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
 	}
-	if diff := xDiffStore(t, ds, xDiffStoreParams{wantPool: newPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
+	newEndpointPool1 = pool.AlphaInferencePoolToEndpointPool(newPool1)
+	if diff := xDiffStore(ds, xDiffStoreParams{wantPool: newEndpointPool1, wantPods: []string{"pod5-rank-0"}}); diff != "" {
 		t.Errorf("Unexpected diff (+got/-want): %s", diff)
 	}
 
-	// Step 4: delete the pool to trigger a datastore clear
+	// Step 4: delete the inferencePool to trigger a datastore clear
 	if err := fakeClient.Get(ctx, req.NamespacedName, newPool1); err != nil {
-		t.Errorf("Unexpected pool get error: %v", err)
+		t.Errorf("Unexpected inferencePool get error: %v", err)
 	}
 	if err := fakeClient.Delete(ctx, newPool1, &client.DeleteOptions{}); err != nil {
-		t.Errorf("Unexpected pool delete error: %v", err)
+		t.Errorf("Unexpected inferencePool delete error: %v", err)
 	}
 	if _, err := inferencePoolReconciler.Reconcile(ctx, req); err != nil {
 		t.Errorf("Unexpected InferencePool reconcile error: %v", err)
 	}
-	if diff := xDiffStore(t, ds, xDiffStoreParams{wantPods: []string{}}); diff != "" {
+	if diff := xDiffStore(ds, xDiffStoreParams{wantPods: []string{}}); diff != "" {
 		t.Errorf("Unexpected diff (+got/-want): %s", diff)
 	}
 }
 
 type xDiffStoreParams struct {
-	wantPool       *v1alpha2.InferencePool
+	wantPool       *datalayer.EndpointPool
 	wantPods       []string
 	wantObjectives []*v1alpha2.InferenceObjective
 }
 
-func xDiffStore(t *testing.T, datastore datastore.Datastore, params xDiffStoreParams) string {
+func xDiffStore(datastore datastore.Datastore, params xDiffStoreParams) string {
 	gotPool, _ := datastore.PoolGet()
 	if gotPool == nil && params.wantPool == nil {
 		return ""
 	}
 
-	gotXPool := &v1alpha2.InferencePool{}
-
-	err := gotXPool.ConvertFrom(gotPool)
-	if err != nil {
-		t.Fatalf("failed to convert InferencePool to XInferencePool: %v", err)
-	}
-
-	// controller-runtime fake client may not populate TypeMeta (APIVersion/Kind).
-	// Ignore it when comparing pools.
-	if diff := cmp.Diff(params.wantPool, gotXPool, cmpopts.IgnoreTypes(metav1.TypeMeta{})); diff != "" {
-		return "pool:" + diff
+	if diff := cmp.Diff(params.wantPool, gotPool); diff != "" {
+		return "inferencePool:" + diff
 	}
 
 	// Default wantPods if not set because PodGetAll returns an empty slice when empty.
diff --git a/pkg/epp/controller/pod_reconciler_test.go b/pkg/epp/controller/pod_reconciler_test.go
index 28f8173..efdb36b 100644
--- a/pkg/epp/controller/pod_reconciler_test.go
+++ b/pkg/epp/controller/pod_reconciler_test.go
@@ -28,13 +28,14 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 	utiltest "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 )
 
@@ -197,7 +198,7 @@ func TestPodReconciler(t *testing.T) {
 
 			// Configure the initial state of the datastore.
 			store := datastore.NewDatastore(t.Context(), pmf, 0)
-			_ = store.PoolSet(t.Context(), fakeClient, test.pool)
+			_ = store.PoolSet(t.Context(), fakeClient, pool.InferencePoolToEndpointPool(test.pool))
 			for _, pod := range test.existingPods {
 				store.PodUpdateOrAddIfNotExist(pod)
 			}
diff --git a/pkg/epp/datalayer/metrics/logger_test.go b/pkg/epp/datalayer/metrics/logger_test.go
index 3ba2e7e..4bf68cf 100644
--- a/pkg/epp/datalayer/metrics/logger_test.go
+++ b/pkg/epp/datalayer/metrics/logger_test.go
@@ -31,6 +31,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
+	poolutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 )
 
 // Buffer to write the logs to
@@ -95,8 +96,9 @@ var pod2 = &datalayer.PodInfo{
 
 type fakeDataStore struct{}
 
-func (f *fakeDataStore) PoolGet() (*v1.InferencePool, error) {
-	return &v1.InferencePool{Spec: v1.InferencePoolSpec{TargetPorts: []v1.Port{{Number: 8000}}}}, nil
+func (f *fakeDataStore) PoolGet() (*datalayer.EndpointPool, error) {
+	pool := &v1.InferencePool{Spec: v1.InferencePoolSpec{TargetPorts: []v1.Port{{Number: 8000}}}}
+	return poolutil.InferencePoolToEndpointPool(pool), nil
 }
 
 func (f *fakeDataStore) PodList(predicate func(datalayer.Endpoint) bool) []datalayer.Endpoint {
diff --git a/pkg/epp/datastore/datastore_test.go b/pkg/epp/datastore/datastore_test.go
index ee59071..73beb1f 100644
--- a/pkg/epp/datastore/datastore_test.go
+++ b/pkg/epp/datastore/datastore_test.go
@@ -32,12 +32,13 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer"
+	pooltuil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 	testutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 )
 
@@ -87,12 +88,12 @@ func TestPool(t *testing.T) {
 				Build()
 			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 			ds := NewDatastore(context.Background(), pmf, 0)
-			_ = ds.PoolSet(context.Background(), fakeClient, tt.inferencePool)
+			_ = ds.PoolSet(context.Background(), fakeClient, pooltuil.InferencePoolToEndpointPool(tt.inferencePool))
 			gotPool, gotErr := ds.PoolGet()
 			if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
 				t.Errorf("Unexpected error diff (+got/-want): %s", diff)
 			}
-			if diff := cmp.Diff(tt.wantPool, gotPool); diff != "" {
+			if diff := cmp.Diff(pooltuil.InferencePoolToEndpointPool(tt.wantPool), gotPool); diff != "" {
 				t.Errorf("Unexpected pool diff (+got/-want): %s", diff)
 			}
 			gotSynced := ds.PoolHasSynced()
@@ -328,7 +329,7 @@ func TestMetrics(t *testing.T) {
 				Build()
 			pmf := backendmetrics.NewPodMetricsFactory(test.pmc, time.Millisecond)
 			ds := NewDatastore(ctx, pmf, 0)
-			_ = ds.PoolSet(ctx, fakeClient, inferencePool)
+			_ = ds.PoolSet(ctx, fakeClient, pooltuil.InferencePoolToEndpointPool(inferencePool))
 			for _, pod := range test.storePods {
 				ds.PodUpdateOrAddIfNotExist(pod)
 			}
@@ -397,7 +398,7 @@ func TestPods(t *testing.T) {
 			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 			ds := NewDatastore(t.Context(), pmf, 0)
 			fakeClient := fake.NewFakeClient()
-			if err := ds.PoolSet(ctx, fakeClient, inferencePool); err != nil {
+			if err := ds.PoolSet(ctx, fakeClient, pooltuil.InferencePoolToEndpointPool(inferencePool)); err != nil {
 				t.Error(err)
 			}
 			for _, pod := range test.existingPods {
@@ -581,7 +582,7 @@ func TestPodInfo(t *testing.T) {
 			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
 			ds := NewDatastore(t.Context(), pmf, 0)
 			fakeClient := fake.NewFakeClient()
-			if err := ds.PoolSet(ctx, fakeClient, test.pool); err != nil {
+			if err := ds.PoolSet(ctx, fakeClient, pooltuil.InferencePoolToEndpointPool(test.pool)); err != nil {
 				t.Error(err)
 			}
 			for _, pod := range test.existingPods {
diff --git a/pkg/epp/metrics/collectors/inference_pool_test.go b/pkg/epp/metrics/collectors/inference_pool_test.go
index af2923e..20ab69f 100644
--- a/pkg/epp/metrics/collectors/inference_pool_test.go
+++ b/pkg/epp/metrics/collectors/inference_pool_test.go
@@ -27,11 +27,12 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/component-base/metrics/testutil"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
+	poolutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 )
 
 var (
@@ -68,13 +69,6 @@ func TestMetricsCollected(t *testing.T) {
 		},
 	}
 	pmf := backendmetrics.NewPodMetricsFactory(pmc, time.Millisecond)
-	ds := datastore.NewDatastore(context.Background(), pmf, 0)
-
-	scheme := runtime.NewScheme()
-	fakeClient := fake.NewClientBuilder().
-		WithScheme(scheme).
-		Build()
-
 	inferencePool := &v1.InferencePool{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: "test-pool",
@@ -83,7 +77,14 @@ func TestMetricsCollected(t *testing.T) {
 			TargetPorts: []v1.Port{{Number: v1.PortNumber(int32(8000))}},
 		},
 	}
-	_ = ds.PoolSet(context.Background(), fakeClient, inferencePool)
+	ds := datastore.NewDatastore(context.Background(), pmf, 0)
+
+	scheme := runtime.NewScheme()
+	fakeClient := fake.NewClientBuilder().
+		WithScheme(scheme).
+		Build()
+
+	_ = ds.PoolSet(context.Background(), fakeClient, poolutil.InferencePoolToEndpointPool(inferencePool))
 	_ = ds.PodUpdateOrAddIfNotExist(pod1)
 
 	time.Sleep(1 * time.Second)
diff --git a/pkg/epp/requestcontrol/director_test.go b/pkg/epp/requestcontrol/director_test.go
index e705bee..f361303 100644
--- a/pkg/epp/requestcontrol/director_test.go
+++ b/pkg/epp/requestcontrol/director_test.go
@@ -32,8 +32,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
@@ -46,6 +46,7 @@ import (
 	schedulingtypes "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 	errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
+	poolutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 	requtil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/request"
 	testutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 )
@@ -89,7 +90,7 @@ type mockDatastore struct {
 	pods []backendmetrics.PodMetrics
 }
 
-func (ds *mockDatastore) PoolGet() (*v1.InferencePool, error) {
+func (ds *mockDatastore) PoolGet() (*datalayer.EndpointPool, error) {
 	return nil, nil
 }
 func (ds *mockDatastore) ObjectiveGet(_ string) *v1alpha2.InferenceObjective {
@@ -190,14 +191,6 @@ func TestDirector_HandleRequest(t *testing.T) {
 		CreationTimestamp(metav1.Unix(1000, 0)).
 		Priority(1).
 		ObjRef()
-
-	// Datastore setup
-	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
-	ds := datastore.NewDatastore(t.Context(), pmf, 0)
-	ds.ObjectiveSet(ioFoodReview)
-	ds.ObjectiveSet(ioFoodReviewResolve)
-	ds.ObjectiveSet(ioFoodReviewSheddable)
-
 	pool := &v1.InferencePool{
 		ObjectMeta: metav1.ObjectMeta{Name: "test-pool", Namespace: "default"},
 		Spec: v1.InferencePoolSpec{
@@ -210,10 +203,18 @@ func TestDirector_HandleRequest(t *testing.T) {
 		},
 	}
 
+	// Datastore setup
+	pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Second)
+	ds := datastore.NewDatastore(t.Context(), pmf, 0)
+	ds.ObjectiveSet(ioFoodReview)
+	ds.ObjectiveSet(ioFoodReviewResolve)
+	ds.ObjectiveSet(ioFoodReviewSheddable)
+
 	scheme := runtime.NewScheme()
 	_ = clientgoscheme.AddToScheme(scheme)
 	fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
-	if err := ds.PoolSet(ctx, fakeClient, pool); err != nil {
+
+	if err := ds.PoolSet(ctx, fakeClient, poolutil.InferencePoolToEndpointPool(pool)); err != nil {
 		t.Fatalf("Error while setting inference pool: %v", err)
 	}
 
@@ -754,8 +755,9 @@ func TestGetRandomPod(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			pmf := backendmetrics.NewPodMetricsFactory(&backendmetrics.FakePodMetricsClient{}, time.Millisecond)
+			endpointPool := poolutil.InferencePoolToEndpointPool(pool)
 			ds := datastore.NewDatastore(t.Context(), pmf, 0)
-			err := ds.PoolSet(t.Context(), fakeClient, pool)
+			err := ds.PoolSet(t.Context(), fakeClient, endpointPool)
 			if err != nil {
 				t.Errorf("unexpected error setting pool: %s", err)
 			}
diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go
index 1bb0794..f0feeef 100644
--- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go
+++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go
@@ -35,7 +35,7 @@ import (
 
 func TestPrefixPluginCompletion(t *testing.T) {
 	config := Config{
-		DefaultBlockSize:       4,
+		BlockSize:              4,
 		MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
 		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 	}
@@ -201,7 +201,7 @@ func TestPrefixPluginCompletion(t *testing.T) {
 
 func TestPrefixPluginChatCompletions(t *testing.T) {
 	config := Config{
-		DefaultBlockSize:       4,
+		BlockSize:              4,
 		MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
 		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 	}
@@ -235,7 +235,7 @@ func TestPrefixPluginChatCompletions(t *testing.T) {
 
 func TestPrefixPluginChatCompletionsGrowth(t *testing.T) {
 	config := Config{
-		DefaultBlockSize:       8, // Use larger block size for more predictable JSON marshaling
+		BlockSize:              8, // Use larger block size for more predictable JSON marshaling
 		MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
 		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 	}
@@ -349,7 +349,7 @@ func BenchmarkPrefixPluginStress(b *testing.B) {
 	blockSize := 4
 	maxPrefixBlocks := 50000
 	config := Config{
-		DefaultBlockSize:       blockSize,
+		BlockSize:              blockSize,
 		MaxPrefixBlocksToMatch: maxPrefixBlocks,
 		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 	}
@@ -409,7 +409,7 @@ func TestNew_InvalidConfigFallbacks(t *testing.T) {
 		{
 			name: "all zero",
 			config: Config{
-				DefaultBlockSize:       0,
+				BlockSize:              0,
 				MaxPrefixBlocksToMatch: 0,
 				LRUCapacityPerServer:   0,
 			},
@@ -420,7 +420,7 @@ func TestNew_InvalidConfigFallbacks(t *testing.T) {
 		{
 			name: "negative values",
 			config: Config{
-				DefaultBlockSize:       -5,
+				BlockSize:              -5,
 				MaxPrefixBlocksToMatch: -10,
 				LRUCapacityPerServer:   -100,
 			},
@@ -431,7 +431,7 @@ func TestNew_InvalidConfigFallbacks(t *testing.T) {
 		{
 			name: "mixed valid and invalid",
 			config: Config{
-				DefaultBlockSize:       32,    // valid
+				BlockSize:              32,    // valid
 				MaxPrefixBlocksToMatch: -1,    // invalid
 				LRUCapacityPerServer:   50000, // valid
 			},
@@ -442,7 +442,7 @@ func TestNew_InvalidConfigFallbacks(t *testing.T) {
 		{
 			name: "all valid",
 			config: Config{
-				DefaultBlockSize:       64,
+				BlockSize:              64,
 				MaxPrefixBlocksToMatch: 200,
 				LRUCapacityPerServer:   30000,
 			},
@@ -459,13 +459,108 @@ func TestNew_InvalidConfigFallbacks(t *testing.T) {
 
 			assert.NotEmpty(t, plugin)
 			assert.NotEmpty(t, plugin.indexer)
-			assert.Equal(t, tt.expectBlock, plugin.config.DefaultBlockSize)
+			assert.Equal(t, tt.expectBlock, plugin.config.BlockSize)
 			assert.Equal(t, tt.expectMaxMatch, plugin.config.MaxPrefixBlocksToMatch)
 			assert.Equal(t, tt.expectCapacity, plugin.config.LRUCapacityPerServer)
 		})
 	}
 }
 
+func TestPrefixPluginAutoTune(t *testing.T) {
+	// Setup common test data
+	podName := "pod-autotune"
+	pod := &types.PodMetrics{
+		Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: podName}},
+		MetricsState: &backendmetrics.MetricsState{
+			CacheBlockSize:    16,   // 16 tokens * 4 chars/token = 64 chars per block
+			CacheNumGPUBlocks: 1000, // 1000 blocks capacity
+		},
+	}
+	pods := []types.Pod{pod}
+
+	req := &types.LLMRequest{
+		RequestId:   uuid.NewString(),
+		TargetModel: "test-model",
+		Body: &types.LLMRequestBody{
+			Completions: &types.CompletionsRequest{
+				// Length 128 chars.
+				// If AutoTune=true (block size 64): 2 blocks
+				// If AutoTune=false (block size 32): 4 blocks
+				Prompt: strings.Repeat("a", 128),
+			},
+		},
+	}
+
+	t.Run("AutoTune Enabled", func(t *testing.T) {
+		config := Config{
+			AutoTune:               true,
+			BlockSize:              32, // Should be ignored in favor of pod metrics (64)
+			MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
+			// Should be ignored in favor of pod metrics (1000)
+			LRUCapacityPerServer: 1,
+		}
+		plugin := New(context.Background(), config)
+
+		// 1. Verify Score uses pod metrics for block size
+		scores := plugin.Score(context.Background(), types.NewCycleState(), req, pods)
+		_ = scores
+
+		state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req.RequestId, plugins.StateKey(plugin.TypedName().String()))
+		assert.NoError(t, err)
+		// Block size from pod is 16 tokens * 4 = 64 chars.
+		// Prompt is 128 chars.
+		// Expected blocks: 128/64 = 2 hashes (model hash is used as seed but not returned as a block)
+		assert.Equal(t, 2, len(state.PrefixHashes), "Should use pod block size (64 chars) -> 2 body blocks")
+
+		// 2. Verify PreRequest uses pod metrics for capacity
+		schedulingResult := &types.SchedulingResult{
+			PrimaryProfileName: "default",
+			ProfileResults: map[string]*types.ProfileRunResult{
+				"default": {TargetPods: []types.Pod{pod}},
+			},
+		}
+		plugin.PreRequest(context.Background(), req, schedulingResult)
+		plugin.wg.Wait()
+
+		// Check indexer state
+		assert.Contains(t, plugin.indexer.Pods(), ServerID(pod.GetPod().NamespacedName))
+	})
+
+	t.Run("AutoTune Disabled", func(t *testing.T) {
+		config := Config{
+			AutoTune:               false,
+			BlockSize:              32, // Should be used (32 chars)
+			MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks,
+			LRUCapacityPerServer:   1, // Should be used, and the first hash should be evicted due to the small
+		}
+		plugin := New(context.Background(), config)
+
+		// 1. Verify Score uses config BlockSize
+		req.RequestId = uuid.NewString() // New request ID
+		scores := plugin.Score(context.Background(), types.NewCycleState(), req, pods)
+		_ = scores
+
+		state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req.RequestId, plugins.StateKey(plugin.TypedName().String()))
+		assert.NoError(t, err)
+		// Block size from config is 32 chars.
+		// Prompt is 128 chars.
+		// 128 / 32 = 4 chunks.
+		assert.Equal(t, 4, len(state.PrefixHashes), "Should use config block size (32 chars) -> 4 body blocks")
+
+		// 2. Verify PreRequest uses config LRUCapacityPerServer
+		schedulingResult := &types.SchedulingResult{
+			PrimaryProfileName: "default",
+			ProfileResults: map[string]*types.ProfileRunResult{
+				"default": {TargetPods: []types.Pod{pod}},
+			},
+		}
+		plugin.PreRequest(context.Background(), req, schedulingResult)
+		plugin.wg.Wait()
+
+		assert.Contains(t, plugin.indexer.Pods(), ServerID(pod.GetPod().NamespacedName))
+	})
+}
+
 // randomPrompt generates a pseudo-random string of length n using lowercase letters.
 func randomPrompt(n int) string {
 	runes := []rune("abcdefghijklmnopqrstuvwxyz")
@@ -481,7 +576,7 @@ func BenchmarkPrefixPluginChatCompletionsStress(b *testing.B) {
 	blockSize := 8
 	maxPrefixBlocks := 50000
 	config := Config{
-		DefaultBlockSize:       blockSize,
+		BlockSize:              blockSize,
 		MaxPrefixBlocksToMatch: maxPrefixBlocks,
 		LRUCapacityPerServer:   DefaultLRUCapacityPerServer,
 	}
diff --git a/pkg/epp/server/runserver_test.go b/pkg/epp/server/runserver_test.go
index b02688c..1729288 100644
--- a/pkg/epp/server/runserver_test.go
+++ b/pkg/epp/server/runserver_test.go
@@ -20,7 +20,6 @@ import (
 	"testing"
 
 	"sigs.k8s.io/controller-runtime/pkg/manager"
-
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/server"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
diff --git a/pkg/epp/util/testing/wrappers.go b/pkg/epp/util/testing/wrappers.go
index 7621bff..3e33805 100644
--- a/pkg/epp/util/testing/wrappers.go
+++ b/pkg/epp/util/testing/wrappers.go
@@ -19,8 +19,12 @@ package testing
 import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
 )
 
 // PodWrapper wraps a Pod.
@@ -219,6 +223,19 @@ func (m *InferencePoolWrapper) ObjRef() *v1.InferencePool {
 	return &m.InferencePool
 }
 
+func (m *InferencePoolWrapper) ToGKNN() common.GKNN {
+	return common.GKNN{
+		NamespacedName: types.NamespacedName{
+			Name:      m.Name,
+			Namespace: m.ObjectMeta.Namespace,
+		},
+		GroupKind: schema.GroupKind{
+			Group: "inference.networking.k8s.io",
+			Kind:  "InferencePool",
+		},
+	}
+}
+
 // AlphaInferencePoolWrapper wraps an group "inference.networking.x-k8s.io" InferencePool.
 type AlphaInferencePoolWrapper struct {
 	v1alpha2.InferencePool
diff --git a/test/integration/epp/hermetic_test.go b/test/integration/epp/hermetic_test.go
index 9ce4fec..f1f32d5 100644
--- a/test/integration/epp/hermetic_test.go
+++ b/test/integration/epp/hermetic_test.go
@@ -48,6 +48,7 @@ import (
 	k8syaml "k8s.io/apimachinery/pkg/util/yaml"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	metricsutils "k8s.io/component-base/metrics/testutil"
+
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/cache"
 	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -56,8 +57,6 @@ import (
 	crmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
 	"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
 	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
-	"sigs.k8s.io/yaml"
-
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/common"
@@ -79,6 +78,7 @@ import (
 	requtil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/request"
 	epptestutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 	integrationutils "sigs.k8s.io/gateway-api-inference-extension/test/integration"
+	"sigs.k8s.io/yaml"
 )
 
 const (
@@ -1170,10 +1170,11 @@ func BeforeSuite() func() {
 	serverRunner.TestPodMetricsClient = &backendmetrics.FakePodMetricsClient{}
 	pmf := backendmetrics.NewPodMetricsFactory(serverRunner.TestPodMetricsClient, 10*time.Millisecond)
 	// Adjust from defaults
-	serverRunner.PoolGKNN = common.GKNN{
+	serverRunner.GKNN = common.GKNN{
 		NamespacedName: types.NamespacedName{Namespace: testNamespace, Name: testPoolName},
 		GroupKind:      schema.GroupKind{Group: v1.GroupVersion.Group, Kind: "InferencePool"},
 	}
+
 	serverRunner.Datastore = datastore.NewDatastore(context.Background(), pmf, 0)
 
 	kvCacheUtilizationScorer := scorer.NewKVCacheUtilizationScorer()
diff --git a/test/utils/server.go b/test/utils/server.go
index 9cf907d..f46ed6f 100644
--- a/test/utils/server.go
+++ b/test/utils/server.go
@@ -30,13 +30,14 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-
 	v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
+	pooltuil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/pool"
 	testutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 )
 
@@ -72,7 +73,7 @@ func PrepareForTestStreamingServer(objectives []*v1alpha2.InferenceObjective, po
 		Build()
 	pool := testutil.MakeInferencePool(poolName).Namespace(namespace).ObjRef()
 	pool.Spec.TargetPorts = []v1.Port{{Number: v1.PortNumber(poolPort)}}
-	_ = ds.PoolSet(context.Background(), fakeClient, pool)
+	_ = ds.PoolSet(context.Background(), fakeClient, pooltuil.InferencePoolToEndpointPool(pool))
 
 	return ctx, cancel, ds, pmc
 }