Repository: kaito-project/kaito

Issue:
Automate versioned docs generation as part of release pipeline


Hints from issue comments:

Solution patch:
diff --git a/.github/workflows/aikit.yaml b/.github/workflows/aikit.yaml
new file mode 100644
index 0000000..85e1020
--- /dev/null
+++ b/.github/workflows/aikit.yaml
@@ -0,0 +1,207 @@
+name: AIKit Integration Test
+
+on:
+  workflow_dispatch:
+    inputs:
+      aikit_image:
+        description: 'AIKit container image to test'
+        required: false
+        default: 'ghcr.io/kaito-project/aikit/llama3.2:1b'
+        type: string
+  pull_request:
+    branches:
+      - main
+
+env:
+  KIND_VERSION: "0.29.0"
+  KUBECTL_VERSION: "1.33.0"
+  AIKIT_IMAGE: ${{ github.event.inputs.aikit_image || 'ghcr.io/kaito-project/aikit/llama3.2:1b' }}
+  KAITO_NAMESPACE: "kaito-system"
+
+jobs:
+  aikit-integration-test:
+    runs-on: ubuntu-latest
+    timeout-minutes: 30
+
+    steps:
+      - name: Harden Runner
+        uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
+        with:
+          egress-policy: audit
+
+      - name: Checkout
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+      - name: Install kind
+        run: |
+          curl -Lo ./kind "https://github.com/kubernetes-sigs/kind/releases/download/v${{ env.KIND_VERSION }}/kind-linux-amd64"
+          chmod +x ./kind
+          sudo mv ./kind /usr/local/bin/kind
+
+      - name: Install Helm
+        uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
+
+      - name: Create kind cluster
+        run: |
+          cat << EOF > kind-config.yaml
+          kind: Cluster
+          apiVersion: kind.x-k8s.io/v1alpha4
+          name: kaito
+          nodes:
+          - role: control-plane
+            kubeadmConfigPatches:
+            - |
+              kind: InitConfiguration
+              nodeRegistration:
+                kubeletExtraArgs:
+                  node-labels: "apps=aikit-test"
+          EOF
+          kind create cluster --config kind-config.yaml
+          kubectl cluster-info
+
+      - name: Build workspace controller
+        run: |
+          # Use the docker-build-workspace target with local configuration
+          make docker-build-workspace
+
+          # Load into kind cluster
+          kind load docker-image local/workspace:test --name kaito
+        env:
+          REGISTRY: "local"
+          IMG_NAME: "workspace"
+          IMG_TAG: "test"
+          OUTPUT_TYPE: "type=docker"
+          ARCH: "amd64"
+
+      - name: Deploy KAITO workspace controller
+        run: |
+          # Update the Helm values with our local image
+          yq -i '(.image.repository) = "local/workspace"' ./charts/kaito/workspace/values.yaml
+          yq -i '(.image.tag) = "test"' ./charts/kaito/workspace/values.yaml
+
+          kubectl create namespace ${{ env.KAITO_NAMESPACE }}
+
+          # Install via Helm
+          # CSI Local Node cannot be configured to not deploy and it crashes on kind on github runners for unknown reasons
+          # so we need to filter it out its components
+          helm template kaito-workspace ./charts/kaito/workspace --namespace ${{ env.KAITO_NAMESPACE }} --include-crds --debug --wait | awk '
+            BEGIN { RS="---\n"; ORS="---\n"; csi=0 }
+            /kind: ServiceAccount/ && /name: csi-local-node/ { csi=1 }
+            /kind: StorageClass/ && /name: kaito-local-nvme-disk/ { csi=1 }
+            /kind: ClusterRole/ && /name: csi-local-node-role/ { csi=1 }
+            /kind: ClusterRoleBinding/ && /name: csi-local-node-binding/ { csi=1 }
+            /kind: DaemonSet/ && /name: csi-local-node/ { csi=1 }
+            !csi { print }
+            { csi=0 }
+          ' | kubectl apply -f -
+
+          # Wait for controller to be ready
+          kubectl wait --for=condition=available --timeout=300s deployment/kaito-workspace -n ${{ env.KAITO_NAMESPACE }}
+
+      - name: Create AIKit workspace
+        run: |
+          cat << EOF > aikit-workspace.yaml
+          apiVersion: kaito.sh/v1beta1
+          kind: Workspace
+          metadata:
+            name: workspace-aikit-test
+          resource:
+            labelSelector:
+              matchLabels:
+                apps: aikit-test
+            preferredNodes:
+              - kaito-control-plane
+          inference:
+            template:
+              spec:
+                containers:
+                  - name: aikit-llama
+                    image: ${{ env.AIKIT_IMAGE }}
+                    args:
+                      - "run"
+                      - "--address=:5000"
+          EOF
+          kubectl apply -f aikit-workspace.yaml
+
+      - name: Wait for inference to be ready
+        run: |
+          echo "Waiting for inference to be ready..."
+          kubectl wait --for='jsonpath={.status.conditions[?(@.type=="InferenceReady")].status}=True' workspace/workspace-aikit-test --timeout 300s
+
+      - name: Validate response content
+        run: |
+          # Set up port forwarding to the workspace service
+          echo "Setting up port forwarding..."
+          kubectl port-forward service/workspace-aikit-test 8080:80 &
+          PORT_FORWARD_PID=$!
+
+          # Wait for port-forward to be ready
+          sleep 5
+
+          # Test that we get expected model information
+          echo 'Validating model response contains expected data...'
+          MODELS_RESPONSE=$(curl -s http://localhost:8080/v1/models)
+          echo "Models response: $MODELS_RESPONSE"
+
+          # Extract the first model name from the response
+          MODEL_NAME=$(echo "$MODELS_RESPONSE" | jq -r '.data[0].id')
+          echo "Extracted model name: $MODEL_NAME"
+
+          # Check if response contains 'llama'
+          if echo "$MODELS_RESPONSE" | grep -i 'llama'; then
+            echo 'Model response validation passed!'
+          else
+            echo 'Model response validation failed - no llama model found'
+            kill $PORT_FORWARD_PID
+            exit 1
+          fi
+
+          echo 'Testing completion response...'
+          COMPLETION_RESPONSE=$(curl -s -X POST \
+            -H 'Content-Type: application/json' \
+            -d "{
+              \"model\": \"$MODEL_NAME\",
+              \"messages\": [{\"role\": \"user\", \"content\": \"hello\"}]
+            }" \
+            http://localhost:8080/v1/chat/completions)
+          echo "Completion response: $COMPLETION_RESPONSE"
+
+          # Check if we get some response with expected fields
+          if echo "$COMPLETION_RESPONSE" | jq -e '(type=="object" and has("choices") and has("usage"))'; then
+            echo 'Completion response validation passed!'
+          else
+            echo 'Completion response validation failed - missing expected fields'
+            kill $PORT_FORWARD_PID
+            exit 1
+          fi
+
+          # Clean up port forwarding
+          kill $PORT_FORWARD_PID
+          echo 'Validation completed successfully!'
+
+      - name: Debug info
+        if: always()
+        run: |
+          echo "=== Workspace Status ==="
+          kubectl get workspace workspace-aikit-test -o yaml
+
+          echo "=== Workspace events ==="
+          kubectl describe workspace workspace-aikit-test
+
+          echo "=== Pods ==="
+          kubectl get pods -A -o wide
+
+          echo "=== Services ==="
+          kubectl get services workspace-aikit-test -o yaml
+
+          echo "=== Pod events ==="
+          kubectl describe pods -l kaito.sh/workspace=workspace-aikit-test
+
+          echo "=== Inference Pod Logs ==="
+          kubectl logs -l kaito.sh/workspace=workspace-aikit-test --tail=-1
+
+          echo "=== Controller logs ==="
+          kubectl logs -l app.kubernetes.io/name=workspace -n ${{ env.KAITO_NAMESPACE }} --tail=-1
+
+          echo "=== Node information ==="
+          kubectl describe nodes
diff --git a/.github/workflows/docs-versioning.yaml b/.github/workflows/docs-versioning.yaml
new file mode 100644
index 0000000..2329a1a
--- /dev/null
+++ b/.github/workflows/docs-versioning.yaml
@@ -0,0 +1,190 @@
+name: Create Versioned Documentation
+
+on:
+  push:
+    tags:
+      # Trigger only on new minor version tags (e.g., v0.6.0, v0.7.0, v1.0.0)
+      # This regex matches tags like v0.6.0, v1.0.0 but excludes patch releases like v0.6.1, v0.6.2
+      - 'v[0-9]+.[0-9]+.0'
+
+permissions:
+  contents: read
+
+jobs:
+  create-versioned-docs:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+      pull-requests: write
+    defaults:
+      run:
+        working-directory: website
+    steps:
+      - name: Harden Runner
+        uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
+        with:
+          egress-policy: audit
+
+      - name: Checkout code
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2
+        with:
+          fetch-depth: 0
+          # Check out the main branch to create versioned docs from the latest content
+          ref: main
+
+      - name: Setup Node.js
+        uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
+        with:
+          node-version: 20.x
+
+      - name: Get yarn cache
+        id: yarn-cache
+        run: echo "dir=$(yarn cache dir)" > $GITHUB_OUTPUT
+
+      - name: Cache dependencies
+        uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
+        with:
+          path: ${{ steps.yarn-cache.outputs.dir }}
+          key: ${{ runner.os }}-website-${{ hashFiles('**/yarn.lock') }}
+          restore-keys: |
+            ${{ runner.os }}-website-
+
+      - name: Install dependencies
+        run: yarn install --frozen-lockfile
+
+      - name: Extract version from tag
+        id: get-version
+        run: |
+          # Extract version from tag (e.g., v0.6.0 -> v0.6.x)
+          VERSION_TAG=${GITHUB_REF#refs/tags/}
+          echo "VERSION_TAG=$VERSION_TAG" >> $GITHUB_OUTPUT
+          
+          # Convert v0.6.0 to v0.6.x format for documentation versioning
+          DOC_VERSION=$(echo $VERSION_TAG | sed -E 's/^v([0-9]+)\.([0-9]+)\.0$/v\1.\2.x/')
+          echo "DOC_VERSION=$DOC_VERSION" >> $GITHUB_OUTPUT
+          
+          echo "Creating documentation version: $DOC_VERSION from tag: $VERSION_TAG"
+
+      - name: Check if version already exists
+        id: check-version
+        run: |
+          if grep -q '"${{ steps.get-version.outputs.DOC_VERSION }}"' versions.json; then
+            echo "VERSION_EXISTS=true" >> $GITHUB_OUTPUT
+            echo "Documentation version ${{ steps.get-version.outputs.DOC_VERSION }} already exists"
+          else
+            echo "VERSION_EXISTS=false" >> $GITHUB_OUTPUT
+            echo "Documentation version ${{ steps.get-version.outputs.DOC_VERSION }} does not exist, will create"
+          fi
+
+      - name: Create versioned documentation
+        if: steps.check-version.outputs.VERSION_EXISTS == 'false'
+        run: |
+          echo "Creating new documentation version: ${{ steps.get-version.outputs.DOC_VERSION }}"
+          npm run docusaurus docs:version ${{ steps.get-version.outputs.DOC_VERSION }}
+
+      - name: Fix relative links in versioned docs
+        if: steps.check-version.outputs.VERSION_EXISTS == 'false'
+        run: |
+          echo "Fixing relative links in versioned documentation..."
+          
+          # Find all .md files in the versioned docs directory
+          VERSIONED_DOCS_DIR="versioned_docs/version-${{ steps.get-version.outputs.DOC_VERSION }}"
+          
+          if [ -d "$VERSIONED_DOCS_DIR" ]; then
+            # Count files to be processed
+            FILE_COUNT=$(find "$VERSIONED_DOCS_DIR" -name "*.md" | wc -l)
+            echo "Found $FILE_COUNT markdown files to process"
+            
+            # Use sed to replace '../..' with '../../..' in markdown links
+            # This handles links like [text](../../path/to/file) -> [text](../../../path/to/file)
+            find "$VERSIONED_DOCS_DIR" -name "*.md" -type f -exec sed -i 's|(\.\.\/\.\.\/|(../../../|g' {} \;
+            
+            echo "✓ Fixed relative links in versioned documentation"
+            
+            # Show a sample of changes (first few files that were modified)
+            echo "Sample of files processed:"
+            find "$VERSIONED_DOCS_DIR" -name "*.md" | head -5 | while read file; do
+              echo "  - $file"
+            done
+          else
+            echo "✗ Versioned docs directory not found: $VERSIONED_DOCS_DIR"
+            exit 1
+          fi
+
+      - name: Verify versioned files were created
+        if: steps.check-version.outputs.VERSION_EXISTS == 'false'
+        run: |
+          echo "Checking if versioned files were created..."
+          
+          # Check if versioned_docs directory was created
+          if [ -d "versioned_docs/version-${{ steps.get-version.outputs.DOC_VERSION }}" ]; then
+            echo "✓ versioned_docs/version-${{ steps.get-version.outputs.DOC_VERSION }} directory created"
+          else
+            echo "✗ versioned_docs directory not found"
+            exit 1
+          fi
+          
+          # Check if versioned sidebar was created
+          if [ -f "versioned_sidebars/version-${{ steps.get-version.outputs.DOC_VERSION }}-sidebars.json" ]; then
+            echo "✓ versioned_sidebars/version-${{ steps.get-version.outputs.DOC_VERSION }}-sidebars.json file created"
+          else
+            echo "✗ versioned sidebar file not found"
+            exit 1
+          fi
+          
+          # Check if versions.json was updated
+          if grep -q '"${{ steps.get-version.outputs.DOC_VERSION }}"' versions.json; then
+            echo "✓ versions.json updated with new version"
+          else
+            echo "✗ versions.json not updated"
+            exit 1
+          fi
+
+      - name: Configure Git
+        if: steps.check-version.outputs.VERSION_EXISTS == 'false'
+        run: |
+          git config user.name "github-actions[bot]"
+          git config user.email "github-actions[bot]@users.noreply.github.com"
+      - name: Create release pull request
+        if: steps.check-version.outputs.VERSION_EXISTS == 'false'
+        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
+        with:
+          commit-message: |
+            docs: add versioned documentation for ${{ steps.get-version.outputs.DOC_VERSION }}
+            
+            - Created versioned_docs/version-${{ steps.get-version.outputs.DOC_VERSION }}/
+            - Created versioned_sidebars/version-${{ steps.get-version.outputs.DOC_VERSION }}-sidebars.json  
+            - Updated versions.json
+            - Fixed relative links for versioned docs directory structure
+            
+            Triggered by release tag: ${{ steps.get-version.outputs.VERSION_TAG }}
+          title: "docs: add versioned documentation for ${{ steps.get-version.outputs.DOC_VERSION }}"
+          branch: "docs/version-${{ steps.get-version.outputs.DOC_VERSION }}-${{ steps.get-version.outputs.VERSION_TAG }}"
+          signoff: true
+          body: |
+            This PR adds versioned documentation for release ${{ steps.get-version.outputs.DOC_VERSION }}.
+
+            ## Changes
+            - ✅ Created \`versioned_docs/version-${{ steps.get-version.outputs.DOC_VERSION }}/\` directory with current docs content
+            - ✅ Created \`versioned_sidebars/version-${{ steps.get-version.outputs.DOC_VERSION }}-sidebars.json\` with current sidebar configuration  
+            - ✅ Updated \`versions.json\` to include the version
+            - ✅ Fixed relative links in markdown files to account for versioned docs directory structure
+
+            ## Release Type
+            🆕 **Minor Release**: This creates new versioned documentation for a new minor version.
+
+            ## Triggered by
+            Release tag: \`${{ steps.get-version.outputs.VERSION_TAG }}\`
+            
+            ## What's next?
+            After merging this PR, the documentation for version ${{ steps.get-version.outputs.DOC_VERSION }} will be available at:
+            - Latest: \`/docs/\` (if this is the latest version)
+            - Versioned: \`/docs/${{ steps.get-version.outputs.DOC_VERSION }}/\`
+            
+            The version will also appear in the documentation version dropdown." \
+              --head "$BRANCH_NAME" \
+              --base main
+      - name: Skip - Version already exists
+        if: steps.check-version.outputs.VERSION_EXISTS == 'true'
+        run: |
+          echo "Skipping documentation versioning - version ${{ steps.get-version.outputs.DOC_VERSION }} already exists in versions.json"
diff --git a/api/v1beta1/workspace_validation.go b/api/v1beta1/workspace_validation.go
index c3af2f3..7013f7d 100644
--- a/api/v1beta1/workspace_validation.go
+++ b/api/v1beta1/workspace_validation.go
@@ -29,6 +29,7 @@ import (
 	"k8s.io/klog/v2"
 	"knative.dev/pkg/apis"
 
+	"github.com/kaito-project/kaito/pkg/featuregates"
 	"github.com/kaito-project/kaito/pkg/model"
 	"github.com/kaito-project/kaito/pkg/utils"
 	"github.com/kaito-project/kaito/pkg/utils/consts"
@@ -57,6 +58,16 @@ func (w *Workspace) Validate(ctx context.Context) (errs *apis.FieldError) {
 	if len(errmsgs) > 0 {
 		errs = errs.Also(apis.ErrInvalidValue(strings.Join(errmsgs, ", "), "name"))
 	}
+
+	// Check if node auto-provisioning is disabled and validate preferred nodes
+	if featuregates.FeatureGates[consts.FeatureFlagDisableNodeAutoProvisioning] {
+		if len(w.Resource.PreferredNodes) < *w.Resource.Count {
+			errs = errs.Also(apis.ErrInvalidValue(
+				fmt.Sprintf("When node auto-provisioning is disabled, the number of preferred nodes (%d) must be at least the required amount (%d) set in count",
+					len(w.Resource.PreferredNodes), *w.Resource.Count), "preferredNodes"))
+		}
+	}
+
 	base := apis.GetBaseline(ctx)
 	if base == nil {
 		klog.InfoS("Validate creation", "workspace", fmt.Sprintf("%s/%s", w.Namespace, w.Name))
diff --git a/charts/kaito/workspace/values.yaml b/charts/kaito/workspace/values.yaml
index 71bda1e..88b2158 100644
--- a/charts/kaito/workspace/values.yaml
+++ b/charts/kaito/workspace/values.yaml
@@ -17,6 +17,7 @@ securityContext:
       - "ALL"
 featureGates:
   vLLM: "true"
+  disableNodeAutoProvisioning: "false"
 webhook:
   port: 9443
 # Knative logging configuration
diff --git a/cmd/workspace/main.go b/cmd/workspace/main.go
index a190ec8..3a09451 100644
--- a/cmd/workspace/main.go
+++ b/cmd/workspace/main.go
@@ -87,7 +87,7 @@ func main() {
 			"Enabling this will ensure there is only one active controller manager.")
 	flag.BoolVar(&enableWebhook, "webhook", true,
 		"Enable webhook for controller manager. Default is true.")
-	flag.StringVar(&featureGates, "feature-gates", "vLLM=true", "Enable Kaito feature gates. Default,	vLLM=true.")
+	flag.StringVar(&featureGates, "feature-gates", "vLLM=true,disableNodeAutoProvisioning=false", "Enable Kaito feature gates. Default: vLLM=true,disableNodeAutoProvisioning=false.")
 	opts := zap.Options{
 		Development: true,
 	}
diff --git a/docs/documentation-versioning.md b/docs/documentation-versioning.md
new file mode 100644
index 0000000..376e90f
--- /dev/null
+++ b/docs/documentation-versioning.md
@@ -0,0 +1,115 @@
+# Documentation Versioning Workflow
+
+This document explains how the automated documentation versioning workflow works in the Kaito project.
+
+## Overview
+
+The `docs-versioning.yaml` GitHub Actions workflow automatically creates versioned documentation when new minor versions are released. This ensures that users can access documentation for specific versions of Kaito.
+
+## How it Works
+
+### Trigger Conditions
+
+The workflow triggers when a new tag is pushed that matches the pattern `v[0-9]+.[0-9]+.0`, which means:
+- ✅ `v0.6.0` - Will trigger (new minor version)
+- ✅ `v0.7.0` - Will trigger (new minor version) 
+- ✅ `v1.0.0` - Will trigger (new major version)
+- ❌ `v0.5.1` - Will NOT trigger (patch version)
+- ❌ `v0.5.2` - Will NOT trigger (patch version)
+
+### Version Format Conversion
+
+The workflow converts semantic version tags to documentation version format:
+- `v0.6.0` → `0.6.x` (documentation version)
+- `v0.7.0` → `0.7.x` (documentation version)
+- `v1.0.0` → `1.0.x` (documentation version)
+
+This follows the pattern where each minor version series (0.6.x, 0.7.x) gets its own documentation version.
+
+### Workflow Steps
+
+1. **Checkout**: Checks out the `main` branch to get the latest documentation content
+2. **Setup**: Installs Node.js and project dependencies
+3. **Version Check**: Verifies if the documentation version already exists
+4. **Create Version**: Runs `npm run docusaurus docs:version <version>` to create versioned docs
+5. **Verify**: Confirms all expected files were created:
+   - `versioned_docs/version-<version>/` directory
+   - `versioned_sidebars/version-<version>-sidebars.json` file
+   - Updated `versions.json` file
+6. **Create PR**: Creates a pull request with the versioned documentation changes
+
+### Generated Files
+
+When versioning documentation for `v0.6.0`, the workflow creates:
+
+```
+website/
+├── versioned_docs/
+│   └── version-0.6.x/          # Copy of current docs/ content
+│       ├── getting-started/
+│       ├── installation/
+│       └── ...
+├── versioned_sidebars/
+│   └── version-0.6.x-sidebars.json  # Copy of current sidebar config
+└── versions.json               # Updated with "0.6.x" entry
+```
+
+## Benefits
+
+1. **Automatic**: No manual intervention required for versioning
+2. **Consistent**: Uses the same versioning pattern every time
+3. **Safe**: Creates a PR for review before merging changes
+4. **Traceable**: Clear commit messages and PR descriptions
+5. **Robust**: Includes verification steps and error handling
+
+## Manual Usage
+
+If you need to create versioned documentation manually:
+
+```bash
+cd website
+npm run docusaurus docs:version 0.6.x
+```
+
+This will create the same files that the automated workflow creates.
+
+## Configuration
+
+The workflow is configured in `.github/workflows/docs-versioning.yaml` and uses:
+- **Node.js 20.x**: For running Docusaurus commands
+- **Yarn**: For dependency management (with caching)
+- **GitHub CLI**: For creating pull requests
+
+## Troubleshooting
+
+### Workflow doesn't trigger
+- Verify the tag follows the correct pattern (`v[0-9]+.[0-9]+.0`)
+- Check that the tag was pushed (not just created locally)
+
+### Version already exists
+- The workflow checks if a version already exists and skips creation if found
+- This prevents duplicate versioning attempts
+
+### PR creation fails
+- Ensure the GitHub token has sufficient permissions
+- Check for branch naming conflicts
+
+### Files not created
+- The workflow includes verification steps that will fail if expected files aren't created
+- Check the Docusaurus command output for errors
+
+## Best Practices
+
+1. **Review PRs**: Always review the generated PR before merging
+2. **Test locally**: Test documentation builds locally before releasing
+3. **Keep versions manageable**: Don't create too many versions (Docusaurus recommends <10)
+4. **Update regularly**: Remove old versions when they're no longer supported
+
+## Integration with Release Process
+
+This workflow integrates with the existing release process:
+1. Release workflow (`capz-release.yaml`) creates and pushes tags
+2. Documentation versioning workflow (`docs-versioning.yaml`) detects new minor version tags
+3. Documentation deployment workflow (`deploy-docs.yaml`) deploys the updated site
+
+The workflows work together to ensure documentation stays in sync with releases.
diff --git a/pkg/featuregates/featuregates.go b/pkg/featuregates/featuregates.go
index 0d69c2d..9885910 100644
--- a/pkg/featuregates/featuregates.go
+++ b/pkg/featuregates/featuregates.go
@@ -25,8 +25,9 @@ import (
 var (
 	// FeatureGates is a map that holds the feature gate names and their default values for Kaito.
 	FeatureGates = map[string]bool{
-		consts.FeatureFlagVLLM:            true,
-		consts.FeatureFlagEnsureNodeClass: false,
+		consts.FeatureFlagVLLM:                        true,
+		consts.FeatureFlagEnsureNodeClass:             false,
+		consts.FeatureFlagDisableNodeAutoProvisioning: false,
 		//	Add more feature gates here
 	}
 )
diff --git a/pkg/utils/consts/consts.go b/pkg/utils/consts/consts.go
index 26ea55f..80544d7 100644
--- a/pkg/utils/consts/consts.go
+++ b/pkg/utils/consts/consts.go
@@ -31,8 +31,9 @@ const (
 	NvidiaGPU                     = "nvidia.com/gpu"
 
 	// Feature flags
-	FeatureFlagVLLM            = "vLLM"
-	FeatureFlagEnsureNodeClass = "ensureNodeClass"
+	FeatureFlagVLLM                        = "vLLM"
+	FeatureFlagEnsureNodeClass             = "ensureNodeClass"
+	FeatureFlagDisableNodeAutoProvisioning = "disableNodeAutoProvisioning"
 
 	// Nodeclaim related consts
 	KaitoNodePoolName             = "kaito"
diff --git a/pkg/workspace/controllers/workspace_controller.go b/pkg/workspace/controllers/workspace_controller.go
index 8409eb0..1d573a9 100644
--- a/pkg/workspace/controllers/workspace_controller.go
+++ b/pkg/workspace/controllers/workspace_controller.go
@@ -47,6 +47,7 @@ import (
 	karpenterv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
 
 	kaitov1beta1 "github.com/kaito-project/kaito/api/v1beta1"
+	"github.com/kaito-project/kaito/pkg/featuregates"
 	"github.com/kaito-project/kaito/pkg/utils"
 	"github.com/kaito-project/kaito/pkg/utils/consts"
 	"github.com/kaito-project/kaito/pkg/utils/nodeclaim"
@@ -360,6 +361,11 @@ func (c *WorkspaceReconciler) applyWorkspaceResource(ctx context.Context, wObj *
 	newNodesCount := lo.FromPtr(wObj.Resource.Count) - len(selectedNodes)
 
 	if newNodesCount > 0 {
+		// Check if node auto-provisioning is disabled
+		if featuregates.FeatureGates[consts.FeatureFlagDisableNodeAutoProvisioning] {
+			return fmt.Errorf("node auto-provisioning is disabled but insufficient nodes available: need %d nodes, have %d selected nodes", lo.FromPtr(wObj.Resource.Count), len(selectedNodes))
+		}
+
 		klog.InfoS("need to create more nodes", "NodeCount", newNodesCount)
 		if err := c.updateStatusConditionIfNotMatch(ctx, wObj,
 			kaitov1beta1.ConditionTypeNodeClaimStatus, metav1.ConditionUnknown,
@@ -419,46 +425,77 @@ func (c *WorkspaceReconciler) applyWorkspaceResource(ctx context.Context, wObj *
 	return nil
 }
 
-func (c *WorkspaceReconciler) getAllQualifiedNodes(ctx context.Context, wObj *kaitov1beta1.Workspace) ([]*corev1.Node, error) {
-	var qualifiedNodes []*corev1.Node
+func nodeIsReadyAndNotDeleting(node corev1.Node) bool {
 
-	nodeList, err := resources.ListNodes(ctx, c.Client, wObj.Resource.LabelSelector.MatchLabels)
-	if err != nil {
-		return nil, err
+	// skip nodes that are being deleted
+	if node.DeletionTimestamp != nil {
+		return false
 	}
 
-	if len(nodeList.Items) == 0 {
-		klog.InfoS("no current nodes match the workspace resource spec", "workspace", klog.KObj(wObj))
-		return nil, nil
-	}
+	// skip nodes that are not ready
+	_, statusRunning := lo.Find(node.Status.Conditions, func(condition corev1.NodeCondition) bool {
+		return condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue
+	})
 
-	preferredNodeSet := sets.New(wObj.Resource.PreferredNodes...)
+	return statusRunning
+}
+
+func (c *WorkspaceReconciler) getAllQualifiedNodes(ctx context.Context, wObj *kaitov1beta1.Workspace) ([]*corev1.Node, error) {
+	var qualifiedNodes []*corev1.Node
 
-	for index := range nodeList.Items {
-		nodeObj := nodeList.Items[index]
-		// skip nodes that are being deleted
-		if nodeObj.DeletionTimestamp != nil {
-			continue
+	if featuregates.FeatureGates[consts.FeatureFlagDisableNodeAutoProvisioning] {
+		for _, name := range wObj.Resource.PreferredNodes {
+			// Get the node with a client call
+			node, err := resources.GetNode(ctx, name, c.Client)
+			if err != nil {
+				klog.ErrorS(err, "failed to get preferred node", "node", name)
+				continue
+			}
+
+			if nodeIsReadyAndNotDeleting(*node) {
+				// Check that all labels in wObj.Resource.LabelSelector.MatchLabels are present in the node's labels.
+				allLabelsMatch := true
+				for k, v := range wObj.Resource.LabelSelector.MatchLabels {
+					val, ok := node.Labels[k]
+					if !ok || v != val {
+						allLabelsMatch = false
+						break
+					}
+				}
+				if allLabelsMatch {
+					qualifiedNodes = append(qualifiedNodes, node)
+				}
+			}
 		}
 
-		// skip nodes that are not ready
-		_, statusRunning := lo.Find(nodeObj.Status.Conditions, func(condition corev1.NodeCondition) bool {
-			return condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue
-		})
-		if !statusRunning {
-			continue
+		if len(qualifiedNodes) < *wObj.Resource.Count {
+			return nil, fmt.Errorf("when node auto-provisioning is disabled, at least %d preferred nodes must match the label selector and be ready and not deleting, only have %d", *wObj.Resource.Count, len(qualifiedNodes))
+		}
+	} else {
+		nodeList, err := resources.ListNodes(ctx, c.Client, wObj.Resource.LabelSelector.MatchLabels)
+		if err != nil {
+			return nil, err
 		}
 
-		// match the preferred node
-		if preferredNodeSet.Has(nodeObj.Name) {
-			qualifiedNodes = append(qualifiedNodes, lo.ToPtr(nodeObj))
-			continue
+		if len(nodeList.Items) == 0 {
+			klog.InfoS("no current nodes match the workspace resource spec", "workspace", klog.KObj(wObj))
+			return nil, nil
 		}
 
-		// match the instanceType
-		if len(wObj.Resource.PreferredNodes) == 0 { // don't match in perferred nodes mode
-			if nodeObj.Labels[corev1.LabelInstanceTypeStable] == wObj.Resource.InstanceType {
-				qualifiedNodes = append(qualifiedNodes, lo.ToPtr(nodeObj))
+		preferredNodeSet := sets.New(wObj.Resource.PreferredNodes...)
+		for index := range nodeList.Items {
+			node := nodeList.Items[index]
+			if nodeIsReadyAndNotDeleting(node) {
+				// match the preferred node
+				if preferredNodeSet.Has(node.Name) {
+					qualifiedNodes = append(qualifiedNodes, lo.ToPtr(node))
+					continue
+				}
+
+				// match the instanceType
+				if node.Labels[corev1.LabelInstanceTypeStable] == wObj.Resource.InstanceType {
+					qualifiedNodes = append(qualifiedNodes, lo.ToPtr(node))
+				}
 			}
 		}
 	}
diff --git a/website/versioned_docs/version-v0.5.0/installation.md b/website/versioned_docs/version-v0.5.0/installation.md
deleted file mode 100644
index eced392..0000000
--- a/website/versioned_docs/version-v0.5.0/installation.md
+++ /dev/null
@@ -1,145 +0,0 @@
----
-title: Installation
----
-
-The following guidance assumes **Azure Kubernetes Service (AKS)** is used to host the Kubernetes cluster. If you want to use Elastic Kubernetes Service (EKS) instead, please follow the installation guide [here](./aws.md).
-
-Before you begin, ensure you have the following tools installed:
-
-- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) to provision Azure resources
-- [Helm](https://helm.sh) to install this operator
-- [kubectl](https://kubernetes.io/docs/tasks/tools/) to view Kubernetes resources
-- [git](https://git-scm.com/downloads) to clone this repo locally
-- [yq](https://github.com/mikefarah/yq) to process yaml files
-- [jq](https://jqlang.github.io/jq/download) to process JSON files
-
-**Important Note**:
-Ensure you use a release branch of the repository for a stable version of the installation.
-
-If you do not already have an AKS cluster, run the following Azure CLI commands to create one:
-
-```bash
-export RESOURCE_GROUP="myResourceGroup"
-export MY_CLUSTER="myCluster"
-export LOCATION="eastus"
-az group create --name $RESOURCE_GROUP --location $LOCATION
-az aks create --resource-group $RESOURCE_GROUP --name $MY_CLUSTER --enable-oidc-issuer --enable-workload-identity --enable-managed-identity --generate-ssh-keys
-```
-
-Connect to the AKS cluster.
-
-```bash
-az aks get-credentials --resource-group $RESOURCE_GROUP --name $MY_CLUSTER
-```
-
-If you do not have `kubectl` installed locally, you can install using the following Azure CLI command.
-
-```bash
-az aks install-cli
-```
-
-## Install workspace controller
-
-> Be sure you've cloned this repo and connected to your AKS cluster before attempting to install the Helm charts.
-
-Install the Workspace controller.
-
-```bash
-export KAITO_WORKSPACE_VERSION=0.5.0
-
-helm install kaito-workspace  --set clusterName=$MY_CLUSTER --wait \
-https://github.com/kaito-project/kaito/raw/gh-pages/charts/kaito/workspace-$KAITO_WORKSPACE_VERSION.tgz --namespace kaito-workspace --create-namespace
-```
-
-Note that if you have installed another node provisioning controller that supports Karpenter-core APIs, the following steps for installing `gpu-provisioner` can be skipped.
-
-## Install gpu-provisioner controller
-
-#### Enable Workload Identity and OIDC Issuer features
-The *gpu-provisioner* controller requires the [workload identity](https://learn.microsoft.com/azure/aks/workload-identity-overview?tabs=dotnet) feature to acquire the access token to the AKS cluster.
-
-> Run the following commands only if your AKS cluster does not already have the Workload Identity and OIDC issuer features enabled.
-
-```bash
-export RESOURCE_GROUP="myResourceGroup"
-export MY_CLUSTER="myCluster"
-az aks update -g $RESOURCE_GROUP -n $MY_CLUSTER --enable-oidc-issuer --enable-workload-identity --enable-managed-identity
-```
-
-#### Create an identity and assign permissions
-The identity `kaitoprovisioner` is created for the *gpu-provisioner* controller. It is assigned Contributor role for the managed cluster resource to allow changing `$MY_CLUSTER` (e.g., provisioning new nodes in it).
-```bash
-export SUBSCRIPTION=$(az account show --query id -o tsv)
-export IDENTITY_NAME="kaitoprovisioner"
-az identity create --name $IDENTITY_NAME -g $RESOURCE_GROUP
-export IDENTITY_PRINCIPAL_ID=$(az identity show --name $IDENTITY_NAME -g $RESOURCE_GROUP --subscription $SUBSCRIPTION --query 'principalId' -o tsv)
-az role assignment create --assignee $IDENTITY_PRINCIPAL_ID --scope /subscriptions/$SUBSCRIPTION/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerService/managedClusters/$MY_CLUSTER  --role "Contributor"
-```
-
-#### Install helm charts
-
-**Important Note**:
-For kaito 0.4.2 and above, please use gpu-provisioner 0.3.2 or higher. For versions below kaito 0.4.2, please use gpu-provisioner 0.2.1.
-
-Install the Node provisioner controller.
-```bash
-# get additional values for helm chart install
-export GPU_PROVISIONER_VERSION=0.3.5
-
-curl -sO https://raw.githubusercontent.com/Azure/gpu-provisioner/main/hack/deploy/configure-helm-values.sh
-chmod +x ./configure-helm-values.sh && ./configure-helm-values.sh $MY_CLUSTER $RESOURCE_GROUP $IDENTITY_NAME
-
-helm install gpu-provisioner --values gpu-provisioner-values.yaml --set settings.azure.clusterName=$MY_CLUSTER --wait \
-https://github.com/Azure/gpu-provisioner/raw/gh-pages/charts/gpu-provisioner-$GPU_PROVISIONER_VERSION.tgz --namespace gpu-provisioner --create-namespace
-```
-
-#### Create the federated credential
-The federated identity credential between the managed identity `kaitoprovisioner` and the service account used by the *gpu-provisioner* controller is created.
-```bash
-export AKS_OIDC_ISSUER=$(az aks show -n $MY_CLUSTER -g $RESOURCE_GROUP --subscription $SUBSCRIPTION --query "oidcIssuerProfile.issuerUrl" -o tsv)
-az identity federated-credential create --name kaito-federatedcredential --identity-name $IDENTITY_NAME -g $RESOURCE_GROUP --issuer $AKS_OIDC_ISSUER --subject system:serviceaccount:"gpu-provisioner:gpu-provisioner" --audience api://AzureADTokenExchange --subscription $SUBSCRIPTION
-```
-Then the *gpu-provisioner* can access the managed cluster using a trust token with the same permissions of the `kaitoprovisioner` identity.
-Note that before finishing this step, the *gpu-provisioner* controller pod will constantly fail with the following message in the log:
-```
-panic: Configure azure client fails. Please ensure federatedcredential has been created for identity XXXX.
-```
-The pod will reach running state once the federated credential is created.
-
-## Verify installation
-You can run the following commands to verify the installation of the controllers were successful.
-
-Check status of the Helm chart installations.
-
-```bash
-helm list -n kaito-workspace
-helm list -n gpu-provisioner
-```
-
-Check status of the `workspace`.
-
-```bash
-kubectl describe deploy kaito-workspace -n kaito-workspace
-```
-
-Check status of the `gpu-provisioner`.
-
-```bash
-kubectl describe deploy gpu-provisioner -n gpu-provisioner
-```
-
-## Troubleshooting
-If you see that the `gpu-provisioner` deployment is not running after some time, it's possible that some values incorrect in your `values.ovveride.yaml`.
-
-Run the following command to check `gpu-provisioner` pod logs for additional details.
-
-```bash
-kubectl logs --selector=app.kubernetes.io\/name=gpu-provisioner -n gpu-provisioner
-```
-
-## Clean up
-
-```bash
-helm uninstall gpu-provisioner -n gpu-provisioner
-helm uninstall kaito-workspace -n kaito-workspace
-```
diff --git a/website/versioned_docs/version-v0.5.0/intro.md b/website/versioned_docs/version-v0.5.0/intro.md
deleted file mode 100644
index 7c92b0b..0000000
--- a/website/versioned_docs/version-v0.5.0/intro.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: Introduction
-slug: /
----
-
-:::info What's NEW!
-
-Retrieval Augmented Generation (RAG) support is live! - KAITO RagEngine uses LlamaIndex and FAISS, learn about it [here](https://kaito-project.github.io/kaito/docs/rag)!
-**Latest Release:** July 2nd, 2025. KAITO v0.5.0.
-
-**First Release:** Nov 15th, 2023. KAITO v0.1.0.
-:::
-
-KAITO is an operator that automates the AI/ML model inference or tuning workload in a Kubernetes cluster.
-The target models are popular open-sourced large models such as [falcon](https://huggingface.co/tiiuae) and [phi-3](https://huggingface.co/docs/transformers/main/en/model_doc/phi3).
-
-## Key Features
-
-KAITO has the following key differentiations compared to most of the mainstream model deployment methodologies built on top of virtual machine infrastructures:
-
-- **Container-based Model Management**: Manage large model files using container images with an OpenAI-compatible server for inference calls
-- **Preset Configurations**: Avoid adjusting workload parameters based on GPU hardware with built-in configurations
-- **Multiple Runtime Support**: Support for popular inference runtimes including [vLLM](https://github.com/vllm-project/vllm) and [transformers](https://github.com/huggingface/transformers)
-- **Auto-provisioning**: Automatically provision GPU nodes based on model requirements
-- **Public Registry**: Host large model images in the public Microsoft Container Registry (MCR) when licenses allow
-
-Using KAITO, the workflow of onboarding large AI inference models in Kubernetes is largely simplified.
-
-## Architecture
-
-KAITO follows the classic Kubernetes Custom Resource Definition(CRD)/controller design pattern. Users manage a `workspace` custom resource which describes the GPU requirements and the inference or tuning specification. KAITO controllers automate the deployment by reconciling the `workspace` custom resource.
-
-![KAITO architecture](/img/arch.png)
-
-The above figure presents the KAITO architecture overview. Its major components consist of:
-
-- **Workspace controller**: Reconciles the `workspace` custom resource, creates `machine` custom resources to trigger node auto provisioning, and creates the inference or tuning workload (`deployment`, `statefulset` or `job`) based on the model preset configurations.
-- **Node provisioner controller**: The controller's name is *gpu-provisioner* in [gpu-provisioner helm chart](https://github.com/Azure/gpu-provisioner/tree/main/charts/gpu-provisioner). It uses the `machine` CRD originated from [Karpenter](https://sigs.k8s.io/karpenter) to interact with the workspace controller. It integrates with Azure Resource Manager REST APIs to add new GPU nodes to the AKS or AKS Arc cluster.
-
-:::note
-The [*gpu-provisioner*](https://github.com/Azure/gpu-provisioner) is an open sourced component. It can be replaced by other controllers if they support [Karpenter-core](https://sigs.k8s.io/karpenter) APIs.
-:::
-
-**NEW!** Starting with version v0.5.0, KAITO releases a new operator, **RAGEngine**, which is used to streamline the process of managing a Retrieval Augmented Generation(RAG) service.
-![KAITO RAGEngine architecture](/img/ragarch.png)
-
-
-As illustrated in the above figure, the **RAGEngine controller** reconciles the `ragengine` custom resource and creates a `RAGService` deployment. The `RAGService` provides the following capabilities:
-  - **Orchestration**: use [LlamaIndex](https://github.com/run-llama/llama_index) orchestrator.
-  - **Embedding**: support both local and remote embedding services, to embed queries and documents in the vector database.
-  - **Vector database**: support a built-in [faiss](https://github.com/facebookresearch/faiss) in-memory vector database. Remote vector database support will be added soon.
-  - **Backend inference**: support any OAI compatible inference service.
-
-The details of the service APIs can be found in this [document](./rag.md).
-
-
-## Getting Started
-
-👉 To get started, please see the [Workspace Installation Guide](installation) and the [RAGEngine Installation Guide](./rag.md)!
-
-👉 For a quick start tutorial, check out [Quick Start](quick-start)!
-
-## Community
-
-- **GitHub**: [kaito-project/kaito](https://github.com/kaito-project/kaito)
-- **Slack**: [Join our community](https://join.slack.com/t/kaito-z6a6575/shared_invite/zt-37gh89vw7-odHfqmPRc5oRnDG99SBJNA)
-- **Email**: [kaito-dev@microsoft.com](mailto:kaito-dev@microsoft.com)
diff --git a/website/versioned_docs/version-v0.5.0/tool-calling.md b/website/versioned_docs/version-v0.5.0/tool-calling.md
deleted file mode 100644
index 71f6d55..0000000
--- a/website/versioned_docs/version-v0.5.0/tool-calling.md
+++ /dev/null
@@ -1,209 +0,0 @@
----
-title: Tool Calling
----
-
-KAITO supports tool calling, allowing you to integrate external tools into your inference service. This feature enables the model to call APIs or execute functions based on the input it receives, enhancing its capabilities beyond text generation.
-
-## Requirements
-
-### Supported Inference Runtimes
-
-Currently, tool calling is only supported with the vLLM inference runtime.
-
-### Supported Models & Chat Templates
-
-Proper chat templates are required for tool calling. The following models and their corresponding chat templates are supported:
-
-| Model Family | Chat Templates                                                                                                                                             | Tool Parser in vLLM |
-|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------|
-| Phi 4        | [tool-chat-phi4-mini.jinja](https://github.com/kaito-project/kaito/blob/main/presets/workspace/inference/chat_templates/tool-chat-phi4-mini.jinja)         | `phi4_mini_json`    |
-| Llama 3      | [tool-chat-llama3.1-json.jinja](https://github.com/kaito-project/kaito/blob/main/presets/workspace/inference/chat_templates/tool-chat-llama3.1-json.jinja) | `llama3_json`       |
-| Mistral      | [tool-chat-mistral.jinja](https://github.com/kaito-project/kaito/blob/main/presets/workspace/inference/chat_templates/tool-chat-mistral.jinja)             | `mistral`           |
-
-
-### Inference Configurations
-
-Create the following ConfigMap before deploying KAITO workspace, with `tool-call-parser` and `chat-template` set to the appropriate values for your model:
-
-```yaml
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: tool-calling-inference-config
-data:
-  inference_config.yaml: |
-    # Maximum number of steps to find the max available seq len fitting in the GPU memory.
-    max_probe_steps: 6
-
-    vllm:
-      cpu-offload-gb: 0
-      swap-space: 4
-      tool-call-parser: "phi4_mini_json" | "llama3_json" | "mistral"
-      chat-template: "/workspace/chat_templates/<chat_template_name>.jinja"
-```
-
-In the Workspace configuration, set `.inference.config` to the name of the ConfigMap you created. For example:
-
-```yaml
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-phi-4-mini-tool-call
-resource:
-  instanceType: "Standard_NC24ads_A100_v4"
-  labelSelector:
-    matchLabels:
-      apps: phi-4
-inference:
-  preset:
-    name: phi-4-mini-instruct
-  config: tool-calling-inference-config
-```
-
-For more details on the inference configuration, refer to [vLLM tool calling documentation](https://docs.vllm.ai/en/latest/features/tool_calling.html).
-
-## Examples
-
-Port-forward the inference service to your local machine:
-
-```bash
-kubectl port-forward svc/workspace-phi-4-mini-tool-call 8000
-```
-
-### Named Function Calling
-
-```python
-from openai import OpenAI
-import json
-
-client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
-
-def get_weather(location: str, unit: str):
-    return f"Getting the weather for {location} in {unit}..."
-tool_functions = {"get_weather": get_weather}
-
-tools = [{
-    "type": "function",
-    "function": {
-        "name": "get_weather",
-        "description": "Get the current weather in a given location",
-        "parameters": {
-            "type": "object",
-            "properties": {
-                "location": {"type": "string", "description": "City and state, e.g., 'San Francisco, CA'"},
-                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
-            },
-            "required": ["location", "unit"]
-        }
-    }
-}]
-
-response = client.chat.completions.create(
-    model=client.models.list().data[0].id,
-    messages=[{"role": "user", "content": "What's the weather like in San Francisco?"}],
-    tools=tools,
-    tool_choice="auto"
-)
-
-tool_call = response.choices[0].message.tool_calls[0].function
-print(f"Function called: {tool_call.name}")
-print(f"Arguments: {tool_call.arguments}")
-print(f"Result: {tool_functions[tool_call.name](**json.loads(tool_call.arguments))}")
-```
-
-Expected output:
-
-```
-Function called: get_weather
-Arguments: {"location": "San Francisco, CA", "unit": "fahrenheit"}
-Result: Getting the weather for San Francisco, CA in fahrenheit...
-```
-
-### Model Context Protocol (MCP)
-
-With the right client framework, inference workload provisioned by KAITO can also call external tools using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). This allows the model to integrate and share data with external tools, systems, and data sources.
-
-In the following example, we will use [uv](https://docs.astral.sh/uv/) to create a Python virtual environment and install the necessary dependencies for [AutoGen](https://microsoft.github.io/autogen/stable//index.html) to call the [DeepWiki](https://deepwiki.com/) MCP service and ask questions about the KAITO project.
-
-```bash
-mkdir kaito-mcp
-cd kaito-mcp
-# Create and activate a virtual environment
-uv venv create && source .venv/bin/activate
-# Install dependencies
-uv pip install "autogen-ext[openai]" "autogen-agentchat" "autogen-ext[mcp]"
-```
-
-Create a Python script `test.py` with the following content:
-
-```python
-import asyncio
-
-from autogen_agentchat.agents import AssistantAgent
-from autogen_agentchat.ui import Console
-from autogen_core import CancellationToken
-from autogen_core.models import ModelFamily, ModelInfo
-from autogen_ext.models.openai import OpenAIChatCompletionClient
-from autogen_ext.tools.mcp import (StreamableHttpMcpToolAdapter,
-                                   StreamableHttpServerParams)
-from openai import OpenAI
-
-
-async def main() -> None:
-    # Create server params for the remote MCP service
-    server_params = StreamableHttpServerParams(
-        url="https://mcp.deepwiki.com/mcp",
-        timeout=30.0,
-        terminate_on_close=True,
-    )
-
-    # Get the ask_question tool from the server
-    adapter = await StreamableHttpMcpToolAdapter.from_server_params(server_params, "ask_question")
-
-    model = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy").models.list().data[0].id
-    model_info: ModelInfo = {
-        "vision": False,
-        "function_calling": True,
-        "json_output": True,
-        "family": ModelFamily.UNKNOWN,
-        "structured_output": True,
-        "multiple_system_messages": True,
-    }
-
-    # Create an agent that can use the ask_question tool
-    model_client = OpenAIChatCompletionClient(base_url="http://localhost:8000/v1", api_key="dummy", model=model, model_info=model_info)
-    agent = AssistantAgent(
-        name="deepwiki",
-        model_client=model_client,
-        tools=[adapter],
-        system_message="You are a helpful assistant.",
-    )
-
-    await Console(
-        agent.run_stream(task="In the GitHub repository 'kaito-project/kaito', how many preset models are there?", cancellation_token=CancellationToken())
-    )
-
-
-if __name__ == "__main__":
-    asyncio.run(main())
-```
-
-To run the script, execute the following command in your terminal:
-
-```bash
-uv run test.py
-```
-
-Expected output:
-
-```
----------- TextMessage (user) ----------
-In the GitHub repository 'kaito-project/kaito', how many preset models are there?
----------- ToolCallRequestEvent (deepwiki) ----------
-[FunctionCall(id='chatcmpl-tool-4e22b15c32d34430b80078a3acc41f0d', arguments='{"repoName": "kaito-project/kaito", "question": "How many preset models are there?"}', name='ask_question')]
-Unknown SSE event: ping
----------- ToolCallExecutionEvent (deepwiki) ----------
-[FunctionExecutionResult(content='[{"type": "text", "text": "There are 16 preset models in the Kaito project.  These models are defined in the `supported_models.yaml` file  and registered programmatically within the codebase. ...", "annotations": null, "meta": null}]', name='ask_question', call_id='chatcmpl-tool-4e22b15c32d34430b80078a3acc41f0d', is_error=False)]
----------- ToolCallSummaryMessage (deepwiki) ----------
-[{"type": "text", "text": "There are 16 preset models in the Kaito project.  These models are defined in the `supported_models.yaml` file  and registered programmatically within the codebase. ...", "annotations": null, "meta": null}]
-```
diff --git a/website/versioned_docs/version-v0.5.1/aws.md b/website/versioned_docs/version-v0.5.1/aws.md
deleted file mode 100644
index 216392d..0000000
--- a/website/versioned_docs/version-v0.5.1/aws.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-title: AWS Deployment
----
-
-# Installation using AWS EKS cluster
-
-Before you begin, ensure you have the following tools installed:
-- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) to provision AWS resources
-- [Eksctl](https://eksctl.io/installation/) (>= v0.191.0) to create and manage clusters on EKS
-- [Helm](https://helm.sh) to install this operator
-- [kubectl](https://kubernetes.io/docs/tasks/tools/) to view Kubernetes resources
-
-## Create EKS Cluster
-If you do not already have an EKS cluster, run the following to create one:
-
-```bash
-cd ../.. #go back to main directory to use MAKE commands
-
-export AWS_CLUSTER_NAME=kaito-aws
-export AWS_REGION=us-west-2
-export AWS_PARTITION=aws
-export AWS_K8S_VERSION=1.30
-export KARPENTER_NAMESPACE=kube-system
-export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
-
-make deploy-aws-cloudformation
-make create-eks-cluster
-```
-
-If you already have an EKS cluster, connect to it using
-```bash
-aws eks update-kubeconfig --name $CLUSTER_NAME --region $AWS_REGION
-```
-
-## Install Karpenter Controller
-```bash
-make aws-karpenter-helm
-```
-
-## Install Workspace Controller
-```bash
-make aws-patch-install-helm
-```
-
-## Verify installation
-You can run the following commands to verify the installation of the controllers were successful.
-
-Check status of the Helm chart installations.
-
-```bash
-helm list -n default
-```
-
-Check status of `workspace`.
-
-```bash
-kubectl describe deploy kaito-workspace -n kaito-workspace
-```
-
-Check status of `karpenter`.
-
-```bash
-kubectl describe deploy karpenter -n $KARPENTER_NAMESPACE
-```
-
-## Create a Workspace and start an inference service
-Once the KAITO and Karpenter controllers are installed, you can follow these commands to start a falcon-7b inference service.
-
-```bash
-$ export kaito_workspace_aws="../../examples/inference/kaito_workspace_falcon_7b_aws.yaml"
-$ cat $kaito_workspace_aws
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: aws-workspace
-resource:
-  instanceType: "g5.4xlarge"
-  labelSelector:
-    matchLabels:
-      apps: falcon-7b
-inference:
-  preset:
-    name: "falcon-7b"
-
-$ kubectl apply -f $kaito_workspace_aws
-```
-
-The workspace status can be tracked by running the following command. When the `WORKSPACESUCCEEDED` column becomes `True`, the model has been deployed successfully.
-
-```sh
-$ kubectl get workspace workspace-falcon-7b
-NAME                  INSTANCE            RESOURCEREADY   INFERENCEREADY    JOBSTARTED  WORKSPACESUCCEEDED  AGE
-aws-workspace         g5.4xlarge          True            True              True        True                10m
-```
-
-Next, one can find the inference service's cluster ip and use a temporal `curl` pod to test the service endpoint in the cluster.
-
-```sh
-$ kubectl get svc aws-workspace
-NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)            AGE
-aws-workspace         ClusterIP   <CLUSTERIP>  <none>        80/TCP,29500/TCP   10m
-
-export CLUSTERIP=$(kubectl get svc aws-workspace -o jsonpath="{.spec.clusterIPs[0]}")
-$ kubectl run -it --rm --restart=Never curl --image=curlimages/curl -- curl -X POST http://$CLUSTERIP/chat -H "accept: application/json" -H "Content-Type: application/json" -d "{\"prompt\":\"YOUR QUESTION HERE\"}"
-```
\ No newline at end of file
diff --git a/website/versioned_docs/version-v0.5.1/contributing.md b/website/versioned_docs/version-v0.5.1/contributing.md
deleted file mode 100644
index 3aec963..0000000
--- a/website/versioned_docs/version-v0.5.1/contributing.md
+++ /dev/null
@@ -1,100 +0,0 @@
----
-title: Contributing
----
-
-This project welcomes contributions and suggestions.
-
-KAITO has adopted the [Cloud Native Compute Foundation Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). For more information see the [KAITO Code of Conduct](https://github.com/kaito-project/kaito/blob/main/CODE_OF_CONDUCT.md).
-
-## Development
-
-### Prerequisites
-
-- [Go 1.24](https://go.dev/doc/install) or later
-- [Tilt](https://tilt.dev/) for rapid, local development
-- [Docker](https://docs.docker.com/get-started/get-docker/) for building container images
-- Kubernetes cluster with Karpenter supported (e.g. AKS, EKS)
-
-### Getting Started
-
-#### 1. Fork and Clone the Repository
-
-```bash
-git clone git@github.com:<username>/kaito.git
-cd kaito
-```
-
-#### 2. Create `tilt-settings.yaml`
-
-Tilt streamlines Kubernetes development by automating image builds, deployments, and live updates. Instead of manually building and pushing images to a registry, Tilt handles this process and hot-reloads changes (Go binaries and CRD YAML files) in real-time.
-
-To configure Tilt, create a tilt-settings.yaml file in the repository root:
-
-```bash
-cat <<EOF > tilt-settings.yaml
-default_registry: <acr_name>.azurecr.io
-allowed_contexts:
-  - <context_name>
-cluster_name: <cluster_name>
-EOF
-```
-
-- `default_registry`: The container registry where Tilt pushes development images (e.g. `<acr_name>.azurecr.io`). Ensure you're logged in with `docker login` and have push/pull permissions.
-If you want to use Azure Container Registry (ACR), set up a new ACR instance and attach it to your AKS cluster with these commands:
-
-```bash
-export ACR_NAME=<acr_name>
-export RESOURCE_GROUP=<resource_group>
-export CLUSTER_NAME=<cluster_name>
-
-az acr -n $ACR_NAME create --resource-group $RESOURCE_GROUP --sku Basic
-az acr login -n $ACR_NAME
-az aks update -n $CLUSTER_NAME -g $RESOURCE_GROUP --attach-acr $ACR_NAME
-```
-
-- `allowed_contexts`: The Kubernetes contexts Tilt is permitted to use. List your available contexts with `kubectl config get-contexts`.
-
-- `cluster_name`: The name of your Kubernetes cluster. If omitted, Tilt defaults to your current kubectl context.
-
-#### 3. Start Tilt
-
-To start Tilt, run the following command in the root of the repository:
-
-```bash
-tilt up
-```
-
-<details>
-<summary>Example Output</summary>
-
-<!-- markdown-link-check-disable -->
-
-```bash
-Tilt started on http://localhost:10350/
-v0.34.0, built 2025-03-11
-
-(space) to open the browser
-(s) to stream logs (--stream=true)
-(t) to open legacy terminal mode (--legacy=true)
-(ctrl-c) to exit
-Opening browser: http://localhost:10350/
-```
-
-<!-- markdown-link-check-enable -->
-
-</details>
-
-<br/>
-
-![Tilt UI](/img/tilt.png)
-
-Tilt automates the following steps during `tilt up`:
-
-<!-- markdown-link-check-disable -->
-1. **Start the Tilt Server**: Runs locally at http://localhost:10350/ and opens the Tilt UI in your browser (press space).
-<!-- markdown-link-check-enable -->
-1. **Generate and Build**: Creates CRD YAML files (`make generate manifests`) and compiles the controller manager Go binary (`go build`), saving it to `./tilt_bin`.
-2. **Build Development Image**: Packages the binary into a container image and tag it with `<default_registry>/kaito/workspace:tilt-<hash>`), then pushes it to your container registry.
-3. **Apply CRDs**: Deploys CRDs to the cluster with `kubectl apply --server-side -f charts/kaito/crds/`.
-4. **Deploy Resources**: Renders the Helm chart (`helm template charts/kaito/workspace`) and applies it to the cluster, including the controller manager, webhooks, RBAC, and NVIDIA device plugin resources.
-5. **Live Updates**: Detects changes to the controller manager source code, rebuilds the binary, updates the container, and restarts the controller manager within the container without re-deploying the pod - all in ~30 seconds.
diff --git a/website/versioned_docs/version-v0.5.1/custom-model.md b/website/versioned_docs/version-v0.5.1/custom-model.md
deleted file mode 100644
index f6955b8..0000000
--- a/website/versioned_docs/version-v0.5.1/custom-model.md
+++ /dev/null
@@ -1,83 +0,0 @@
----
-title: Custom Model Integration
----
-
-## Option 1: Use Pre-Built Docker Image Without Model Weights
-If you want to avoid building a Docker image with model weights, use our pre-built reference image (`ghcr.io/kaito-project/kaito/llm-reference-preset:latest`). This image, built with [Dockerfile.reference](../../../examples/custom-model-integration/Dockerfile.reference), dynamically downloads model weights from HuggingFace at runtime, reducing the need to create and maintain custom images.
-
-
-- **[Sample Deployment YAML](../../../examples/custom-model-integration/reference-image-deployment.yaml)**
-
-
-## Option 2: Build a Custom Docker Image with Model Weights
-
-### Step 1: Clone the Repository
-
-```sh
-git clone https://github.com/kaito-project/kaito.git
-```
-
-### Step 2: Download Your Private/Custom Model Weights
-
-For example, assuming HuggingFace weights:
-```sh
-git lfs install
-git clone git@hf.co:<MODEL_ID>  # Example: git clone git@hf.co:bigscience/bloom
-# OR
-git clone https://huggingface.co/bigscience/bloom
-```
-
-Alternatively, use curl:
-```
-curl -sSL https://huggingface.co/bigscience/bloom/resolve/main/config.json?download=true -o config.json
-```
-
-More information on downloading models from HuggingFace can be found [here](https://huggingface.co/docs/hub/en/models-downloading).
-
-
-### Step 3: Log In to Your Container Registry
-
-Before pushing the Docker image, ensure you’re logged into the appropriate container registry. Here are general login methods depending on the registry you use:
-
-1. GitHub Container Registry (ghcr.io):
-```sh
-echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
-```
-Replace CR_PAT with your GitHub Personal Access Token and USERNAME with your GitHub username. This token should have the write:packages and read:packages permissions.
-
-2. Azure Container Registry (ACR): If you're using ACR:
-
-```sh
-az acr login --name <REGISTRY_NAME>
-```
-Replace `<REGISTRY_NAME>` with your Azure Container Registry name.
-
-3. Docker Hub or Other Container Registries:
-```sh
-docker login <REGISTRY_URL>
-```
-Enter your username and password when prompted. Replace `<REGISTRY_URL>` with your registry URL, such as `docker.io` for Docker Hub.
-
-
-### Step 4: Build Docker Image with Private/Custom Weights
-
-1. Set Environment Variables
-
-Before building the Docker image, set the relevant environment variables for the image name, version, and weights path:
-```sh
-export IMAGE_NAME="modelsregistry.azurecr.io/phi-3-mini-4k-instruct:0.0.1"
-export VERSION="0.0.1"
-export WEIGHTS_PATH="kaito/phi-3-mini-4k-instruct/weights"
-```
-
-2. Build and Push the Docker Image
-
-Navigate to the KAITO base directory and build the Docker image, ensuring the weights directory is included in the build context:
-```sh
-docker build -t <IMAGE_NAME> --file docker/presets/workspace/models/tfs/Dockerfile --build-arg WEIGHTS_PATH=<WEIGHTS_PATH> --build-arg MODEL_TYPE=text-generation --build-arg VERSION=<VERSION> .
-
-docker push <IMAGE_NAME>
-```
-
-### Step 5: Deploy
-Follow the [Custom Template](../../../examples/custom-model-integration/custom-deployment-template.yaml)
\ No newline at end of file
diff --git a/website/versioned_docs/version-v0.5.1/example_rag_client.py b/website/versioned_docs/version-v0.5.1/example_rag_client.py
deleted file mode 100644
index e2a5760..0000000
--- a/website/versioned_docs/version-v0.5.1/example_rag_client.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# Copyright (c) KAITO 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.
-
-import json
-
-import requests
-
-
-class KAITORAGClient:
-    """
-    A bare bones client for interacting with the RAGEngine API.
-    This client provides methods to index documents, query the engine,
-    update documents, delete documents, list documents, and manage indexes.
-    """
-
-    def __init__(self, base_url):
-        self.base_url = base_url
-        self.headers = {
-            "Content-Type": "application/json",
-            "Accept": "application/json",
-        }
-
-    def index_documents(self, index_name, documents):
-        """
-        Index documents in the RAGEngine.
-        documents: list of dicts, each with 'text' and optional 'metadata'
-        """
-        url = f"{self.base_url}/index"
-        payload = {"index_name": index_name, "documents": documents}
-        resp = requests.post(url, json=payload, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
-
-    def query(self, index_name, query, llm_temperature, llm_max_tokens, top_k=5):
-        """
-        Query the RAGEngine.
-        query: str, the query text
-        top_k: int, number of results to return
-        metadata: optional dict, additional metadata for the query
-        """
-        url = f"{self.base_url}/query"
-        payload = {
-            "index_name": index_name,
-            "query": query,
-            "top_k": top_k,
-            "llm_params": {
-                "temperature": llm_temperature,
-                "max_tokens": llm_max_tokens,
-            },
-        }
-        resp = requests.post(url, json=payload, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
-
-    def update_documents(self, index_name, documents):
-        """
-        Update a document in the RAGEngine.
-        documents: list of dicts, each with 'id', 'text', and optional 'metadata'
-        """
-        url = f"{self.base_url}/indexes/{index_name}/documents"
-        payload = {"documents": documents}
-        resp = requests.post(url, json=payload, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
-
-    def delete_documents(self, index_name, document_ids):
-        """
-        Delete a document from the RAGEngine by its ID.
-        """
-        url = f"{self.base_url}/indexes/{index_name}/documents/delete"
-        payload = {"doc_ids": document_ids}
-        resp = requests.post(url, json=payload, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
-
-    def list_documents(self, index_name, metadata_filter, limit=10, offset=0):
-        """
-        List documents in the RAGEngine.
-        """
-        url = f"{self.base_url}/indexes/{index_name}/documents"
-        params = {"limit": limit, "offset": offset}
-        if metadata_filter:
-            params["metadata_filter"] = json.dumps(metadata_filter)
-        resp = requests.get(url, headers=self.headers, params=params)
-        resp.raise_for_status()
-        return resp.json()
-
-    def list_indexes(self):
-        """
-        List all indexes in the RAGEngine.
-        """
-        url = f"{self.base_url}/indexes"
-        resp = requests.get(url, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
-
-    def persist_index(self, index_name, path="/tmp"):
-        """
-        Persist an index in the RAGEngine.
-        """
-        url = f"{self.base_url}/persist/{index_name}"
-        params = {"path": path}
-        resp = requests.post(url, params=params, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
-
-    def load_index(self, index_name, path="/tmp", overwrite=True):
-        """
-        Load an index in the RAGEngine.
-        """
-        url = f"{self.base_url}/load/{index_name}"
-        params = {"path": path, "overwrite": overwrite}
-        resp = requests.post(url, params=params, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
-
-    def delete_index(self, index_name):
-        """
-        Delete an index from the RAGEngine.
-        """
-        url = f"{self.base_url}/indexes/{index_name}"
-        resp = requests.delete(url, headers=self.headers)
-        resp.raise_for_status()
-        return resp.json()
diff --git a/website/versioned_docs/version-v0.5.1/faq.md b/website/versioned_docs/version-v0.5.1/faq.md
deleted file mode 100644
index ba41b80..0000000
--- a/website/versioned_docs/version-v0.5.1/faq.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: FAQ
----
-
-### How do I ensure preferred nodes are correctly labeled for use in my workspace?
-
-For using preferred nodes, make sure the node has the label specified in the labelSelector under matchLabels. For example, if your labelSelector is:
-
-```yaml
-labelSelector:
-  matchLabels:
-    apps: falcon-7b
-```
-
-Then the node should have the label: `apps=falcon-7b`.
-
-### How to upgrade the existing deployment to use the latest model configuration?
-
-When using hosted public models, you can delete the existing inference workload (`Deployment` or `StatefulSet`) manually, and the workspace controller will create a new one with the latest preset configuration (e.g., the image version) defined in the current release.
-
-For private models, it is recommended to create a new workspace with a new image version in the Spec.
-
-### How to update model/inference parameters to override the KAITO Preset Configuration?
-
-KAITO provides a limited capability to override preset configurations for models that use `transformer` runtime manually.
-
-To update parameters for a deployed model, perform `kubectl edit` against the workload, which could be either a `StatefulSet` or `Deployment`.
-
-For example, to enable 4-bit quantization on a `falcon-7b-instruct` deployment:
-
-```bash
-kubectl edit deployment workspace-falcon-7b-instruct
-```
-
-Within the deployment specification, locate and modify the command field.
-
-**Original:**
-```bash
-accelerate launch --num_processes 1 --num_machines 1 --machine_rank 0 --gpu_ids all inference_api.py --pipeline text-generation --torch_dtype bfloat16
-```
-
-**Modified to enable 4-bit Quantization:**
-```bash
-accelerate launch --num_processes 1 --num_machines 1 --machine_rank 0 --gpu_ids all inference_api.py --pipeline text-generation --torch_dtype bfloat16 --load_in_4bit
-```
-
-Currently, we allow users to change the following parameters manually:
-
-- `pipeline`: For text-generation models this can be either `text-generation` or `conversational`.
-- `load_in_4bit` or `load_in_8bit`: Model quantization resolution.
-
-Should you need to customize other parameters, kindly file an issue for potential future inclusion.
-
-### What is the difference between instruct and non-instruct models?
-
-The main distinction lies in their intended use cases:
-
-- **Instruct models**: Fine-tuned versions optimized for interactive chat applications. They are typically the preferred choice for most implementations due to their enhanced performance in conversational contexts.
-- **Non-instruct (raw) models**: Designed for further fine-tuning with your own data.
diff --git a/website/versioned_docs/version-v0.5.1/inference.md b/website/versioned_docs/version-v0.5.1/inference.md
deleted file mode 100644
index b1694e0..0000000
--- a/website/versioned_docs/version-v0.5.1/inference.md
+++ /dev/null
@@ -1,258 +0,0 @@
----
-title: Inference
----
-
-This document presents how to use the KAITO `workspace` Custom Resource Definition (CRD) for model serving and serving with LoRA adapters.
-
-:::tip Multi-Node Inference
-For large models requiring multiple nodes, see the [Multi-Node Inference](./multi-node-inference.md) documentation.
-:::
-
-## Usage
-
-The basic usage for inference is simple. Users just need to specify the GPU SKU used for inference in the `resource` spec and one of the KAITO supported model name in the `inference` spec in the `workspace` custom resource. For example,
-
-```yaml
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-falcon-7b
-resource:
-  instanceType: "Standard_NC24ads_A100_v4"
-  labelSelector:
-    matchLabels:
-      apps: falcon-7b
-inference:
-  preset:
-    name: "falcon-7b"
-```
-
-If a user runs KAITO in an on-premise Kubernetes cluster where GPU SKUs are unavailable, the GPU nodes can be pre-configured. The user should ensure that the corresponding vendor-specific GPU plugin is installed successfully in every prepared node, i.e. the node status should report a non-zero GPU resource in the allocatable field. For example:
-
-```
-$ kubectl get node $NODE_NAME -o json | jq .status.allocatable
-{
-  "cpu": "XXXX",
-  "ephemeral-storage": "YYYY",
-  "hugepages-1Gi": "0",
-  "hugepages-2Mi": "0",
-  "memory": "ZZZZ",
-  "nvidia.com/gpu": "1",
-  "pods": "100"
-}
-```
-
-Next, the user needs to add the node names in the `preferredNodes` field in the `resource` spec. As a result, the KAITO controller will skip the steps for GPU node provisioning and use the prepared nodes to run the inference workload.
-
-:::warning
-The node objects of the preferred nodes need to contain the same matching labels as specified in the `resource` spec. Otherwise, the KAITO controller would not recognize them.
-:::
-
-### Inference runtime selection
-
-KAITO now supports both [vLLM](https://github.com/vllm-project/vllm) and [transformers](https://github.com/huggingface/transformers) runtime. `vLLM` provides better serving latency and throughput. `transformers` provides more compatibility with models in the Huggingface model hub.
-
-From KAITO v0.4.0, the default runtime is switched to `vLLM`. If you want to use `transformers` runtime, you can specify the runtime in the `inference` spec using an annotation. For example,
-
-```yaml
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-falcon-7b
-  annotations:
-    kaito.sh/runtime: "transformers"
-resource:
-  instanceType: "Standard_NC24ads_A100_v4"
-  labelSelector:
-    matchLabels:
-      apps: falcon-7b
-inference:
-  preset:
-    name: "falcon-7b"
-```
-
-:::note Multi-Node Support
-Multi-node distributed inference is currently supported only with the vLLM runtime. For details on configuring multi-node deployments, see [Multi-Node Inference](./multi-node-inference.md).
-:::
-
-### Inference with custom parameters
-
-Users can customize vLLM runtime parameters by creating a ConfigMap containing an `inference_config.yaml` file and referencing it in the workspace spec. For example:
-
-```yaml
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  namespace: myns
-  name: my-inference-params
-data:
-  inference_config.yaml: |
-    max_probe_steps: 6
-    vllm:
-      gpu-memory-utilization: 0.95  # Controls GPU memory usage (0.0-1.0)
-      tensor-parallel-size: 2        # Number of GPUs for tensor parallelism
-      max-model-len: 131072         # Maximum sequence length
-      swap-space: 4                 # CPU swap space in GB
-      cpu-offload-gb: 0             # Amount of GPU memory to offload to CPU
----
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  namespace: myns
-  name: workspace-example
-resource:
-  instanceType: "Standard_NC24ads_A100_v4"
-  labelSelector:
-    matchLabels:
-      apps: example
-inference:
-  preset:
-    name: "example-model"
-  config: "my-inference-params"  # Reference to ConfigMap name
-```
-
-Key vLLM parameters include:
-- `gpu-memory-utilization`: Controls fraction of GPU memory allocated (between 0.0 and 1.0)
-- `tensor-parallel-size`: Number of GPUs to use for tensor parallelism
-- `max-model-len`: Maximum sequence length the model can handle
-
-For the complete list of vLLM parameters, refer to the [vLLM documentation](https://docs.vllm.ai/en/latest/serving/engine_args.html).
-
-### Inference with LoRA adapters
-
-KAITO also supports running the inference workload with LoRA adapters produced by [model fine-tuning jobs](./tuning.md). Users can specify one or more adapters in the `adapters` field of the `inference` spec. For example,
-
-```yaml
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-falcon-7b
-resource:
-  instanceType: "Standard_NC24ads_A100_v4"
-  labelSelector:
-    matchLabels:
-      apps: falcon-7b
-inference:
-  preset:
-    name: "falcon-7b"
-  adapters:
-    - source:
-        name: "falcon-7b-adapter"
-        image:  "<YOUR_IMAGE>"
-      strength: "0.2"
-```
-Currently, only images are supported as adapter sources. The `strength` field specifies the multiplier applied to the adapter weights relative to the raw model weights.
-
-**Note:** When building a container image for an existing adapter, ensure all adapter files are copied to the **/data** directory inside the container.
-
-For detailed `InferenceSpec` API definitions, refer to the [documentation](https://github.com/kaito-project/kaito/blob/2ccc93daf9d5385649f3f219ff131ee7c9c47f3e/api/v1alpha1/workspace_types.go#L75).
-
-### Inference API
-
-The OpenAPI specification for the inference API is available at [vLLM API](../../../presets/workspace/inference/vllm/api_spec.json), [transformers API](../../../presets/workspace/inference/text-generation/api_spec.json).
-
-#### vLLM inference API
-
-vLLM supports OpenAI-compatible inference APIs. Check [here](https://docs.vllm.ai/en/stable/serving/openai_compatible_server.html) for more details.
-
-#### Transformers inference API
-
-The inference service endpoint is `/chat`.
-
-**basic example**
-```
-curl -X POST "http://<SERVICE>:80/chat" -H "accept: application/json" -H "Content-Type: application/json" -d '{"prompt":"YOUR_PROMPT_HERE"}'
-```
-
-**example with full configurable parameters**
-```
-curl -X POST \
-    -H "accept: application/json" \
-    -H "Content-Type: application/json" \
-    -d '{
-        "prompt":"YOUR_PROMPT_HERE",
-        "return_full_text": false,
-        "clean_up_tokenization_spaces": false,
-        "prefix": null,
-        "handle_long_generation": null,
-        "generate_kwargs": {
-                "max_length":200,
-                "min_length":0,
-                "do_sample":true,
-                "early_stopping":false,
-                "num_beams":1,
-                "num_beam_groups":1,
-                "diversity_penalty":0.0,
-                "temperature":1.0,
-                "top_k":10,
-                "top_p":1,
-                "typical_p":1,
-                "repetition_penalty":1,
-                "length_penalty":1,
-                "no_repeat_ngram_size":0,
-                "encoder_no_repeat_ngram_size":0,
-                "bad_words_ids":null,
-                "num_return_sequences":1,
-                "output_scores":false,
-                "return_dict_in_generate":false,
-                "forced_bos_token_id":null,
-                "forced_eos_token_id":null,
-                "remove_invalid_values":null
-            }
-        }' \
-        "http://<SERVICE>:80/chat"
-```
-
-**parameters**
-- `prompt`: The initial text provided by the user, from which the model will continue generating text.
-- `return_full_text`: If False only generated text is returned, else full text is returned.
-- `clean_up_tokenization_spaces`: True/False, determines whether to remove potential extra spaces in the text output.
-- `prefix`: Prefix added to the prompt.
-- `handle_long_generation`: Provides strategies to address generations beyond the model's maximum length capacity.
-- `max_length`: The maximum total number of tokens in the generated text.
-- `min_length`: The minimum total number of tokens that should be generated.
-- `do_sample`: If True, sampling methods will be used for text generation, which can introduce randomness and variation.
-- `early_stopping`: If True, the generation will stop early if certain conditions are met, for example, when a satisfactory number of candidates have been found in beam search.
-- `num_beams`: The number of beams to be used in beam search. More beams can lead to better results but are more computationally expensive.
-- `num_beam_groups`: Divides the number of beams into groups to promote diversity in the generated results.
-- `diversity_penalty`: Penalizes the score of tokens that make the current generation too similar to other groups, encouraging diverse outputs.
-- `temperature`: Controls the randomness of the output by scaling the logits before sampling.
-- `top_k`: Restricts sampling to the k most likely next tokens.
-- `top_p`: Uses nucleus sampling to restrict the sampling pool to tokens comprising the top p probability mass.
-- `typical_p`: Adjusts the probability distribution to favor tokens that are "typically" likely, given the context.
-- `repetition_penalty`: Penalizes tokens that have been generated previously, aiming to reduce repetition.
-- `length_penalty`: Modifies scores based on sequence length to encourage shorter or longer outputs.
-- `no_repeat_ngram_size`: Prevents the generation of any n-gram more than once.
-- `encoder_no_repeat_ngram_size`: Similar to `no_repeat_ngram_size` but applies to the encoder part of encoder-decoder models.
-- `bad_words_ids`: A list of token ids that should not be generated.
-- `num_return_sequences`: The number of different sequences to generate.
-- `output_scores`: Whether to output the prediction scores.
-- `return_dict_in_generate`: If True, the method will return a dictionary containing additional information.
-- `pad_token_id`: The token ID used for padding sequences to the same length.
-- `eos_token_id`: The token ID that signifies the end of a sequence.
-- `forced_bos_token_id`: The token ID that is forcibly used as the beginning of a sequence token.
-- `forced_eos_token_id`: The token ID that is forcibly used as the end of a sequence when max_length is reached.
-- `remove_invalid_values`: If True, filters out invalid values like NaNs or infs from model outputs to prevent crashes.
-
-# Inference workload
-
-Depending on whether the specified model supports distributed inference or not, the KAITO controller will choose to use either Kubernetes **apps.deployment** workload (by default) or Kubernetes **apps.statefulset** workload (if the model supports distributed inference) to manage the inference service, which is exposed using a Cluster-IP type of Kubernetes `service`.
-
-For multi-node distributed inference, KAITO uses StatefulSets to ensure stable pod identity and coordination between leader and worker pods. See [Multi-Node Inference](./multi-node-inference.md) for detailed architecture information.
-
-When adapters are specified in the `inference` spec, the KAITO controller adds an initcontainer for each adapter in addition to the main container. The pod structure is shown in Figure 1.
-
-![KAITO inference service pod structure](/img/kaito-inference-adapter.png)
-
-If an image is specified as the adapter source, the corresponding initcontainer uses that image as its container image. These initcontainers ensure all adapter data is available locally before the inference service starts. The main container uses a supported model image, launching the [inference_api.py](../../../presets/workspace/inference/text-generation/inference_api.py) script.
-
-All containers share local volumes by mounting the same `EmptyDir` volumes, avoiding file copies between containers.
-
-## Workload update
-
-To update the `adapters` field in the `inference` spec, users can modify the `workspace` custom resource. The KAITO controller will apply the changes, triggering a workload deployment update. This will recreate the inference service pod, resulting in a brief service downtime. Once the new adapters are merged with the raw model weights and loaded into GPU memory, the service will resume.
-
-
-# Troubleshooting
-
-TBD
\ No newline at end of file
diff --git a/website/versioned_docs/version-v0.5.1/kaito-on-byo-gpu-nodes.md b/website/versioned_docs/version-v0.5.1/kaito-on-byo-gpu-nodes.md
deleted file mode 100644
index 054871a..0000000
--- a/website/versioned_docs/version-v0.5.1/kaito-on-byo-gpu-nodes.md
+++ /dev/null
@@ -1,256 +0,0 @@
----
-title: Bring Your Own GPU Nodes
----
-
-This guide walks you through deploying KAITO on a Kubernetes cluster with self-provisioned GPU nodes.
-
-## Prerequisites
-
-If you are following the demo as is then you would need access to an Azure account.
-
-### Tools
-
-- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
-- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
-- [Helm](https://helm.sh/docs/intro/install/)
-- [jq](https://jqlang.github.io/jq/download/)
-
-## Set up a Kubernetes cluster with GPU nodes
-
-:::note
-If you already have a Kubernetes cluster, you can skip this section.
-:::
-
-For the sake of this guide, we will create an Azure Kubernetes Service (AKS) cluster.
-
-### Environment variables
-
-Make necessary changes to the following environment variables and copy paste them into your terminal:
-
-```bash
-export LOCATION="southcentralus"
-export RESOURCE_GROUP="kaito-rg"
-export AKS_RG="${RESOURCE_GROUP}-aks"
-export CLUSTER_NAME="kaito"
-export AKS_WORKER_USER_NAME="azuser"
-export SSH_KEY=~/.ssh/id_rsa.pub
-export GPU_NODE_SIZE="Standard_NC24ads_A100_v4"
-export GPU_NODE_COUNT=1
-export GPU_NODE_POOL_NAME="gpunodes"
-```
-
-### Create a resource group
-
-Run the following command to create a resource group:
-
-```bash
-az group create \
-    --name "${RESOURCE_GROUP}" \
-    --location "${LOCATION}"
-```
-
-### Create an Azure Kubernetes Service (AKS) cluster
-
-Run the following command to create an AKS cluster:
-
-```bash
-az aks create \
-    --resource-group "${RESOURCE_GROUP}" \
-    --node-resource-group "${AKS_RG}" \
-    --name "${CLUSTER_NAME}" \
-    --enable-oidc-issuer \
-    --enable-workload-identity \
-    --enable-managed-identity \
-    --node-count 1 \
-    --location "${LOCATION}" \
-    --ssh-key-value "${SSH_KEY}" \
-    --admin-username "${AKS_WORKER_USER_NAME}" \
-    --os-sku Ubuntu
-```
-
-### Add GPU nodes
-
-Your GPU nodes should be prepared for workload deployment by installing the GPU driver and k8s device plugin, specific to your NVIDIA or AMD GPU. The [NVIDIA GPU Operator](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/overview.html) and [AMD GPU Operator](https://instinct.docs.amd.com/projects/gpu-operator/en/latest/overview.html) are useful to automate the management and installation of both GPU software components. In the below example, the `gpu-driver` API field is set to `none` at create time of the GPU node pool on Azure Kubernetes Service (AKS) to allow for installation of the NVIDIA GPU Operator:
-
-:::warning
-On AKS GPU-enabled node pools, the `gpu-driver` API field should be set to `none`, to avoid duplicate installation of the GPU driver and/or unexpected conflicts on your GPU nodes. You can learn more [here](https://learn.microsoft.com/azure/aks/gpu-cluster?tabs=add-ubuntu-gpu-node-pool#skip-gpu-driver-installation).
-:::
-
-Run the following command to add GPU node to the AKS cluster:
-
-```bash
-az aks nodepool add \
-    --name "${GPU_NODE_POOL_NAME}" \
-    --resource-group "${RESOURCE_GROUP}" \
-    --cluster-name "${CLUSTER_NAME}" \
-    --node-count "${GPU_NODE_COUNT}" \
-    --node-vm-size "${GPU_NODE_SIZE}" \
-    --gpu-driver none
-```
-
-### Download kubeconfig
-
-Run the following command to download the kubeconfig file:
-
-```bash
-az aks get-credentials \
-    --resource-group "${RESOURCE_GROUP}" \
-    --name "${CLUSTER_NAME}"
-```
-
-## Prepare the Kubernetes cluster for GPU workloads
-
-### Install the NVIDIA GPU operator
-
-:::note
-If you have already set up your Kubernetes cluster with the NVIDIA GPU operator, you can skip the following steps for installation.
-:::
-
-Run the following commands to create a namespace for the GPU operator:
-
-```bash
-kubectl create ns gpu-operator
-kubectl label --overwrite ns gpu-operator pod-security.kubernetes.io/enforce=privileged
-```
-
-Run the following commands to install the GPU operator:
-
-```bash
-helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
-helm repo update
-
-helm install \
-    --wait \
-    --generate-name \
-    -n gpu-operator \
-    --create-namespace \
-    nvidia/gpu-operator
-```
-
-Ensure that the GPU operator is installed by running the following command:
-
-```bash
-kubectl -n gpu-operator wait pod \
-    --for=condition=Ready \
-    -l app.kubernetes.io/component=gpu-operator \
-    --timeout=300s
-```
-
-Finally ensure that the `nvidia` runtime class is created by running the following command:
-
-```bash
-kubectl get runtimeclass nvidia
-```
-
-A typical output would look like this:
-
-```bash
-$ kubectl get runtimeclass nvidia
-NAME     HANDLER   AGE
-nvidia   nvidia    16m
-```
-
-### Label the GPU nodes
-
-We need to label the GPU nodes `apps=gpu`, so that the KAITO workspace controller can schedule the inference workloads on these nodes. If you are following along the guide, you can run the following command to label the GPU nodes:
-
-```bash
-kubectl get nodes \
-    -l agentpool="${GPU_NODE_POOL_NAME}" \
-    -o name | \
-    xargs -I {} \
-    kubectl label --overwrite {} apps=gpu
-```
-
-:::tip
-If you have used a different set up to create the GPU nodes, you can label the nodes manually by running the following command: `kubectl label node <node-name> apps=gpu`.
-:::
-
-## Install KAITO on the Kubernetes cluster
-
-Run the following command to install KAITO:
-
-```bash
-helm install workspace \
-    ./charts/kaito/workspace \
-    --namespace kaito-workspace \
-    --create-namespace
-```
-
-Ensure that kaito is installed by running the following command:
-
-```bash
-kubectl -n kaito-workspace wait pod \
-    --for=condition=Ready \
-    -l app.kubernetes.io/instance=workspace \
-    --timeout=300s
-```
-
-## Deploying a model
-
-### Deploy a workspace with a GPU model
-
-To deploy a workspace with a GPU model, run the following command:
-
-```yaml
-cat <<EOF | kubectl apply -f -
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-falcon-7b
-resource:
-  instanceType: "${GPU_NODE_SIZE}"
-  labelSelector:
-    matchLabels:
-      apps: gpu
-inference:
-  preset:
-    name: "falcon-7b"
-EOF
-```
-
-:::note
-In the above configuration you can see we have used a node labelSelector value as `apps: gpu`, this is the same label we have applied when we added the GPU node pool earlier.
-:::
-
-Ensure that the workspace is ready by running the following command:
-
-```bash
-kubectl get workspace workspace-falcon-7b
-```
-
-A typical output would look like this:
-
-```bash
-$ kubectl get workspace workspace-falcon-7b
-NAME                  INSTANCE                   RESOURCEREADY   INFERENCEREADY   JOBSTARTED   WORKSPACESUCCEEDED   AGE
-workspace-falcon-7b   Standard_NC24ads_A100_v4   True            True                          True                 16m
-```
-
-### Use the workspace
-
-Run the following command to find the cluster IP to send the request to:
-
-```bash
-export CLUSTERIP=$(kubectl get \
-    svc workspace-falcon-7b \
-    -o jsonpath="{.spec.clusterIPs[0]}")
-```
-
-Let's send a request to the workspace to get an inference response. Modify the prompt as you see fit:
-
-```bash
-export QUESTION="What are LLMs?"
-```
-
-Run the following command to send the request:
-
-```bash
-kubectl run -it --rm \
-    --restart=Never \
-    curl --image=curlimages/curl \
-    -- curl -X POST http://$CLUSTERIP/chat \
-    -H "accept: application/json" \
-    -H "Content-Type: application/json" \
-    -d "{\"prompt\":\"${QUESTION}\"}" | jq
-```
diff --git a/website/versioned_docs/version-v0.5.1/kaito-oom-prevention.md b/website/versioned_docs/version-v0.5.1/kaito-oom-prevention.md
deleted file mode 100644
index c2e6237..0000000
--- a/website/versioned_docs/version-v0.5.1/kaito-oom-prevention.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: OOM Prevention
----
-
-Large Language Model (LLM) inference typically requires significant GPU memory. Unlike traditional virtual memory management in operating systems, CUDA does not support GPU memory swapping. This limitation often leads to CUDA Out-Of-Memory (OOM) errors when GPU memory is insufficient for inference requests. Addressing GPU OOM issues is one of the most challenging aspects of LLM deployment and tuning.
-
-This document explains the mechanisms KAITO implements to prevent CUDA OOM errors in inference workspaces. Since KAITO uses vLLM as its default inference engine, most of the discussion focuses on vLLM use cases.
-
-## Background
-
-The following diagram illustrates the GPU memory layout when serving inference requests using the vLLM engine:
-
-![GPU memory layout](/img/gpumemlayout.png)
-
-The GPU memory is divided into three main components:
-
-1. **Model Parameters**: These are the model weights (loaded from weight files, with or without quantization) that remain in GPU memory throughout the server's lifecycle.
-
-2. **vLLM Managed KV Cache**: The vLLM cache manager attempts to allocate all remaining GPU memory for KV cache blocks to maximize memory utilization. To prevent the cache manager from allocating too much memory, vLLM provides a `gpu-memory-utilization` configuration parameter. This parameter specifies the fraction of GPU memory to be used for the model inference, ranging from 0 to 1. For example, a value of 0.9 reserves 10% of GPU memory for other operations.
-
-3. **Other Memory**: A small portion of memory is reserved for ephemeral operations such as model activation.
-
-The KV cache usage is dynamic and roughly proportional to the context length (the total length of input and output tokens). vLLM requires the KV cache to be sufficient for the full context when serving a request. To enforce this constraint, vLLM uses a `max-model-len` parameter, which defaults to the model's maximum context length (e.g., 128K for Phi-3 models) if not specified. During startup, vLLM performs an internal probing test using a fake request with the specified `max-model-len`. If the configured KV cache cannot handle this request, the vLLM engine fails to start. After startup, any inference request exceeding the `max-model-len` will fail.
-
-## OOM Scenarios and KAITO's Solutions
-
-Based on the above understanding, we identify and address the following potential OOM scenarios:
-
-### Scenario 1: Insufficient GPU Memory for Model Parameters
-
-Loading model parameters into GPU memory is a fundamental requirement for any LLM engine. KAITO records the required memory size for each supported model based on 16-bit floating-point precision, as documented in the model's HuggingFace documentation. The validation webhook checks if the specified GPU SKU has sufficient aggregated memory to meet these requirements. However, this check is skipped for SKUs not in KAITO's built-in SKU list.
-
-> **Note**: KAITO allows users to modify the default vLLM configuration through a custom ConfigMap, which may include model quantization options. In such cases, the webhook's resource check might be overly restrictive because the actual memory footprint will be smaller if 4-bit or 8-bit quantization is enabled. Users can bypass this check by adding the annotation `kaito.sh/bypass-resource-checks: "true"` to the workspace custom resource when using quantized models on GPUs with limited memory.
-
-### Scenario 2: Insufficient Memory for Other Operations
-
-Setting a high `gpu-memory-utilization` value maximizes GPU memory utilization but can lead to OOM errors during operations like model activation if insufficient memory remains. Since different GPU SKUs have different GPU memory sizes, using a fixed percentage threshold for `gpu-memory-utilization` is suboptimal. KAITO dynamically adjusts the `gpu-memory-utilization` based on the GPU SKU's memory size, ensuring a smaller threshold for GPU SKUs with less than 20GB memory and a larger threshold (capped at 95%) for GPU SKUs with larger memory.
-
-### Scenario 3: KV Cache Size Insufficient for `max-model-len`
-
-This issue commonly occurs with models supporting large context windows (e.g., 128K). When users set a high `max-model-len` or use models with large default context windows, vLLM's internal probing test may fail on GPUs with limited memory. Typically, finding the optimal `max-model-len` to maximize KV cache utilization without causing OOM errors requires tedious manual searches.
-
-To simplify this process, KAITO implements an external probing test using a binary search algorithm to find a near-optimal `max-model-len` based on available GPU memory. While this test slightly increases service startup time, it eliminates the need for manual tuning. Users can find the calculated `max-model-len` in the inference service pod logs.
-
-> **Note**: The external probing test currently only supports SKUs with a single GPU instance. For SKUs with multiple GPU instances and small per-instance memory (e.g., `Standard_NC12s_v3` with two 16GB v100 instances), users must manually specify the `max-model-len` in the workspace's ConfigMap. The validation webhook enforces this requirement.
-
-## Generic Memory Usage Reduction
-
-Benchmark tests demonstrate that enabling `expandable_segments=true` in PYTORCH_CUDA_ALLOC_CONF helps reduce memory fragmentation and peak memory consumption, particularly for tuning jobs. KAITO enables this flag by default in workspace pods.
diff --git a/website/versioned_docs/version-v0.5.1/model-as-oci-artifacts.md b/website/versioned_docs/version-v0.5.1/model-as-oci-artifacts.md
deleted file mode 100644
index 73a3b26..0000000
--- a/website/versioned_docs/version-v0.5.1/model-as-oci-artifacts.md
+++ /dev/null
@@ -1,189 +0,0 @@
----
-title: Model As OCI Artifacts
-description: Efficiently distribute Large Language Models using Open Container Initiative (OCI) Artifacts
----
-
-# Model As OCI Artifacts
-
-KAITO efficiently distributes Large Language Models (LLMs) by leveraging Open Container Initiative (OCI) Artifacts to package and deploy model weights separately from the inference runtime. This architecture provides superior performance, scalability, and maintainability for large language model deployments.
-
-## Overview
-
-KAITO uses a split architecture approach for model distribution:
-
-- **Base Images**: Contain the inference runtime and dependencies
-- **OCI Artifacts**: Contain the model weights and configuration files stored separately in OCI registries
-
-This design choice enables KAITO to optimize both build and deployment performance while maintaining compatibility with standard container infrastructure. The model weights are downloaded as OCI artifacts during pod initialization, providing faster deployment times and more efficient resource utilization compared to traditional monolithic container images.
-
-## Design Rationale
-
-KAITO chose the OCI Artifacts approach to address fundamental challenges in large language model distribution and deployment:
-
-### Build Efficiency
-
-By separating model weights from base images, KAITO eliminates redundant rebuilds:
-
-- **Reduced Build Time**: Model images only need to be built once and can be reused across base image updates
-- **Efficient Build Context**: Large model files are not included in Docker build context, avoiding unnecessary transfers
-- **Resource Optimization**: Build processes for large models like Falcon-40B are reduced from nearly 2 hours to minutes
-
-### Deployment Performance
-
-KAITO's OCI Artifacts approach optimizes the deployment pipeline:
-
-- **Concurrent Downloads**: Model weights can be downloaded in parallel, improving bandwidth utilization
-- **Advanced Compression**: Zstd compression provides better decompression performance than traditional gzip
-- **Optimized Unpacking**: Separate artifact handling reduces serialization bottlenecks during pod startup
-
-#### Understanding Container Performance
-
-KAITO's design optimizes all phases of the container pull process:
-
-1. **Downloading** layer data from the registry
-2. **Decompressing** the layer if necessary  
-3. **Checking** sha256 digest
-4. **Unpacking** the layer, applying changes to the snapshot
-
-KAITO addresses these performance characteristics by optimizing not just the download phase (30% of total time) but also the decompression, validation, and unpacking phases through its OCI Artifacts approach.
-
-## How KAITO Uses OCI Artifacts
-
-### 1. Efficient Build Process
-
-KAITO uses [ORAS](https://github.com/oras-project/oras) to package model weights and configuration files directly into OCI artifacts without requiring large build contexts. This approach achieves the same distribution goals as traditional Docker builds but with significantly improved efficiency.
-
-### 2. Optimized Compression
-
-KAITO employs zstd compression instead of gzip, providing superior decompression performance specifically optimized for large model weight files.
-
-### 3. Split Architecture Design
-
-KAITO implements a two-component architecture:
-
-- **Base Image**: Contains the inference runtime and dependencies
-- **OCI Artifacts**: Contains the model weights and configuration files
-
-This architectural separation enables model images to be built once and reused across multiple base image updates, significantly reducing maintenance overhead.
-
-## When to Use OCI Artifacts
-
-KAITO automatically uses OCI Artifacts for supported models, providing benefits in several scenarios:
-
-### Recommended Use Cases
-
-- **Large Language Models**: Models with weights exceeding 1GB benefit significantly from the optimized distribution
-- **Frequent Updates**: Environments with regular base image updates for security patches or feature additions
-- **Multi-Model Deployments**: When deploying multiple model variants that share similar runtime requirements
-- **Resource-Constrained Environments**: Clusters where build time and bandwidth optimization are critical
-
-### Performance Considerations
-
-The OCI Artifacts approach provides the most benefit when:
-- Model weights are large relative to the runtime container
-- Network bandwidth is limited or expensive
-- Build infrastructure resources are constrained
-- Deployment frequency is high
-
-## Architecture
-
-### Model Weights Download Process
-
-The system uses an [initContainer](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) to download model weights as OCI artifacts using ORAS:
-
-```mermaid
-sequenceDiagram
-    participant CR as Container Runtime
-    participant IC as InitContainer
-    participant PV as Persistent Volume
-    participant IS as Inference Server
-
-    CR->>CR: Pull Base Image
-    CR->>IC: Start InitContainer
-    IC->>IC: Pull Model Weights as OCI Artifacts using ORAS
-    IC->>PV: Store Model Weights
-    IC->>CR: Complete
-    CR->>IS: Start Main Inference Container
-    IS->>PV: Load Model Weights
-    IS->>IS: Inference Server Ready
-```
-
-
-
-### OCI Artifacts vs OCI Images
-
-The Open Container Initiative (OCI) defines specifications and standards for container technologies, including the OCI Distribution Specification. OCI Image Manifests have a required field `config.mediaType` that differentiates between various types of artifacts.
-
-- **OCI Image**: A subset of OCI artifacts, accepting only specific mediatypes
-- **OCI Artifacts**: More general format that can contain various types of content including model weights
-
-ORAS (OCI Registry As Storage) is the tool used for managing OCI artifacts, including pushing, pulling, and handling metadata in OCI registries.
-
-### Alternative Approaches for Large Models
-
-For extremely large models that may not be practical to package as OCI artifacts due to size constraints, KAITO also supports:
-
-- **Direct Download from Hugging Face**: Models can be downloaded directly from Hugging Face Hub during pod initialization for models that exceed registry limitations
-- **External Model Caching**: Integration with external model caching solutions for improved performance with very large models
-
-## Compatibility
-
-### Container Runtimes
-
-- **CRI-O**: Supports general OCI artifacts
-- **Containerd**: Limited support for OCI artifacts (see [containerd issue](https://github.com/containerd/containerd/issues/11381#issuecomment-2917050414))
-
-### OCI Registries
-
-Most OCI registries support OCI artifacts. For a complete list of compatible registries, see the [ORAS compatibility documentation](https://oras.land/docs/compatible_oci_registries#registries-supporting-oci-artifacts).
-
-## Benefits
-
-### Performance Improvements
-
-- **Reduced Build Time**: Model images only need to be built once
-- **Faster Pulls**: Improved download concurrency and compression
-- **Better Resource Usage**: Optimized bandwidth utilization
-
-### Operational Benefits
-
-- **Simplified Maintenance**: Base image updates don't require rebuilding all model images
-- **Storage Efficiency**: Better compression ratios with zstd
-- **Scalability**: More efficient handling of large model files
-
-## Performance Results
-
-Testing on Standard_NC24s_v3 with phi4 model shows significant improvements:
-
-![Model as OCI Artifacts Evaluation](/img/model-as-oci-artifacts-evaluation.png)
-
-The evaluation compared different configurations:
-
-| Configuration | Description | Benefits |
-|---------------|-------------|----------|
-| Baseline (single-layer-tar-gz) | Current approach with all files in one layer | Simple, self-contained |
-| OCI Artifacts | Base image + separate model artifacts | Reduced build time, better performance |
-
-## Getting Started
-
-### Automatic Usage
-
-KAITO automatically uses OCI Artifacts for supported models when the feature is available. No additional configuration is required - simply deploy your model using standard KAITO workflows and the system will optimize the distribution automatically.
-
-### Verifying OCI Artifacts Usage
-
-To confirm that your model is using OCI Artifacts:
-
-1. Check the pod specification for initContainers that download model artifacts
-2. Monitor pod startup times for improved performance compared to traditional container pulls
-3. Review registry storage to see separate base images and model artifact entries
-
-### Troubleshooting
-
-If you encounter issues with OCI Artifacts:
-
-- Ensure your container runtime supports OCI Artifacts (see Compatibility section)
-- Verify registry compatibility with OCI Artifacts specification
-- Check initContainer logs for artifact download status
-- Refer to the [technical specification](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20250609-model-as-oci-artifacts.md) for advanced configuration options
-
diff --git a/website/versioned_docs/version-v0.5.1/monitoring.md b/website/versioned_docs/version-v0.5.1/monitoring.md
deleted file mode 100644
index 2fc88ca..0000000
--- a/website/versioned_docs/version-v0.5.1/monitoring.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: Monitoring
----
-
-Inference service exposes Prometheus metrics for monitoring system stats.
-
-## vLLM runtime
-
-vLLM exposes Prometheus metrics at the `/metrics` endpoint. These metrics provide detailed insights into the system's performance, resource utilization, and request processing statistics. The following table lists all available metrics:
-
-vLLM version: 0.8.2
-
-:::note
-vLLM V1 engine is now enabled by default. Check [here](https://docs.vllm.ai/en/latest/getting_started/v1_user_guide.html) for details.
-:::
-
-| Category | Metric Name | Type | Description |
-|----------|-------------|------|-------------|
-| System Stats - Scheduler | `vllm:num_requests_running` | Gauge | Number of requests currently running on GPU |
-| System Stats - Scheduler | `vllm:num_requests_waiting` | Gauge | Number of requests waiting to be processed |
-| System Stats - Scheduler | `vllm:lora_requests_info`   | Gauge | Running stats on lora requests |
-| System Stats - Scheduler | `vllm:num_requests_swapped` | Gauge | Number of requests swapped to CPU (DEPRECATED: KV cache offloading is not used in V1) |
-| System Stats - Cache | `vllm:gpu_cache_usage_perc` | Gauge | GPU KV-cache usage. 1 means 100 percent usage |
-| System Stats - Cache | `vllm:cpu_cache_usage_perc` | Gauge | CPU KV-cache usage. 1 means 100 percent usage (DEPRECATED: KV cache offloading is not used in V1) |
-| System Stats - Cache | `vllm:cpu_prefix_cache_hit_rate` | Gauge | CPU prefix cache block hit rate (DEPRECATED: KV cache offloading is not used in V1) |
-| System Stats - Cache | `vllm:gpu_prefix_cache_hit_rate` | Gauge | GPU prefix cache block hit rate (DEPRECATED: use `vllm:gpu_prefix_cache_queries` and `vllm:gpu_prefix_cache_hits in V1`) |
-| System Stats - Cache | `vllm:gpu_cache_usage_perc` | Counter | GPU prefix cache queries, in terms of number of queried blocks (V1 only) |
-| System Stats - Cache | `vllm:gpu_prefix_cache_hits` | Counter | GPU prefix cache hits, in terms of number of cached blocks (V1 only) |
-| Iteration Stats | `vllm:num_preemptions_total` | Counter | Cumulative number of preemption from the engine |
-| Iteration Stats | `vllm:prompt_tokens_total` | Counter | Number of prefill tokens processed |
-| Iteration Stats | `vllm:generation_tokens_total` | Counter | Number of generation tokens processed |
-| Iteration Stats | `vllm:iteration_tokens_total`  | Histogram | Number of tokens per engine_step |
-| Iteration Stats | `vllm:time_to_first_token_seconds` | Histogram | Time to first token in seconds |
-| Iteration Stats | `vllm:time_per_output_token_seconds` | Histogram | Time per output token in seconds |
-| Request Stats | `vllm:e2e_request_latency_seconds` | Histogram | End to end request latency in seconds |
-| Request Stats | `vllm:request_queue_time_seconds` | Histogram | Time spent in WAITING phase for request |
-| Request Stats | `vllm:request_inference_time_seconds` | Histogram | Time spent in RUNNING phase for request |
-| Request Stats | `vllm:request_prefill_time_seconds` | Histogram | Time spent in PREFILL phase for request |
-| Request Stats | `vllm:request_decode_time_seconds` | Histogram | Time spent in DECODE phase for request |
-| Request Stats | `vllm:request_prompt_tokens` | Histogram | Number of prefill tokens processed per request |
-| Request Stats | `vllm:request_generation_tokens` | Histogram | Number of generation tokens processed per request |
-| Request Stats | `vllm:request_max_num_generation_tokens` | Histogram | Maximum number of requested generation tokens |
-| Request Stats | `vllm:request_params_n` | Histogram | The 'n' request parameter |
-| Request Stats | `vllm:request_params_max_tokens` | Histogram | The 'max_tokens' request parameter |
-| Request Stats | `vllm:request_success_total` | Counter | Count of successfully processed requests |
-| Speculative Decoding | `vllm:spec_decode_draft_acceptance_rate` | Gauge | Speculative token acceptance rate (DEPRECATED: Unused in V1) |
-| Speculative Decoding | `vllm:spec_decode_efficiency` | Gauge | Speculative decoding system efficiency (DEPRECATED: Unused in V1) |
-| Speculative Decoding | `vllm:spec_decode_num_accepted_tokens_total` | Counter | Number of accepted tokens |
-| Speculative Decoding | `vllm:spec_decode_num_draft_tokens_total` | Counter | Number of draft tokens |
-| Speculative Decoding | `vllm:spec_decode_num_emitted_tokens_total` | Counter | Number of emitted tokens (DEPRECATED: Unused in V1) |
diff --git a/website/versioned_docs/version-v0.5.1/multi-node-inference.md b/website/versioned_docs/version-v0.5.1/multi-node-inference.md
deleted file mode 100644
index 7cb7aed..0000000
--- a/website/versioned_docs/version-v0.5.1/multi-node-inference.md
+++ /dev/null
@@ -1,263 +0,0 @@
----
-title: Multi-Node Inference
----
-
-This document explains how to configure and use multi-node distributed inference in KAITO for large models that require more GPU resources than a single node can provide.
-
-## Overview
-
-Multi-node inference allows you to deploy large AI models across multiple nodes (servers) when the model is too large to fit on a single node. KAITO supports different parallelism strategies depending on your model's requirements:
-
-| Strategy | Use Case | Supported |
-|----------|----------|-----------|
-| **Single GPU** | Small models that fit on one GPU | ✅ |
-| **Single-Node Multi-GPU** | Models that need multiple GPUs but fit on one node | ✅ |
-| **Multi-Node Multi-GPU** | Very large models (400B+ parameters) requiring multiple nodes | ✅ |
-
-## When to Use Multi-Node Inference
-
-Consider multi-node inference when:
-
-- Your model has 400B+ parameters and cannot fit on a single node
-- You need to serve models like very large language models that exceed single-node memory capacity
-- You have specific performance requirements that benefit from distributed processing
-
-:::note
-Multi-node inference introduces additional complexity and network overhead. Only use it when your model truly requires more resources than a single node can provide.
-:::
-
-## Supported Models
-
-The following preset models support multi-node distributed inference:
-
-- **Llama3**: `llama-3.3-70b-instruct`
-
-Check the [presets documentation](./presets.md) for the complete list and their specific requirements.
-
-## Configuration
-
-### Basic Multi-Node Setup
-
-To deploy a model across multiple nodes, specify the node count in your Workspace configuration:
-
-```yaml
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-large-model
-resource:
-  count: 2                    # Number of nodes to use
-  instanceType: "Standard_NC80adis_H100_v5"
-  labelSelector:
-    matchLabels:
-      apps: large-model
-inference:
-  preset:
-    name: "llama-3.3-70b-instruct"
-```
-
-### Pre-Provisioned Nodes
-
-If you're using pre-provisioned GPU nodes, specify them explicitly:
-
-```yaml
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-large-model
-resource:
-  count: 2
-  preferredNodes:
-    - gpu-node-1
-    - gpu-node-2
-  labelSelector:
-    matchLabels:
-      apps: large-model
-inference:
-  preset:
-    name: "llama-3.3-70b-instruct"
-```
-
-:::warning
-Pre-provisioned nodes must have the same matching labels as specified in the `resource` spec, and each node must report available GPU resources.
-:::
-
-### Custom vLLM Parameters for Multi-Node
-
-You can customize vLLM runtime parameters for distributed inference using a ConfigMap:
-
-```yaml
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: distributed-inference-config
-data:
-  inference_config.yaml: |
-    vllm:
-      gpu-memory-utilization: 0.95
-      max-model-len: 131072
----
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-large-model
-resource:
-  count: 2
-  instanceType: "Standard_NC80adis_H100_v5"
-  labelSelector:
-    matchLabels:
-      apps: large-model
-inference:
-  preset:
-    name: "llama-3.3-70b-instruct"
-  config: "distributed-inference-config"
-```
-
-Key parameters for multi-node inference:
-- `tensor-parallel-size`: Automatically set by KAITO based on the number of GPUs per node
-- `pipeline-parallel-size`: Automatically set by KAITO based on the number of nodes
-- `gpu-memory-utilization`: Fraction of GPU memory to use (0.0-1.0) - user configurable
-- `max-model-len`: Maximum sequence length - user configurable
-
-:::note
-The `tensor-parallel-size` and `pipeline-parallel-size` parameters are automatically managed by KAITO based on your cluster configuration and do not need to be specified in the ConfigMap.
-:::
-
-## Architecture
-
-### Single-Node Multi-GPU
-
-When using multiple GPUs on a single node, KAITO uses **tensor parallelism** to split the model across GPUs within that node:
-
-```
-┌─────────────────────────────────────┐
-│             Node 1                  │
-│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐    │
-│  │GPU 1│ │GPU 2│ │GPU 3│ │GPU 4│    │
-│  └─────┘ └─────┘ └─────┘ └─────┘    │
-│           Model Split               │
-└─────────────────────────────────────┘
-```
-
-### Multi-Node Multi-GPU
-
-For multi-node deployments, KAITO combines **pipeline parallelism** between nodes and **tensor parallelism** within each node:
-
-```
-┌─────────────────────┐    ┌─────────────────────┐
-│       Node 1        │    │       Node 2        │
-│  ┌─────┐┌─────┐     │    │  ┌─────┐ ┌─────┐    │
-│  │GPU 1││GPU 2│     │◄──►│  │GPU 3│ │GPU 4│    │
-│  └─────┘└─────┘     │    │  └─────┘ └─────┘    │
-│   Layers 1-N/2      │    │   Layers N/2+1-N    │
-└─────────────────────┘    └─────────────────────┘
-```
-
-## Resource Validation
-
-KAITO automatically validates that your configuration provides sufficient resources:
-
-- **GPU Count**: `(GPUs per instance) × (workspace.resource.count) ≥ (Required GPUs for model)`
-- **Memory**: `(GPU memory) × (Total GPUs) ≥ (Required model memory)`
-
-If validation fails, you'll receive an error message when creating or updating the workspace.
-
-:::tip Resource Optimization
-KAITO may use fewer nodes than specified in `workspace.resource.count` if the model can fit efficiently on fewer nodes. This optimizes GPU utilization and reduces network overhead, but be mindful of the costs when provisioning many nodes.
-:::
-
-## Service Architecture
-
-Multi-node inference uses Kubernetes StatefulSets to ensure stable pod identity and coordination:
-
-- **Leader Pod** (index 0): Coordinates the distributed inference and serves the API
-- **Worker Pods** (index 1+): Join the Ray cluster and participate in model serving
-
-The service endpoint points to the leader pod, which handles all incoming requests and coordinates with worker pods.
-
-:::note Future Enhancement
-KAITO will support [LeaderWorkerSet](https://lws.sigs.k8s.io/docs/overview/) in the future to provide better management of leader-worker topologies and improved fault tolerance for multi-node deployments.
-:::
-
-## Health Monitoring
-
-Multi-node deployments use specialized health checks:
-
-- **Liveness Probes**: Monitor Ray cluster health and detect dead actors
-- **Readiness Probes**: Check service availability via the leader pod's `/health` endpoint
-
-If worker pods fail, the leader will restart to reinitialize the entire cluster, ensuring all pods are synchronized.
-
-## Best Practices
-
-1. **Resource Planning**: Carefully plan your GPU and memory requirements before deployment
-2. **Network Bandwidth**: Ensure sufficient network bandwidth between nodes for optimal performance
-3. **Monitoring**: Monitor both individual node health and overall cluster performance
-4. **Cost Management**: Be aware that multi-node deployments can be expensive; only use when necessary
-
-## Troubleshooting
-
-### Service Unavailable After Deployment
-
-If the service becomes unavailable:
-
-1. Check if all pods are running: `kubectl get pods -l app=<your-app-label>`
-2. Verify Ray cluster health in leader pod logs
-3. Ensure network connectivity between nodes
-4. Check resource allocation and GPU availability
-
-### Worker Pod Failures
-
-Worker pod failures will trigger leader pod restart to reinitialize the cluster:
-
-1. Monitor pod restart events
-2. Check for resource constraints (memory, GPU)
-3. Verify node-to-node network connectivity
-4. Review pod logs for Ray cluster connection issues
-
-### Performance Issues
-
-If you experience poor performance:
-
-1. Monitor network latency between nodes
-2. Check GPU utilization across all nodes
-3. Review memory usage and potential bottlenecks
-4. Consider adjusting parallelism parameters
-
-## API Usage
-
-Multi-node inference services expose the same API as single-node deployments. The vLLM runtime provides OpenAI-compatible endpoints:
-
-```bash
-# Get the cluster IP of your service
-kubectl get services
-
-# Check service health
-kubectl run -it --rm --restart=Never curl --image=curlimages/curl -- \
-  curl http://<CLUSTER-IP>:80/health
-
-# Generate text using chat completions API
-kubectl run -it --rm --restart=Never curl --image=curlimages/curl -- \
-  curl -X POST http://<CLUSTER-IP>:80/v1/chat/completions \
-  -H "Content-Type: application/json" \
-  -d '{
-    "model": "llama-3.3-70b-instruct",
-    "messages": [{"role": "user", "content": "Your prompt here"}],
-    "max_tokens": 100
-  }'
-```
-
-For detailed API specifications, see the [inference documentation](./inference.md#inference-api).
-
-## Limitations
-
-- **Custom Models**: Multi-node inference is currently only supported for preset models
-- **Fault Tolerance**: The system requires leader restart when worker pods fail
-- **Network Dependency**: Performance heavily depends on inter-node network quality
-- **Complexity**: Debugging and monitoring are more complex than single-node deployments
-
-## Related Documentation
-
-- [Inference](./inference.md) - General inference documentation
-- [Presets](./presets.md) - Supported models and their requirements
-- [Custom Model](./custom-model.md) - Using custom models (single-node only)
\ No newline at end of file
diff --git a/website/versioned_docs/version-v0.5.1/preset-onboarding.md b/website/versioned_docs/version-v0.5.1/preset-onboarding.md
deleted file mode 100644
index 7ba3054..0000000
--- a/website/versioned_docs/version-v0.5.1/preset-onboarding.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: Preset onboarding
----
-
-This document describes how to add a new supported OSS model in KAITO. The process is designed to allow community users to initiate the request. KAITO maintainers will follow up and deal with managing the model images and guiding the code changes to set up the model preset configurations.
-
-## Step 1: Make a proposal
-
-This step is done by the requestor. The requestor should make a PR to describe the target OSS model following this [template](https://github.com/kaito-project/kaito/blob/main/docs/proposals/YYYYMMDD-model-template.md). The proposal status should be `provisional` in the beginning. KAITO maintainers will review the PR and decide to accept or reject the PR. The PR could be rejected if the target OSS model has low usage, or it has strict license limitations, or it is a relatively small model with limited capabilities.
-
-
-## Step 2: Validate and test the model
-
-This step is done by KAITO maintainers. Based on the information provided in the proposal, KAITO maintainers will download the model and test it using the specified runtime. The entire process is automated via GitHub actions when KAITO maintainers file a PR to add the model to the [supported\_models.yaml](https://github.com/kaito-project/kaito/blob/main/presets/workspace/models/supported_models.yaml).
-
-
-## Step 3: Push model image to MCR
-
-This step is done by KAITO maintainers. If the model license allows, KAITO maintainers will push the model image to MCR, making the image publicly available. This step is skipped if only private access is allowed for the model image. Once this step is done, KAITO maintainers will update the status of the proposal submitted in Step 1 to `ready to integrate`.
-
-## Step 4: Add preset configurations
-
-This step is done by the requestor. The requestor will work on a PR to register the model with preset configurations. The PR will contain code changes to implement a simple inference interface. [Here](https://github.com/kaito-project/kaito/blob/main/presets/workspace/models/falcon/model.go) is an existing example. In the same PR, or a separate PR, the status of the proposal status should be updated to `integrated`.
-
-## Step 5: Add an E2E test
-
-This step is done by the requestor. A new e2e test should be added to [here](https://github.com/kaito-project/kaito/blob/main/test/e2e/preset_test.go) which ensures the inference service is up and running with preset configurations.
-
-
-After all the above are done, a new model becomes available in KAITO.
diff --git a/website/versioned_docs/version-v0.5.1/presets.md b/website/versioned_docs/version-v0.5.1/presets.md
deleted file mode 100644
index 97c39dd..0000000
--- a/website/versioned_docs/version-v0.5.1/presets.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: Presets
----
-
-The current supported model families with preset configurations are listed below.
-
-| Model Family                                | Compatible KAITO Versions |
-|---------------------------------------------|---------------------------|
-| [falcon](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/falcon)         | v0.0.1+                   |
-| [mistral](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/mistral)       | v0.2.0+                   |
-| [phi2](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/phi2)             | v0.2.0+                   |
-| [phi3](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/phi3)             | v0.3.0+                   |
-| [phi4](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/phi4)             | v0.4.5+                   |
-| [qwen7b](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/qwen)           | v0.4.1+                   |
-| [qwen32b](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/qwen)          | v0.4.5+                   |
-| [llama3](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/llama3)         | v0.4.6+                   |
-
-## Validation
-
-Each preset model has its own hardware requirements in terms of GPU count and GPU memory defined in the respective `model.go` file. KAITO controller performs a validation check of whether the specified SKU and node count are sufficient to run the model or not. In case the provided SKU is not in the known list, the controller bypasses the validation check which means users need to ensure the model can run with the provided SKU.
-
-## Distributed inference
-
-For models that support distributed inference, when the node count is larger than one, [Torch Distributed Elastic](https://pytorch.org/docs/stable/distributed.elastic.html) is configured with master/worker pods running in multiple nodes and the service endpoint is the master pod.
-
-The following preset models support multi-node distributed inference:
-
-| Model Family | Models | Multi-Node Support |
-|--------------|--------|-------------------|
-| [llama3](https://github.com/kaito-project/kaito/tree/main/presets/workspace/models/llama3) | `llama-3.3-70b-instruct` | ✅ |
-
-For detailed information on configuring and using multi-node inference, see the [Multi-Node Inference](./multi-node-inference.md) documentation.
diff --git a/website/versioned_docs/version-v0.5.1/proposals.md b/website/versioned_docs/version-v0.5.1/proposals.md
deleted file mode 100644
index c521b45..0000000
--- a/website/versioned_docs/version-v0.5.1/proposals.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: Proposals
----
-
-This section contains proposals for adding new models to KAITO. Each proposal describes the process of evaluating and integrating new OSS models into the KAITO ecosystem.
-
-## Proposal Template
-
-Before creating a new model proposal, please use the following template: [Model Proposal Template](https://github.com/kaito-project/kaito/blob/main/docs/proposals/YYYYMMDD-model-template.md)
-
-## Current Proposals
-
-Below are the current model proposals in various stages of integration:
-
-### Provisional Status
-- [Llama 3.3 70B Instruct](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20250529-llama-3.3-70b-instruct.md) - Meta's multilingual instruction-tuned 70B model
-- [Qwen2.5 Coder](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20250103-qwen2.5-coder.md) - Qwen2.5 series for code generation
-- [Phi-4 Instruct](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20241212-phi4-instruct.md) - Microsoft's latest Phi-4 instruction-tuned model
-- [Distributed Inference](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20250325-distributed-inference.md) - Support for distributed inference across multiple GPUs
-- [Model as OCI Artifacts](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20250609-model-as-oci-artifacts.md) - Packaging models as OCI artifacts
-
-### Integrated Status
-- [Mistral Instruct](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20240205-mistral-instruct.md) - Mistral AI's instruction-tuned model
-- [Mistral](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20240205-mistral.md) - Base Mistral model
-- [Phi-2](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20240206-phi-2.md) - Microsoft's Phi-2 small language model
-- [Phi-3 Instruct](https://github.com/kaito-project/kaito/blob/main/docs/proposals/20240527-phi3-instruct.md) - Microsoft's Phi-3 instruction-tuned model
-
-## Proposal Process
-
-For detailed information about the model onboarding process, see the [Model Onboarding Guide](./preset-onboarding.md).
-
-### Step 1: Create a Proposal
-Use the model proposal template to describe the target OSS model, including licensing, usage statistics, and technical requirements.
-
-### Step 2: Model Validation
-KAITO maintainers validate and test the proposed model using the specified runtime.
-
-### Step 3: Image Publishing
-If licensing allows, model images are published to Microsoft Container Registry (MCR).
-
-### Step 4: Integration
-Implement preset configurations and inference interfaces for the model.
-
-### Step 5: Testing
-Add comprehensive E2E tests to ensure the model works correctly with KAITO.
-
-## Contributing a Proposal
-
-To contribute a new model proposal:
-
-1. Fork the KAITO repository
-2. Copy the [model template](https://github.com/kaito-project/kaito/blob/main/docs/proposals/YYYYMMDD-model-template.md) to `docs/proposals/YYYYMMDD-<model-name>.md`
-3. Fill out all required sections
-4. Submit a pull request for review
-
-The proposal status will be updated as it progresses through the integration pipeline.
diff --git a/website/versioned_docs/version-v0.5.1/quick-start.md b/website/versioned_docs/version-v0.5.1/quick-start.md
deleted file mode 100644
index 6159ae0..0000000
--- a/website/versioned_docs/version-v0.5.1/quick-start.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-title: Quick Start
----
-
-After installing KAITO, you can quickly deploy a phi-3.5-mini-instruct inference service to get started.
-
-## Prerequisites
-
-- A Kubernetes cluster with KAITO installed (see [Installation](installation))
-- `kubectl` configured to access your cluster
-
-## Deploy Your First Model
-
-Let's start by deploying a phi-3.5-mini-instruct model using a workspace configuration:
-
-```yaml title="phi-3.5-workspace.yaml"
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-phi-3-5-mini
-resource:
-  instanceType: "Standard_NC24ads_A100_v4"
-  labelSelector:
-    matchLabels:
-      apps: phi-3-5
-inference:
-  preset:
-    name: phi-3.5-mini-instruct
-```
-
-Apply this configuration to your cluster:
-
-```bash
-kubectl apply -f phi-3.5-workspace.yaml
-```
-
-## Monitor Deployment
-
-Track the workspace status to see when the model has been deployed successfully:
-
-```bash
-kubectl get workspace workspace-phi-3-5-mini
-```
-
-When the `WORKSPACESUCCEEDED` column becomes `True`, the model has been deployed successfully:
-
-```bash
-NAME                     INSTANCE                   RESOURCEREADY   INFERENCEREADY   JOBSTARTED   WORKSPACESUCCEEDED   AGE
-workspace-phi-3-5-mini   Standard_NC24ads_A100_v4   True            True                          True                 4h15m
-```
-
-## Test the Model
-
-Find the inference service's cluster IP and test it using a temporary curl pod:
-
-```bash
-# Get the service endpoint
-kubectl get svc workspace-phi-3-5-mini
-export CLUSTERIP=$(kubectl get svc workspace-phi-3-5-mini -o jsonpath="{.spec.clusterIPs[0]}")
-
-# List available models
-kubectl run -it --rm --restart=Never curl --image=curlimages/curl -- curl -s http://$CLUSTERIP/v1/models | jq
-```
-
-You should see output similar to:
-
-```json
-{
-  "object": "list",
-  "data": [
-    {
-      "id": "phi-3.5-mini-instruct",
-      "object": "model",
-      "created": 1733370094,
-      "owned_by": "vllm",
-      "root": "/workspace/vllm/weights",
-      "parent": null,
-      "max_model_len": 16384
-    }
-  ]
-}
-```
-
-## Make an Inference Call
-
-Now make an inference call using the model:
-
-```bash
-kubectl run -it --rm --restart=Never curl --image=curlimages/curl -- curl -X POST http://$CLUSTERIP/v1/chat/completions \
-  -H "Content-Type: application/json" \
-  -d '{
-    "model": "phi-3.5-mini-instruct",
-    "messages": [{"role": "user", "content": "What is kubernetes?"}],
-    "max_tokens": 50,
-    "temperature": 0
-  }'
-```
-
-## Next Steps
-
-🎉 Congratulations! You've successfully deployed and tested your first model with KAITO.
-
-- **Learn More**: Explore the full range of supported models in the [presets documentation](https://github.com/kaito-project/kaito/tree/main/presets)
-- **Advanced Usage**: Learn about [workspace configurations](https://github.com/kaito-project/kaito/blob/main/api/v1alpha1/workspace_types.go)
-- **Contributing**: See how to [contribute new models](https://github.com/kaito-project/kaito/blob/main/docs/How-to-add-new-models.md)
diff --git a/website/versioned_docs/version-v0.5.1/rag.md b/website/versioned_docs/version-v0.5.1/rag.md
deleted file mode 100644
index 97fa070..0000000
--- a/website/versioned_docs/version-v0.5.1/rag.md
+++ /dev/null
@@ -1,428 +0,0 @@
-# Retrieval-Augmented Generation (RAG)
-
-This document presents how to use the KAITO `ragengine` Custom Resource Definition (CRD) for retrieval-augumented generatoin workflow. By creating a RAGEngine resource, you can quickly stand up a service that indexes documents and queries them in conjunction with an existing LLM inference endpoint—no need to custom-build pipelines. This enables your large language model to answer questions based on your own private content.
-
-## Installation
-
-> Be sure you've cloned this repo and followed [kaito workspace installation](./installation.md) if you plan to use local embedding model. RAGEngine needs the gpu-provisioner component to provision GPU nodes.
-
-```bash
-helm install ragengine ./charts/kaito/ragengine --namespace kaito-ragengine --create-namespace
-```
-
-## Verify installation
-You can run the following commands to verify the installation of the controllers were successful.
-
-Check status of the Helm chart installations.
-
-```bash
-helm list -n kaito-ragengine
-```
-
-Check status of the `ragengine`.
-
-```bash
-kubectl describe deploy ragengine -n kaito-ragengine
-```
-
-## Clean up
-
-```bash
-helm uninstall kaito-ragengine
-```
-
-## Usage
-
-### Prerequisite
-Before creating a RAGEngine, ensure you have an accessible model inference endpoint. This endpoint can be:
-
-1.	A model deployed through KAITO Workspace CRD (e.g., a local Hugging Face model, a vLLM instance, etc.).
-2.	An external API (e.g., Huggingface service or other REST-based LLM providers).
-
-### Define the RAGEngine
-Create a YAML manifest defining your RAGEngine. Key fields under spec include:
-
-Embedding: how to generate vector embeddings for your documents. You may choose remote or local (one must be left unset if you pick the other):
-
-```yaml
-embedding:
-    local:
-      modelID: "BAAI/bge-small-en-v1.5"
-```
-InferenceService: points to the LLM endpoint that RAGEngine will call for final text generation.
-```yaml
-inferenceService:
-  url: "<inference-url>/v1/completions"
-```
-Users also need to specify the GPU SKU used for inference in the `compute` spec. For example,
-
-```yaml
-apiVersion: kaito.sh/v1alpha1
-kind: RAGEngine
-metadata:
-  name: ragengine-start
-spec:
-  compute:
-    instanceType: "Standard_NC4as_T4_v3"
-    labelSelector:
-      matchLabels:
-        apps: ragengine-example
-  embedding:
-    local:
-      modelID: "BAAI/bge-small-en-v1.5"
-  inferenceService:
-    url: "<inference-url>/v1/completions"
-```
-
-### Apply the manifest
-After you create your YAML configuration, run:
-```sh
-kubectl apply -f examples/RAG/kaito_ragengine_phi_3.yaml
-```
-
-## API definitions and examples
-
-A **RAGEngine index** is a logical collection that organizes and stores your documents for retrieval-augmented generation workflows. The relationship between indexes, documents, and document nodes is as follows:
-
-- **Index**: An index is a named container that holds a set of documents. Each index is independent and can be created, updated, queried, persisted, loaded, or deleted via the API.
-
-- **Documents**: Documents are the primary units of content that you add to an index. Each document contains a `text` field (the content to be indexed) and optional `metadata` (such as author, source, or custom tags). When you add documents to an index, each is assigned a unique `doc_id`.
-
-- **Document Nodes**: When a document is ingested, it is automatically split into smaller chunks called *nodes*. The splitting strategy depends on the document type and metadata:
-  - By default, documents are split into sentences.
-  - If you specify code-aware splitting (using the `split_type` and `language` metadata), the document is split into code blocks or logical code units.
-  - Each node represents a chunk of text that is indexed and can be retrieved as part of a query.
-
-**How it works in practice:**
-- When you index a document, it is divided into nodes for efficient retrieval and semantic search.
-- When you query an index, the engine retrieves the most relevant nodes (not necessarily whole documents) and can use them to generate answers or summaries.
-- The `source_nodes` field in query responses contains the actual nodes that matched your query, along with their scores and metadata.
-
-This design enables fine-grained retrieval and more accurate, context-aware responses from your LLM-powered applications.
-
-### Creating an Index With Documents
-
-To add documents to an index or create a new index, use the `/index` API route. This endpoint accepts a POST request with the index name and a list of documents to be indexed.
-
-**Request Example:**
-
-```json
-POST /index
-{
-  "index_name": "rag_index",
-  "documents": [
-    {
-      "text": "Retrieval Augmented Generation (RAG) is an architecture that augments the capabilities of a Large Language Model (LLM) like ChatGPT by adding an information retrieval system that provides grounding data.",
-      "metadata": {
-        "author": "Microsoft",
-        "source": "https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview?tabs=docs"
-      }
-    }
-  ]
-}
-```
-
-- `index_name`: The name of the index to create or update.
-- `documents`: A list of documents, each with a `text` field and optional `metadata`.
-
-**Response Example:**
-
-```json
-[
-  {
-    "doc_id": "123456",
-    "text": "Retrieval Augmented Generation (RAG) is an architecture that augments the capabilities of a Large Language Model (LLM) like ChatGPT by adding an information retrieval system that provides grounding data.",
-    "hash_value": "text_hash_value",
-    "metadata": {
-      "author": "Microsoft",
-      "source": "https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview?tabs=docs"
-    },
-    "is_truncated": false
-  }
-]
-```
-
-Each returned document includes its unique `doc_id`, the original text, metadata, and a flag indicating if the text was truncated. The `doc_id` will be important for document update/delete calls. 
-
-
-#### Splitting Documents with CodeSplitter
-
-By default, RAGEngine splits documents into sentences. However, you can instruct the engine to split documents using the `CodeSplitter` (for code-aware chunking) by providing metadata in your API request.
-
-To use the `CodeSplitter`, set the `split_type` to `"code"` and specify the programming language in the `language` field of the document metadata. For example, when calling the RAGEngine API to index documents:
-
-```json
-{
-  "documents": [
-    {
-      "text": "def foo():\n    return 42\n\n# Another function\ndef bar():\n    pass",
-      "metadata": {
-        "split_type": "code",
-        "language": "python"
-      }
-    }
-  ]
-}
-```
-
-This instructs the RAGEngine to use code-aware splitting for the provided document. If `split_type` is not set or set to any other value, sentence splitting will be used by default.
-
-### List Documents
-
-To retrieve a paginated list of documents from a specific index, use the `/indexes/{index_name}/documents` API route. This endpoint accepts a GET request with optional query parameters for pagination, text truncation, and metadata filtering.
-
-**Request Example:**
-
-```
-GET /indexes/rag_index/documents?limit=5&offset=0&max_text_length=500
-```
-
-- `limit`: (optional) Maximum number of documents to return (default: 10, max: 100).
-- `offset`: (optional) Starting point for the document list (default: 0).
-- `max_text_length`: (optional) Maximum text length to return per document (default: 1000).
-- `metadata_filter`: (optional) A JSON string representing key-value pairs to filter documents by their metadata.
-
-**Response Example:**
-
-```json
-{
-  "documents": [
-    {
-      "doc_id": "123456",
-      "text": "Retrieval Augmented Generation (RAG) is an architecture that augments the capabilities of a Large Language Model (LLM) like ChatGPT by adding an information retrieval system that provides grounding data.",
-      "hash_value": "text_hash_value",
-      "metadata": {
-        "author": "Microsoft",
-        "source": "https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview?tabs=docs"
-      },
-      "is_truncated": false
-    }
-  ],
-  "count": 1
-}
-```
-
-Each document in the response includes its unique `doc_id`, the (possibly truncated) text, metadata, and a flag indicating if the text was truncated.
-
-**Note:**  
-If you want to filter documents by metadata, provide the `metadata_filter` parameter as a JSON string. For example:
-
-```
-GET /indexes/rag_index/documents?metadata_filter={"author":"Microsoft"}
-```
-
-
-### Updating Documents
-
-To update existing documents in a specific index, use the `/indexes/{index_name}/documents` API route. This endpoint accepts a POST request with the index name in the URL and a list of documents to update in the request body.
-
-**Request Example:**
-
-```json
-POST /indexes/rag_index/documents
-{
-  "documents": [
-    {
-      "doc_id": "123456",
-      "text": "Retrieval Augmented Generation (RAG) is an architecture that augments the capabilities of a Large Language Model (LLM) like ChatGPT by adding an information retrieval system that provides grounding data. Adding an information retrieval system gives you control over grounding data used by an LLM when it formulates a response.",
-      "hash_value": "text_hash_value",
-      "metadata": {
-        "author": "Microsoft",
-        "source": "https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview?tabs=docs"
-      },
-    }
-  ]
-}
-```
-
-- `doc_id`: The unique identifier of the document to update.
-- `text`: The new or updated text for the document.
-- `metadata`: (Optional) Updated metadata for the document.
-
-**Response Example:**
-
-```json
-{
-  "updated_documents": [
-    {
-      "doc_id": "123456",
-      "text": "Retrieval Augmented Generation (RAG) is an architecture that augments the capabilities of a Large Language Model (LLM) like ChatGPT by adding an information retrieval system that provides grounding data. Adding an information retrieval system gives you control over grounding data used by an LLM when it formulates a response.",
-      "hash_value": "text_hash_value",
-      "metadata": {
-        "author": "Microsoft",
-        "source": "https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview?tabs=docs"
-      },
-    }
-  ],
-  "unchanged_documents": [],
-  "not_found_documents": []
-}
-```
-
-- `updated_documents`: Documents that were successfully updated.
-- `unchanged_documents`: Documents that were provided but did not require changes.
-- `not_found_documents`: Documents with IDs that were not found in the index.
-
-Use this endpoint to keep your indexed documents up to date with the latest content or metadata.
-
-### Delete Documents
-
-
-To delete one or more documents from a specific index, use the `/indexes/{index_name}/documents/delete` API route. This endpoint accepts a POST request with the index name in the URL and a list of document IDs to delete in the request body.
-
-**Request Example:**
-
-```json
-POST /indexes/rag_index/documents/delete
-{
-  "doc_ids": ["123456"]
-}
-```
-
-- `doc_ids`: A list of document IDs to delete from the specified index.
-
-**Response Example:**
-
-```json
-{
-  "deleted_doc_ids": ["123456"],
-  "not_found_doc_ids": []
-}
-```
-
-- `deleted_doc_ids`: Document IDs that were successfully deleted.
-- `not_found_doc_ids`: Document IDs that were not found in the index.
-
-Use this endpoint to remove documents that are no longer needed from your index.
-
-### Persist Index
-
-To save (persist) the data of an index to disk, use the `/persist/{index_name}` API route. This endpoint accepts a POST request with the index name in the URL and an optional `path` query parameter specifying where to save the index data.
-
-**Request Example:**
-
-```
-POST /persist/rag_index?path=./custom_path
-```
-
-- `index_name`: The name of the index to persist.
-- `path`: (optional) The directory path where the index will be saved. If not provided, the default directory is used.
-
-**Response Example:**
-
-```json
-{
-  "message": "Successfully persisted index rag_index to ./custom_path/rag_index."
-}
-```
-
-Use this endpoint to ensure your indexed data is safely stored on disk.
-
----
-
-### Load Index
-
-To load an existing index from disk, use the `/load/{index_name}` API route. This endpoint accepts a POST request with the index name in the URL, an optional `path` query parameter specifying where to load the index from, and an optional `overwrite` flag.
-
-**Request Example:**
-
-```
-POST /load/rag_index?path=./custom_path/rag_index
-```
-
-- `index_name`: The name of the index to load.
-- `path`: (optional) The path to load the index from. If not provided, the default directory is used.
-- `overwrite`: (optional, default: false) If true, will overwrite the existing index if it already exists in memory.
-
-**Response Example:**
-
-```json
-{
-  "message": "Successfully loaded index rag_index from ./custom_path/rag_index."
-}
-```
-
-Use this endpoint to restore previously persisted indexes into memory for querying and updates.
-
-### Delete Index
-
-To delete an entire index and all of its documents, use the `/indexes/{index_name}` API route. This endpoint accepts a DELETE request with the index name in the URL. Deleting an index is irreversible and will remove all associated documents from memory.
-
-**Request Example:**
-
-```
-DELETE /indexes/rag_index
-```
-
-- `index_name`: The name of the index to delete.
-
-**Response Example:**
-
-```json
-{
-  "message": "Successfully deleted index rag_index."
-}
-```
-
-Use this endpoint to permanently remove an index and all its data when it is no longer needed.
-
-### Query Index
-
-To query a specific index for relevant documents, use the `/query` API route. This endpoint accepts a POST request with the index name, query string, and optional parameters for result count, and LLM generation.
-
-**Request Example:**
-
-```json
-POST /query
-{
-  "index_name": "rag_index",
-  "query": "What is RAG?",
-  "top_k": 5,
-  "llm_params": {
-    "temperature": 0.7,
-    "max_tokens": 2048
-  }
-}
-```
-
-- `index_name`: The name of the index to query.
-- `query`: The query string.
-- `top_k`: (optional) Number of top documents to retrieve (default: 5).
-- `llm_params`: (optional) Parameters for LLM-based generation (e.g., temperature, max_tokens).
-
-**Response Example:**
-
-```json
-{
-  "response": "Retrieval Augmented Generation (RAG) is an architecture that augments the capabilities of a Large Language Model...",
-  "source_nodes": [
-    {
-      "doc_id": "123456",
-      "node_id": "2853a565-8c1f-4982-acaa-a0ab52691435",
-      "text": "Retrieval Augmented Generation (RAG) is an architecture that augments the capabilities of a Large Language Model...",
-      "score": 0.95,
-      "metadata": {
-        "author": "Microsoft",
-        "source": "https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview?tabs=docs"
-      },
-    }
-  ],
-  "metadata": {
-    "2853a565-8c1f-4982-acaa-a0ab52691435": {
-      "author": "Microsoft",
-      "source": "https://learn.microsoft.com/en-us/azure/search/retrieval-augmented-generation-overview?tabs=docs"
-    }
-  }
-}
-```
-
-- `response`: The generated answer or summary from the LLM (if enabled).
-- `source_nodes`: List of source nodes with their text, score, and metadata.
-- `metadata`: Additional metadata about the query or response.
-
-Use this endpoint to retrieve relevant information from your indexed documents and optionally generate answers using an LLM.
-
-
-## Example Client
-
-You can leverage the [example_rag_client.py](./example_rag_client.py) as a starting point for a rag client with inputs that match the route documentation above.
diff --git a/website/versioned_docs/version-v0.5.1/tuning.md b/website/versioned_docs/version-v0.5.1/tuning.md
deleted file mode 100644
index 2f75fde..0000000
--- a/website/versioned_docs/version-v0.5.1/tuning.md
+++ /dev/null
@@ -1,168 +0,0 @@
----
-title: Fine Tuning
----
-
-This document presents how to use the KAITO `workspace` Custom Resource Definition (CRD) for parameter-efficient fine-tuning (PEFT) of models, how a Kubernetes job is designed to automate the tuning workflow, and several best practices for troubleshooting.
-
-## Usage
-KAITO tuning APIs allow users to specify supported tuning methods like [LoRA or QLoRA](https://huggingface.co/docs/peft/main/en/conceptual_guides/lora), the input dataset and configuration settings, and the output destination for saving the tuning results. Currently, KAITO supports URL, image
-and Kubernetes volume as the types of tuning input sources, and image, Kubernetes volume as the types of tuning output destination.
-
-
-### Tuning workspace
-Here are two examples of using KAITO workspace CRD to define workspaces for tuning different models:
-
-Example 1: Tuning [`phi-3-mini`](../../../examples/fine-tuning/kaito_workspace_tuning_phi_3.yaml). This example uses a public dataset specified by a URL in the input.
-
-Example 2: Tuning `falcon-7b`. This example shows how to use an image as the source of input data.
-```yaml
-apiVersion: kaito.sh/v1beta1
-kind: Workspace
-metadata:
-  name: workspace-tuning-falcon
-resource:
-  instanceType: "Standard_NC24ads_A100_v4"
-  labelSelector:
-    matchLabels:
-      app: tuning-falcon
-tuning:
-  preset:
-    name: falcon-7b
-  method: qlora
-  input:
-    image: PULLREGISTRY/DATA_NAME_HERE:0.0.1
-    imagePullSecrets:
-      - IMAGE_PULL_SECRETS_HERE
-  output:
-    image: PUSHREGISTRY/ADAPTER_NAME_HERE:0.0.1  # Tuning Output
-    imagePushSecret: IMAGE_PUSH_SECRET_HERE
-
-```
-
-Example 3: Tuning [`phi-3-mini`](../../../examples/fine-tuning/kaito_workspace_tuning_phi_3_with_pvc_volume.yaml). This example shows how to use a Kubernetes volume as the source of input dataset and output destination. We use AzureFile as an example, but any other supported volume type can be used. You should save your input dataset in the volume before creating the workspace, and the output adapter will be saved in the output volume after the tuning job is completed.
-
-The detailed `TuningSpec` API definitions can be found [here](https://github.com/kaito-project/kaito/blob/2ccc93daf9d5385649f3f219ff131ee7c9c47f3e/api/v1alpha1/workspace_types.go#L145).
-
-### Tuning configurations
-KAITO provides default tuning configurations for different tuning methods. They are managed by Kubernetes configmaps.
-- [default LoRA configmap](../../../charts/kaito/workspace/templates/lora-params.yaml)
-- [default QLoRA configmap](../../../charts/kaito/workspace/templates/qlora-params.yaml)
-
-## Tuning configmaps
-Users can specify a customized configmap via the `Config` field of the `TuningSpec`. The customized configmap should be structured based on the default configmaps provided by KAITO. Please read the following section carefully when attempting to change the default parameters used by KAITO.
-
-### Categorized key parameters
-Note that changing these parameters may largely impact the tuning result. In addition, users can add extra parameters that are not presented in the default configmaps. For a complete list of supported parameters, please refer to the provided huggingface documentation.
-
-ModelConfig([full list](https://huggingface.co/docs/transformers/v4.40.2/en/model_doc/auto#transformers.AutoModelForCausalLM.from_pretrained))
-- torch_dtype: Specifies the data type for PyTorch tensors, e.g., "bfloat16".
-- local_files_only: Indicates whether to only use local files.
-- device_map: Configures device mapping for the model, typically "auto".
-
-QuantizationConfig([full list](https://huggingface.co/docs/transformers/v4.40.2/en/main_classes/quantization#transformers.BitsAndBytesConfig))
-- load_in_4bit: Enables loading the model with 4-bit precision.
-- bnb_4bit_quant_type: Specifies the type of 4-bit quantization, e.g., "nf4".
-- bnb_4bit_compute_dtype: Data type for computation, e.g., "bfloat16".
-- bnb_4bit_use_double_quant: Enables double quantization.
-
-LoraConfig([full list](https://huggingface.co/docs/peft/v0.8.2/en/package_reference/lora#peft.LoraConfig))
-- **r**: Rank of the low-rank matrices used in LoRA.
-- lora_alpha: Scaling factor for LoRA.
-- lora_dropout: Dropout rate for LoRA layers.
-
-TrainingArguments([full list](https://huggingface.co/docs/transformers/v4.40.2/en/main_classes/trainer#transformers.TrainingArguments))
-- ddp_find_unused_parameters: Flag to handle unused parameters during distributed training.
-- save_strategy: Strategy for saving checkpoints, e.g., "epoch".
-- per_device_train_batch_size: Batch size per device during training.
-- num_train_epochs: Total number of training epochs to perform, defaults to 3.0.
-
-DataCollator([full list](https://huggingface.co/docs/transformers/v4.40.2/en/main_classes/data_collator#transformers.DataCollatorForLanguageModeling))
-- mlm: Masked language modeling flag.
-
-DatasetConfig([full list](https://github.com/kaito-project/kaito/blob/main/presets/workspace/tuning/text-generation/cli.py#L44))
-- shuffle_dataset: Whether to shuffle the dataset.
-- train_test_split: Proportion of data used for training, typically set to 1 for using all data.
-
-## Input dataset format
-The input dataset for fine-tuning should follow specific formats defined in the HuggingFace trainer library. Supported formats include conversational and instruction formats.
-
-For example, [HuggingFace Dolly 15k OAI-style dataset](https://huggingface.co/datasets/philschmid/dolly-15k-oai-style/tree/main)
-
-- Conversational format
-  ```json
-  {
-    "messages": [
-      {"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."},
-      {"role": "user", "content": "What's the capital of France?"},
-      {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}
-    ]
-  }
-  ```
-
-For example, [HuggingFace Instruction Dataset](https://huggingface.co/datasets/HuggingFaceH4/instruction-dataset/tree/main)
-
-- Instruction format
-  ```json
-  {"prompt": "<prompt text>", "completion": "<ideal generated text>"}
-  ```
-
-If your dataset is not in one of these formats, it will be passed directly to the training library ([SFTTrainer](https://huggingface.co/docs/trl/en/sft_trainer)) without any preprocessing. This may result in undefined behavior if the dataset does not align with the trainer's expected input structure. To ensure proper functionality, you may need to preprocess the dataset to match one of the supported formats. For more details, please refer to this [documentation](https://huggingface.co/docs/trl/v0.9.4/sft_trainer#dataset-format-support).
-
-
-Note: if you build a container image for the input dataset, please copy the dataset to the **`/data`** directory inside the container.
-
-# Tuning Job
-KAITO uses the Kubernetes **batchv1.job** workload to manage the tuning Pod. When a tuning workspace custom resource is created, the KAITO controller will create a job with the same name as the workspace in the same namespace. The pod structure will be different depending on whether the input and output are specified as URLs, images, or Kubernetes volumes.
-
-## Pod structure when input and output are specified as URLs or images
-To streamline the tuning workflow, KAITO adds two containers in addition to the main container that runs the tuning process. The pod structure is illustrated in Figure 1.
-
-![KAITO fine tuning](/img/kaito-fine-tuning.png)
-
-Figure 1. KAITO tuning pod structure.
-
-- Initcontainer `data-downloader`: It downloads the training input dataset from the URLs specified in the tuning spec if needed. If an image is specified in the input, the `data-downloader` container uses the specified image as the container image. This initcontainer ensures the training data is available locally before the training process starts.
-
-- Sidecar container: It is introduced to support automatically pushing the tuning results to a container registry. This container, with `docker` installed, runs a script to periodically check the training progress. Once the training is done, indicated by a sentinel file created by the training process, the script builds a container image containing the training results and pushes the image to the specified container registry.
-
-- Main container: It uses one of the supported model images. The image entry launches the [fine\_tuning.py](https://github.com/kaito-project/kaito/blob/main/presets/workspace/tuning/text-generation/fine_tuning.py) script.
-
-All three containers use shared local volumes (by mounting the same `EmptyDir` volumes), hence file copies between containers are avoided.
-
-## Pod structure when input and output are specified as Kubernetes volumes
-When the input and output are specified as Kubernetes volumes, the initcontainer and sidecar container are removed and only the main container is used. Since the input dataset and output destination are already available in the specified volumes, the main container can directly access them.
-
-Other than the absence of the init and sidecar containers, the main container is the same as described in the previous section.
-
-# Troubleshooting
-
-### Job pod failures
-When the tuning job reaches the failed state, at least one of the above three containers has encountered errors. Users can check the logs of these containers using the `kubectl logs PODNAME -n NAMESPACE -c CONTAINERNAME` command.
-
-For the initcontainer and sidecar container, possible errors include invalid input/output URLs or invalid image pull secrets. Users can fix these problems by updating the workspace custom resource with corrections. The KAITO controller will create a new job using the updated spec.
-
-For the main container, errors may occur when CUDA reports out of GPU memory. Users should reduce the batch size (the default is 1) if it has been customized to a value larger than 1. If the batch size is already 1, the workspace must be recreated using a different GPU SKU with larger GPU memory. Note that KAITO has optimized the training memory usage by dropping the preallocated memory cache. Our internal tests show that the performance impact due to this change is negligible.
-
-### LoraConfig target modules errors
-If you encounter the error: `ValueError: Target modules {'target_module_here'} not found in the base model. Please check the target modules and try again.`, you need to manually specify the target_modules parameter in your KAITO configmap. This is a huggingface requirement.
-
-This error occurs because the automatic module detection failed for your model. You must identify the specific target modules for your model and explicitly list them in the LoraConfig section. For example, a valid configuration for phi-4-mini-instruct would look like:
-```yaml
-LoraConfig: # Configurable Parameters: https://huggingface.co/docs/peft/v0.8.2/en/package_reference/lora#peft.LoraConfig
-    r: 8
-    lora_alpha: 8
-    lora_dropout: 0.0
-    target_modules: ["qkv_proj", "o_proj"]
-```
-You can find valid module names by checking the "Files info" tab on the model card on Huggingface and examining the model.safetensors.index.json file.
-
-For more information on target_modules configuration, see the official [PEFT documentation](https://huggingface.co/docs/peft/en/package_reference/lora#peft.LoraConfig.target_modules).
-
-### Time for job completion
-The training job can take a long time depending on the size of the input dataset and training pipeline configurations. The total training time is largely determined by the total number of training steps, calculated as:
-```
-total steps = number of epochs * (number of samples in dataset / batch size)
-```
-where `number of epochs` and `batch size` can be customized in the tuning configmap. However, if the `max_steps` parameter is also specified in the configmap, training will stop after reaching the max steps, even if the specified epochs have not been completed. Users can track the tuning progress in the job pod's log, reported by the number of steps completed out of the total.
-
-Please file issues if you experience abnormal slowness of the training job.
diff --git a/website/versioned_docs/version-v0.5.1/usage.md b/website/versioned_docs/version-v0.5.1/usage.md
deleted file mode 100644
index 0857d36..0000000
--- a/website/versioned_docs/version-v0.5.1/usage.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: Usage
----
-
-The detailed usage for Kaito supported models can be found in [**HERE**](./presets.md). In case users want to deploy their own containerized models, they can provide the pod template in the `inference` field of the workspace custom resource (please see [API definitions](../../../api/v1alpha1/workspace_types.go) for details). The controller will create a deployment workload using all provisioned GPU nodes. Note that currently the controller does **NOT** handle automatic model upgrade. It only creates inference workloads based on the preset configurations if the workloads do not exist.
-
-The number of the supported models in Kaito is growing! Please check [this](./preset-onboarding.md) document to see how to add a new supported model.
-
-Starting with version v0.3.0, Kaito supports model fine-tuning and using fine-tuned adapters in the inference service. Refer to the [tuning document](./tuning.md) and [inference document](./inference.md) for more information.
diff --git a/website/versioned_docs/version-v0.5.0/aws.md b/website/versioned_docs/version-v0.5.x/aws.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/aws.md
rename to website/versioned_docs/version-v0.5.x/aws.md
diff --git a/website/versioned_docs/version-v0.5.0/contributing.md b/website/versioned_docs/version-v0.5.x/contributing.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/contributing.md
rename to website/versioned_docs/version-v0.5.x/contributing.md
diff --git a/website/versioned_docs/version-v0.5.0/custom-model.md b/website/versioned_docs/version-v0.5.x/custom-model.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/custom-model.md
rename to website/versioned_docs/version-v0.5.x/custom-model.md
diff --git a/website/versioned_docs/version-v0.5.0/example_rag_client.py b/website/versioned_docs/version-v0.5.x/example_rag_client.py
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/example_rag_client.py
rename to website/versioned_docs/version-v0.5.x/example_rag_client.py
diff --git a/website/versioned_docs/version-v0.5.0/faq.md b/website/versioned_docs/version-v0.5.x/faq.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/faq.md
rename to website/versioned_docs/version-v0.5.x/faq.md
diff --git a/website/versioned_docs/version-v0.5.0/inference.md b/website/versioned_docs/version-v0.5.x/inference.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/inference.md
rename to website/versioned_docs/version-v0.5.x/inference.md
diff --git a/website/versioned_docs/version-v0.5.1/installation.md b/website/versioned_docs/version-v0.5.x/installation.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.1/installation.md
rename to website/versioned_docs/version-v0.5.x/installation.md
diff --git a/website/versioned_docs/version-v0.5.1/intro.md b/website/versioned_docs/version-v0.5.x/intro.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.1/intro.md
rename to website/versioned_docs/version-v0.5.x/intro.md
diff --git a/website/versioned_docs/version-v0.5.0/kaito-on-byo-gpu-nodes.md b/website/versioned_docs/version-v0.5.x/kaito-on-byo-gpu-nodes.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/kaito-on-byo-gpu-nodes.md
rename to website/versioned_docs/version-v0.5.x/kaito-on-byo-gpu-nodes.md
diff --git a/website/versioned_docs/version-v0.5.0/kaito-oom-prevention.md b/website/versioned_docs/version-v0.5.x/kaito-oom-prevention.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/kaito-oom-prevention.md
rename to website/versioned_docs/version-v0.5.x/kaito-oom-prevention.md
diff --git a/website/versioned_docs/version-v0.5.0/model-as-oci-artifacts.md b/website/versioned_docs/version-v0.5.x/model-as-oci-artifacts.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/model-as-oci-artifacts.md
rename to website/versioned_docs/version-v0.5.x/model-as-oci-artifacts.md
diff --git a/website/versioned_docs/version-v0.5.0/monitoring.md b/website/versioned_docs/version-v0.5.x/monitoring.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/monitoring.md
rename to website/versioned_docs/version-v0.5.x/monitoring.md
diff --git a/website/versioned_docs/version-v0.5.0/multi-node-inference.md b/website/versioned_docs/version-v0.5.x/multi-node-inference.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/multi-node-inference.md
rename to website/versioned_docs/version-v0.5.x/multi-node-inference.md
diff --git a/website/versioned_docs/version-v0.5.0/preset-onboarding.md b/website/versioned_docs/version-v0.5.x/preset-onboarding.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/preset-onboarding.md
rename to website/versioned_docs/version-v0.5.x/preset-onboarding.md
diff --git a/website/versioned_docs/version-v0.5.0/presets.md b/website/versioned_docs/version-v0.5.x/presets.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/presets.md
rename to website/versioned_docs/version-v0.5.x/presets.md
diff --git a/website/versioned_docs/version-v0.5.0/proposals.md b/website/versioned_docs/version-v0.5.x/proposals.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/proposals.md
rename to website/versioned_docs/version-v0.5.x/proposals.md
diff --git a/website/versioned_docs/version-v0.5.0/quick-start.md b/website/versioned_docs/version-v0.5.x/quick-start.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/quick-start.md
rename to website/versioned_docs/version-v0.5.x/quick-start.md
diff --git a/website/versioned_docs/version-v0.5.0/rag.md b/website/versioned_docs/version-v0.5.x/rag.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/rag.md
rename to website/versioned_docs/version-v0.5.x/rag.md
diff --git a/website/versioned_docs/version-v0.5.1/tool-calling.md b/website/versioned_docs/version-v0.5.x/tool-calling.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.1/tool-calling.md
rename to website/versioned_docs/version-v0.5.x/tool-calling.md
diff --git a/website/versioned_docs/version-v0.5.0/tuning.md b/website/versioned_docs/version-v0.5.x/tuning.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/tuning.md
rename to website/versioned_docs/version-v0.5.x/tuning.md
diff --git a/website/versioned_docs/version-v0.5.0/usage.md b/website/versioned_docs/version-v0.5.x/usage.md
similarity index 100%
rename from website/versioned_docs/version-v0.5.0/usage.md
rename to website/versioned_docs/version-v0.5.x/usage.md
diff --git a/website/versioned_sidebars/version-v0.4.6-sidebars.json b/website/versioned_sidebars/version-v0.4.6-sidebars.json
deleted file mode 100644
index 96de6c0..0000000
--- a/website/versioned_sidebars/version-v0.4.6-sidebars.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
-  "sidebar": [
-    {
-      "type": "category",
-      "label": "Getting Started",
-      "collapsed": false,
-      "items": [
-        "intro",
-        "installation",
-        "quick-start",
-        "presets",
-        "usage",
-        "faq"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Features",
-      "collapsed": false,
-      "items": [
-        "inference",
-        "multi-node-inference",
-        "tuning",
-        "rag",
-        "custom-model",
-        "tool-calling"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Cloud Providers",
-      "collapsed": false,
-      "items": [
-        "aws"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Operations",
-      "collapsed": false,
-      "items": [
-        "monitoring",
-        "kaito-oom-prevention",
-        "kaito-on-byo-gpu-nodes"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Contributing",
-      "collapsed": false,
-      "items": [
-        "contributing",
-        "preset-onboarding",
-        "proposals"
-      ]
-    }
-  ]
-}
diff --git a/website/versioned_sidebars/version-v0.5.1-sidebars.json b/website/versioned_sidebars/version-v0.5.1-sidebars.json
deleted file mode 100644
index b896459..0000000
--- a/website/versioned_sidebars/version-v0.5.1-sidebars.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
-  "sidebar": [
-    {
-      "type": "category",
-      "label": "Getting Started",
-      "collapsed": false,
-      "items": [
-        "intro",
-        "installation",
-        "quick-start",
-        "presets",
-        "usage",
-        "faq"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Features",
-      "collapsed": false,
-      "items": [
-        "inference",
-        "multi-node-inference",
-        "tuning",
-        "rag",
-        "custom-model",
-        "tool-calling",
-        "model-as-oci-artifacts"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Cloud Providers",
-      "collapsed": false,
-      "items": [
-        "aws"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Operations",
-      "collapsed": false,
-      "items": [
-        "monitoring",
-        "kaito-oom-prevention",
-        "kaito-on-byo-gpu-nodes"
-      ]
-    },
-    {
-      "type": "category",
-      "label": "Contributing",
-      "collapsed": false,
-      "items": [
-        "contributing",
-        "preset-onboarding",
-        "proposals"
-      ]
-    }
-  ]
-}
diff --git a/website/versioned_sidebars/version-v0.5.0-sidebars.json b/website/versioned_sidebars/version-v0.5.x-sidebars.json
similarity index 100%
rename from website/versioned_sidebars/version-v0.5.0-sidebars.json
rename to website/versioned_sidebars/version-v0.5.x-sidebars.json
diff --git a/website/versions.json b/website/versions.json
index 22784ab..f459232 100644
--- a/website/versions.json
+++ b/website/versions.json
@@ -1,5 +1,4 @@
 [
   "v0.6.x",
-  "v0.5.1",
-  "v0.5.0"
+  "v0.5.x"
 ]



Test patch:
diff --git a/pkg/workspace/controllers/workspace_controller_test.go b/pkg/workspace/controllers/workspace_controller_test.go
index 9e7c0c4..01292f4 100644
--- a/pkg/workspace/controllers/workspace_controller_test.go
+++ b/pkg/workspace/controllers/workspace_controller_test.go
@@ -24,19 +24,21 @@ import (
 	"time"
 
 	"github.com/awslabs/operatorpkg/status"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
-	"gotest.tools/assert"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/ptr"
 	"knative.dev/pkg/apis"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	karpenterv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
 
 	"github.com/kaito-project/kaito/api/v1beta1"
+	"github.com/kaito-project/kaito/pkg/featuregates"
 	"github.com/kaito-project/kaito/pkg/utils"
 	"github.com/kaito-project/kaito/pkg/utils/consts"
 	"github.com/kaito-project/kaito/pkg/utils/test"
@@ -378,7 +380,7 @@ func TestEnsureService(t *testing.T) {
 
 			err := reconciler.ensureService(ctx, tc.workspace)
 			if tc.expectedError == nil {
-				assert.Check(t, err == nil, "Not expected to return error")
+				assert.NoError(t, err)
 			} else {
 				assert.Equal(t, tc.expectedError.Error(), err.Error())
 			}
@@ -494,7 +496,7 @@ func TestApplyInferenceWithPreset(t *testing.T) {
 
 			err := reconciler.applyInference(ctx, &tc.workspace)
 			if tc.expectedError == nil {
-				assert.Check(t, err == nil, fmt.Sprintf("Not expected to return error: %v", err))
+				assert.NoError(t, err)
 			} else {
 				assert.Equal(t, tc.expectedError.Error(), err.Error())
 			}
@@ -551,7 +553,7 @@ func TestApplyInferenceWithTemplate(t *testing.T) {
 
 			err := reconciler.applyInference(ctx, &tc.workspace)
 			if tc.expectedError == nil {
-				assert.Check(t, err == nil, "Not expected to return error")
+				assert.NoError(t, err)
 			} else {
 				assert.Equal(t, tc.expectedError.Error(), err.Error())
 			}
@@ -560,6 +562,10 @@ func TestApplyInferenceWithTemplate(t *testing.T) {
 }
 
 func TestGetAllQualifiedNodes(t *testing.T) {
+	// Save and restore feature gate state
+	originalNAP := featuregates.FeatureGates[consts.FeatureFlagDisableNodeAutoProvisioning]
+	defer func() { featuregates.FeatureGates[consts.FeatureFlagDisableNodeAutoProvisioning] = originalNAP }()
+
 	deletedNode := corev1.Node{
 		ObjectMeta: v1.ObjectMeta{
 			Name: "node4",
@@ -571,12 +577,16 @@ func TestGetAllQualifiedNodes(t *testing.T) {
 	}
 	mockWorkspaceWithPreferredNodes := test.MockWorkspaceWithPreferredNodes.DeepCopy()
 	mockWorkspaceWithPreferredNodes.Resource.PreferredNodes = []string{"node-p1", "node-p2"}
+	mockWorkspaceWithPreferredNodes.Resource.Count = ptr.To(2)
+
+	mockWorkspaceWithSinglePreferredNode := test.MockWorkspaceWithPreferredNodes.DeepCopy()
 
 	testcases := map[string]struct {
 		callMocks     func(c *test.MockClient)
 		workspace     *v1beta1.Workspace
 		expectedError error
 		expectedNodes []string
+		disableNAP    bool
 	}{
 		"Fails to get qualified nodes because can't list nodes": {
 			callMocks: func(c *test.MockClient) {
@@ -585,6 +595,7 @@ func TestGetAllQualifiedNodes(t *testing.T) {
 			workspace:     test.MockWorkspaceDistributedModel,
 			expectedError: errors.New("Failed to list nodes"),
 			expectedNodes: nil,
+			disableNAP:    false,
 		},
 		"Gets all qualified nodes": {
 			callMocks: func(c *test.MockClient) {
@@ -606,6 +617,7 @@ func TestGetAllQualifiedNodes(t *testing.T) {
 			workspace:     test.MockWorkspaceDistributedModel,
 			expectedError: nil,
 			expectedNodes: []string{"node1"},
+			disableNAP:    false,
 		},
 		"Gets all qualified nodes with preferred": {
 			callMocks: func(c *test.MockClient) {
@@ -676,14 +688,187 @@ func TestGetAllQualifiedNodes(t *testing.T) {
 
 				c.On("List", mock.IsType(context.Background()), mock.IsType(&corev1.NodeList{}), mock.Anything).Return(nil)
 			},
-			workspace:     mockWorkspaceWithPreferredNodes,
+			workspace:     mockWorkspaceWithSinglePreferredNode,
 			expectedError: nil,
 			expectedNodes: []string{"node-p1"},
 		},
+		"NAP disabled: all preferred nodes present and ready, returns all": {
+			callMocks: func(c *test.MockClient) {
+				nodeList := &corev1.NodeList{Items: []corev1.Node{
+					{
+						ObjectMeta: v1.ObjectMeta{
+							Name: "node-p1",
+							Labels: map[string]string{
+								corev1.LabelInstanceTypeStable: "vendor1",
+								"apps":                         "test",
+							},
+						},
+						Status: corev1.NodeStatus{
+							Conditions: []corev1.NodeCondition{{
+								Type:   corev1.NodeReady,
+								Status: corev1.ConditionTrue,
+							}},
+						},
+					},
+					{
+						ObjectMeta: v1.ObjectMeta{
+							Name: "node-p2",
+							Labels: map[string]string{
+								corev1.LabelInstanceTypeStable: "vendor2",
+								"apps":                         "test",
+							},
+						},
+						Status: corev1.NodeStatus{
+							Conditions: []corev1.NodeCondition{{
+								Type:   corev1.NodeReady,
+								Status: corev1.ConditionTrue,
+							}},
+						},
+					},
+				}}
+
+				c.CreateOrUpdateObjectInMap(&nodeList.Items[0])
+				c.CreateOrUpdateObjectInMap(&nodeList.Items[1])
+
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil)
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil)
+
+			},
+			workspace:     mockWorkspaceWithPreferredNodes.DeepCopy(),
+			expectedError: nil,
+			expectedNodes: []string{"node-p1", "node-p2"},
+			disableNAP:    true,
+		},
+		"NAP disabled: one preferred node not ready, returns error": {
+			callMocks: func(c *test.MockClient) {
+				nodeList := &corev1.NodeList{Items: []corev1.Node{
+					{
+						ObjectMeta: v1.ObjectMeta{
+							Name: "node-p1",
+							Labels: map[string]string{
+								corev1.LabelInstanceTypeStable: "vendor1",
+								"apps":                         "test",
+							},
+						},
+						Status: corev1.NodeStatus{
+							Conditions: []corev1.NodeCondition{{
+								Type:   corev1.NodeReady,
+								Status: corev1.ConditionTrue,
+							}},
+						},
+					},
+					{
+						ObjectMeta: v1.ObjectMeta{
+							Name: "node-p2",
+							Labels: map[string]string{
+								corev1.LabelInstanceTypeStable: "vendor2",
+								"apps":                         "test",
+							},
+						},
+						Status: corev1.NodeStatus{
+							Conditions: []corev1.NodeCondition{{
+								Type:   corev1.NodeReady,
+								Status: corev1.ConditionFalse,
+							}},
+						},
+					},
+				}}
+
+				c.CreateOrUpdateObjectInMap(&nodeList.Items[0])
+				c.CreateOrUpdateObjectInMap(&nodeList.Items[1])
+
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil)
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil)
+			},
+			workspace:     mockWorkspaceWithPreferredNodes.DeepCopy(),
+			expectedError: errors.New("when node auto-provisioning is disabled, at least 2 preferred nodes must match the label selector and be ready and not deleting, only have 1"),
+			expectedNodes: nil,
+			disableNAP:    true,
+		},
+		"NAP disabled: one preferred node deleting, returns error": {
+			callMocks: func(c *test.MockClient) {
+				deletingTime := v1.Time{Time: time.Now()}
+				nodeList := &corev1.NodeList{Items: []corev1.Node{
+					{
+						ObjectMeta: v1.ObjectMeta{
+							Name: "node-p1",
+							Labels: map[string]string{
+								corev1.LabelInstanceTypeStable: "vendor1",
+								"apps":                         "test",
+							},
+						},
+						Status: corev1.NodeStatus{
+							Conditions: []corev1.NodeCondition{{
+								Type:   corev1.NodeReady,
+								Status: corev1.ConditionTrue,
+							}},
+						},
+					},
+					{
+						ObjectMeta: v1.ObjectMeta{
+							Name: "node-p2",
+							Labels: map[string]string{
+								corev1.LabelInstanceTypeStable: "vendor2",
+								"apps":                         "test",
+							},
+							DeletionTimestamp: &deletingTime,
+						},
+						Status: corev1.NodeStatus{
+							Conditions: []corev1.NodeCondition{{
+								Type:   corev1.NodeReady,
+								Status: corev1.ConditionTrue,
+							}},
+						},
+					},
+				}}
+
+				c.CreateOrUpdateObjectInMap(&nodeList.Items[0])
+				c.CreateOrUpdateObjectInMap(&nodeList.Items[1])
+
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil)
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil)
+			},
+			workspace:     mockWorkspaceWithPreferredNodes.DeepCopy(),
+			expectedError: errors.New("when node auto-provisioning is disabled, at least 2 preferred nodes must match the label selector and be ready and not deleting, only have 1"),
+			expectedNodes: nil,
+			disableNAP:    true,
+		},
+		"NAP disabled: missing preferred node, returns error": {
+			callMocks: func(c *test.MockClient) {
+				nodeList := &corev1.NodeList{Items: []corev1.Node{
+					{
+						ObjectMeta: v1.ObjectMeta{
+							Name: "node-p1",
+							Labels: map[string]string{
+								corev1.LabelInstanceTypeStable: "vendor1",
+								"apps":                         "test",
+							},
+						},
+						Status: corev1.NodeStatus{
+							Conditions: []corev1.NodeCondition{{
+								Type:   corev1.NodeReady,
+								Status: corev1.ConditionTrue,
+							}},
+						},
+					},
+				}}
+
+				c.CreateOrUpdateObjectInMap(&nodeList.Items[0])
+
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(nil)
+				c.On("Get", mock.IsType(context.Background()), mock.Anything, mock.IsType(&corev1.Node{}), mock.Anything).Return(errors.New("nodes \"node-p2\" not found"))
+			},
+			workspace:     mockWorkspaceWithPreferredNodes.DeepCopy(),
+			expectedError: errors.New("when node auto-provisioning is disabled, at least 2 preferred nodes must match the label selector and be ready and not deleting, only have 1"),
+			expectedNodes: nil,
+			disableNAP:    true,
+		},
 	}
 
 	for k, tc := range testcases {
 		t.Run(k, func(t *testing.T) {
+			featuregates.FeatureGates[consts.FeatureFlagDisableNodeAutoProvisioning] = tc.disableNAP
+
 			mockClient := test.NewClient()
 			reconciler := &WorkspaceReconciler{
 				Client: mockClient,
@@ -696,14 +881,15 @@ func TestGetAllQualifiedNodes(t *testing.T) {
 			nodes, err := reconciler.getAllQualifiedNodes(ctx, tc.workspace)
 
 			if tc.expectedError != nil {
-				assert.Equal(t, tc.expectedError.Error(), err.Error())
-				assert.Check(t, nodes == nil, "Response node array should be nil")
+				assert.NotNil(t, err)
+				assert.Equal(t, tc.expectedError, err)
+				assert.Nil(t, nodes)
 				return
 			}
 
-			assert.Check(t, err == nil, "Not expected to return error")
-			assert.Check(t, nodes != nil, "Response node array should not be nil")
-			assert.Check(t, len(nodes) == len(tc.expectedNodes), "Unexpected qualified nodes")
+			assert.NoError(t, err)
+			assert.NotNil(t, nodes)
+			assert.Equal(t, len(tc.expectedNodes), len(nodes))
 		})
 	}
 }
@@ -855,7 +1041,7 @@ func TestApplyWorkspaceResource(t *testing.T) {
 
 			err := reconciler.applyWorkspaceResource(ctx, &tc.workspace)
 			if tc.expectedError == nil {
-				assert.Check(t, err == nil, "Not expected to return error")
+				assert.NoError(t, err)
 			} else {
 				assert.Equal(t, tc.expectedError.Error(), err.Error())
 			}
@@ -1062,7 +1248,7 @@ func TestSyncControllerRevision(t *testing.T) {
 
 			err := reconciler.syncControllerRevision(ctx, &tc.workspace)
 			if tc.expectedError == nil {
-				assert.Check(t, err == nil, "Not expected to return error")
+				assert.NoError(t, err)
 			} else {
 				assert.Equal(t, tc.expectedError.Error(), err.Error())
 			}