# ScaledObject that scales vLLM via the CUSTOM external scaler (one composite trigger).
# This is the DEFAULT scaling path — the one bootstrap/platform.sh's autoscaling() layer
# and gitops/applications.yaml's inference-scaler Application actually deploy/sync. Apply
# this OR the built-in-scaler version (gpu-node/keda-scaledobject-gpu.yaml) — only one
# ScaledObject may target a given Deployment.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-qwen
  namespace: inference
spec:
  scaleTargetRef:
    name: vllm-qwen
  minReplicaCount: 1
  maxReplicaCount: 2           # two physical GPUs — matches gpu-node/keda-scaledobject-gpu.yaml
  # 2x the 15s scrape interval (../../gpu-node/observability.yaml's PodMonitor),
  # not equal to it -- see docs/autoscaling-signal-staleness.md. This governs
  # the GetMetrics/HPA metric-value path; streamPollInterval below governs the
  # scaler's own, independent StreamIsActive activation ticker.
  pollingInterval: 30
  cooldownPeriod: 60
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleDown:
          # >= 2x the worst-case signal age (scrape 15s + poll 30s = 45s). See
          # docs/autoscaling-signal-staleness.md.
          stabilizationWindowSeconds: 90
  triggers:
    - type: external
      metadata:
        scalerAddress: inference-scaler.inference.svc:6000
        # Prometheus via the laptop NODE IP (hostNetwork Prometheus, :9090) — cross-node
        # ClusterIP is down under WSL2 mirrored mode (see docs/cluster-troubleshooting-log.md).
        prometheusAddress: http://192.168.18.142:9090
        queueThreshold: "3"
        kvCacheThreshold: "0.7"
        activationThreshold: "1"
        # Same 2x-scrape-interval rule as pollingInterval above, applied to this
        # separate ticker explicitly rather than leaving it at the scaler's
        # default (10s -- below the 15s scrape interval, which was re-reading
        # the same sample most ticks). See docs/autoscaling-signal-staleness.md.
        streamPollInterval: "30s"
