GKE 추론 게이트웨이 구성 맞춤설정


이 페이지에서는 GKE 추론 게이트웨이 배포를 맞춤설정하는 방법을 설명합니다.

이 페이지는 GKE 인프라를 관리하는 네트워킹 전문가와 AI 워크로드를 관리하는 플랫폼 관리자를 대상으로 합니다.

추론 워크로드를 관리하고 최적화하려면 GKE 추론 게이트웨이의 고급 기능을 구성합니다.

다음 고급 기능을 이해하고 구성합니다.

AI 보안 및 안전 검사 구성

GKE 추론 게이트웨이는 Model Armor와 통합되어 대규모 언어 모델 (LLM)을 사용하는 애플리케이션의 프롬프트와 응답에 대한 안전 검사를 실행합니다. 이 통합은 애플리케이션 수준의 안전 조치를 보완하는 인프라 수준의 추가 안전 시행 레이어를 제공합니다. 이렇게 하면 모든 LLM 트래픽에 중앙 집중식 정책 적용이 가능합니다.

다음 다이어그램은 GKE 클러스터에서 GKE 추론 게이트웨이와 모델 Armor를 통합하는 방법을 보여줍니다.

GKE 클러스터의 Google Cloud Model Armor 통합
그림: GKE 클러스터의 모델 Armor 통합

AI 안전 검사를 구성하려면 다음 단계를 따르세요.

  1. 다음 기본 요건이 충족되었는지 확인합니다.

    1. Google Cloud 프로젝트에서 Model Armor 서비스를 사용 설정합니다.
    2. Model Armor 콘솔, Google Cloud CLI 또는 API를 사용하여 Model Armor 템플릿을 만듭니다.
  2. my-model-armor-template-name-id라는 Model Armor 템플릿을 이미 만들었는지 확인합니다.

  3. GCPTrafficExtension을 구성하려면 다음 단계를 따르세요.

    1. 다음 샘플 매니페스트를 gcp-traffic-extension.yaml로 저장합니다.

      kind: GCPTrafficExtension
      apiVersion: networking.gke.io/v1
      metadata:
        name: my-model-armor-extension
      spec:
        targetRefs:
        - group: "gateway.networking.k8s.io"
          kind: Gateway
          name: GATEWAY_NAME
        extensionChains:
        - name: my-model-armor-chain1
          matchCondition:
            celExpressions:
            - celMatcher: request.path.startsWith("/")
          extensions:
          - name: my-model-armor-service
            supportedEvents:
            - RequestHeaders
            timeout: 1s
            googleAPIServiceName: "modelarmor.us-central1.rep.googleapis.com"
            metadata:
              'extensionPolicy': MODEL_ARMOR_TEMPLATE_NAME
              'sanitizeUserPrompt': 'true'
              'sanitizeUserResponse': 'true'
      

      다음을 바꿉니다.

      • GATEWAY_NAME: 게이트웨이의 이름입니다.
      • MODEL_ARMOR_TEMPLATE_NAME: 모델 장갑 템플릿의 이름입니다.

      gcp-traffic-extension.yaml 파일에는 다음 설정이 포함됩니다.

      • targetRefs: 이 확장 프로그램이 적용되는 게이트웨이를 지정합니다.
      • extensionChains: 트래픽에 적용할 확장 프로그램 체인을 정의합니다.
      • matchCondition: 확장 프로그램이 적용되는 조건을 정의합니다.
      • extensions: 적용할 확장 프로그램을 정의합니다.
      • supportedEvents: 확장 프로그램이 호출되는 이벤트를 지정합니다.
      • timeout: 확장 프로그램의 제한 시간을 지정합니다.
      • googleAPIServiceName: 확장 프로그램의 서비스 이름을 지정합니다.
      • metadata: extensionPolicy 및 프롬프트 또는 응답 정리 설정을 비롯한 확장 프로그램의 메타데이터를 지정합니다.
    2. 샘플 매니페스트를 클러스터에 적용합니다.

      kubectl apply -f `gcp-traffic-extension.yaml`
      

AI 안전 검사를 구성하고 게이트웨이와 통합하면 Model Armor가 정의된 규칙에 따라 메시지와 응답을 자동으로 필터링합니다.

관측 가능성 구성

GKE 추론 게이트웨이는 추론 워크로드의 상태, 성능, 동작에 관한 통계를 제공합니다. 이를 통해 문제를 식별하고 해결하고, 리소스 활용도를 최적화하고, 애플리케이션의 안정성을 보장할 수 있습니다.

Google Cloud 는 GKE 추론 게이트웨이에 대한 추론 관측 가능성을 제공하는 다음과 같은 Cloud Monitoring 대시보드를 제공합니다.

  • GKE 추론 게이트웨이 대시보드: InferencePool의 요청 및 토큰 처리량, 지연 시간, 오류, 캐시 사용률과 같은 LLM 제공을 위한 골드 표준 측정항목을 제공합니다. 사용 가능한 GKE 추론 게이트웨이 측정항목의 전체 목록은 노출된 측정항목을 참고하세요.
  • 모델 서버 대시보드: 모델 서버의 골든 신호에 관한 대시보드를 제공합니다. 이를 통해 KVCache UtilizationQueue length와 같은 모델 서버의 부하와 성능을 모니터링할 수 있습니다. 이를 통해 모델 서버의 부하와 성능을 모니터링할 수 있습니다.
  • 부하 분산기 대시보드: 초당 요청, 엔드 투 엔드 요청 처리 지연 시간, 요청-응답 상태 코드와 같은 부하 분산기의 측정항목을 보고합니다. 이러한 측정항목은 엔드 투 엔드 요청 처리의 성능을 파악하고 오류를 식별하는 데 도움이 됩니다.
  • Data Center GPU Manager (DCGM) 측정항목: NVIDIA GPU의 성능 및 사용률과 같은 NVIDIA GPU의 측정항목을 제공합니다. Cloud Monitoring에서 NVIDIA Data Center GPU Manager (DCGM) 측정항목을 구성할 수 있습니다. 자세한 내용은 DCGM 측정항목 수집 및 보기를 참고하세요.

GKE 추론 게이트웨이 대시보드 보기

GKE 추론 게이트웨이 대시보드를 보려면 다음 단계를 따르세요.

  1. Google Cloud 콘솔에서 Monitoring 페이지로 이동합니다.

    Monitoring으로 이동

  2. 탐색창에서 대시보드를 선택합니다.

  3. 통합 섹션에서 GMP를 선택합니다.

  4. Cloud Monitoring 대시보드 템플릿 페이지에서 '게이트웨이'를 검색합니다.

  5. GKE 추론 게이트웨이 대시보드를 봅니다.

또는 모니터링 대시보드의 안내를 따르세요.

모델 서버 관측 가능성 대시보드 구성

각 모델 서버에서 골드 신호를 수집하고 GKE 추론 게이트웨이 성능에 기여하는 요소를 파악하려면 모델 서버의 자동 모니터링을 구성하면 됩니다. 여기에는 다음과 같은 모델 서버가 포함됩니다.

통합 대시보드를 보려면 다음 단계를 따르세요.

  1. 모델 서버에서 측정항목을 수집합니다.
  2. Google Cloud 콘솔에서 Monitoring 페이지로 이동합니다.

    Monitoring으로 이동

  3. 탐색창에서 대시보드를 선택합니다.

  4. 통합에서 GMP를 선택합니다. 해당하는 통합 대시보드가 표시됩니다.

    통합 대시보드 보기
    그림: 통합 대시보드

자세한 내용은 애플리케이션 모니터링 맞춤설정을 참고하세요.

부하 분산기 관측성 대시보드 구성

GKE 추론 게이트웨이와 함께 애플리케이션 부하 분산기를 사용하려면 다음 단계에 따라 대시보드를 가져옵니다.

  1. 부하 분산기 대시보드를 만들려면 다음 파일을 만들고 dashboard.json로 저장합니다.

    
    {
        "displayName": "GKE Inference Gateway (Load Balancer) Prometheus Overview",
        "dashboardFilters": [
          {
            "filterType": "RESOURCE_LABEL",
            "labelKey": "cluster",
            "templateVariable": "",
            "valueType": "STRING"
          },
          {
            "filterType": "RESOURCE_LABEL",
            "labelKey": "location",
            "templateVariable": "",
            "valueType": "STRING"
          },
          {
            "filterType": "RESOURCE_LABEL",
            "labelKey": "namespace",
            "templateVariable": "",
            "valueType": "STRING"
          },
          {
            "filterType": "RESOURCE_LABEL",
            "labelKey": "forwarding_rule_name",
            "templateVariable": "",
            "valueType": "STRING"
          }
        ],
        "labels": {},
        "mosaicLayout": {
          "columns": 48,
          "tiles": [
            {
              "height": 8,
              "width": 48,
              "widget": {
                "title": "",
                "id": "",
                "text": {
                  "content": "### Inferece Gateway Metrics\n\nPlease refer to the [official documentation](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/site-src/guides/metrics.md) for more details of underlying metrics used in the dashboard.\n\n\n### External Application Load Balancer Metrics\n\nPlease refer to the [pubic page](/load-balancing/docs/metrics) for complete list of External Application Load Balancer metrics.\n\n### Model Server Metrics\n\nYou can redirect to the detail dashboard for model servers under the integration tab",
                  "format": "MARKDOWN",
                  "style": {
                    "backgroundColor": "#FFFFFF",
                    "fontSize": "FS_EXTRA_LARGE",
                    "horizontalAlignment": "H_LEFT",
                    "padding": "P_EXTRA_SMALL",
                    "pointerLocation": "POINTER_LOCATION_UNSPECIFIED",
                    "textColor": "#212121",
                    "verticalAlignment": "V_TOP"
                  }
                }
              }
            },
            {
              "yPos": 8,
              "height": 4,
              "width": 48,
              "widget": {
                "title": "External Application Load Balancer",
                "id": "",
                "sectionHeader": {
                  "dividerBelow": false,
                  "subtitle": ""
                }
              }
            },
            {
              "yPos": 12,
              "height": 15,
              "width": 24,
              "widget": {
                "title": "E2E Request Latency p99 (by code)",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.99, sum by(le, response_code) (rate(loadbalancing_googleapis_com:https_external_regional_total_latencies_bucket{monitored_resource=\"http_external_regional_lb_rule\",forwarding_rule_name=~\".*inference-gateway.*\"}[1m])))",
                        "unitOverride": "ms"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 12,
              "height": 43,
              "width": 48,
              "widget": {
                "title": "Regional",
                "collapsibleGroup": {
                  "collapsed": false
                },
                "id": ""
              }
            },
            {
              "yPos": 12,
              "xPos": 24,
              "height": 15,
              "width": 24,
              "widget": {
                "title": "E2E Request Latency p95 (by code)",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le, response_code) (rate(loadbalancing_googleapis_com:https_external_regional_total_latencies_bucket{monitored_resource=\"http_external_regional_lb_rule\",forwarding_rule_name=~\".*inference-gateway.*\"}[1m])))",
                        "unitOverride": "ms"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 27,
              "height": 15,
              "width": 24,
              "widget": {
                "title": "E2E Request Latency p90 (by code)",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.90, sum by(le, response_code) (rate(loadbalancing_googleapis_com:https_external_regional_total_latencies_bucket{monitored_resource=\"http_external_regional_lb_rule\",forwarding_rule_name=~\".*inference-gateway.*\"}[1m])))",
                        "unitOverride": "ms"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 27,
              "xPos": 24,
              "height": 15,
              "width": 24,
              "widget": {
                "title": "E2E Request Latency p50 (by code)",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.50, sum by(le, response_code) (rate(loadbalancing_googleapis_com:https_external_regional_total_latencies_bucket{monitored_resource=\"http_external_regional_lb_rule\",forwarding_rule_name=~\".*inference-gateway.*\"}[1m])))",
                        "unitOverride": "ms"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 42,
              "height": 13,
              "width": 48,
              "widget": {
                "title": "Request /s (by code)",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "sum by (response_code)(rate(loadbalancing_googleapis_com:https_external_regional_request_count{monitored_resource=\"http_external_regional_lb_rule\", forwarding_rule_name=~\".*inference-gateway.*\"}[1m]))",
                        "unitOverride": ""
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 55,
              "height": 4,
              "width": 48,
              "widget": {
                "title": "Inference Optimized Gateway",
                "id": "",
                "sectionHeader": {
                  "dividerBelow": false,
                  "subtitle": ""
                }
              }
            },
            {
              "yPos": 59,
              "height": 17,
              "width": 48,
              "widget": {
                "title": "Request Latency",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(inference_model_request_duration_seconds_bucket{}[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(inference_model_request_duration_seconds_bucket{}[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(inference_model_request_duration_seconds_bucket{}[${__interval}])))",
                        "unitOverride": "s"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 59,
              "height": 65,
              "width": 48,
              "widget": {
                "title": "Inference Model",
                "collapsibleGroup": {
                  "collapsed": false
                },
                "id": ""
              }
            },
            {
              "yPos": 76,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Request / s",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "sum by(model_name, target_model_name) (rate(inference_model_request_total{}[${__interval}]))",
                        "unitOverride": ""
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 76,
              "xPos": 24,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Request Error / s",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "sum by (error_code,model_name,target_model_name) (rate(inference_model_request_error_total[${__interval}]))",
                        "unitOverride": ""
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 92,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Request Size",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(inference_model_request_sizes_bucket{}[${__interval}])))",
                        "unitOverride": "By"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(inference_model_request_sizes_bucket{}[${__interval}])))",
                        "unitOverride": "By"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(inference_model_request_sizes_bucket{}[${__interval}])))",
                        "unitOverride": "By"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 92,
              "xPos": 24,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Response Size",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(inference_model_response_sizes_bucket{}[${__interval}])))",
                        "unitOverride": "By"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(inference_model_response_sizes_bucket{}[${__interval}])))",
                        "unitOverride": "By"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(inference_model_response_sizes_bucket{}[${__interval}])))",
                        "unitOverride": "By"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 108,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Input Token Count",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(inference_model_input_tokens_bucket{}[${__interval}])))",
                        "unitOverride": ""
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(inference_model_input_tokens_bucket{}[${__interval}])))",
                        "unitOverride": ""
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(inference_model_input_tokens_bucket{}[${__interval}])))",
                        "unitOverride": ""
                      }
                    }
                  ],
                  "thresholds": []
                }
              }
            },
            {
              "yPos": 108,
              "xPos": 24,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Output Token Count",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(inference_model_output_tokens_bucket{}[${__interval}])))",
                        "unitOverride": ""
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(inference_model_output_tokens_bucket{}[${__interval}])))",
                        "unitOverride": ""
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(inference_model_output_tokens_bucket{}[${__interval}])))",
                        "unitOverride": ""
                      }
                    }
                  ],
                  "thresholds": []
                }
              }
            },
            {
              "yPos": 124,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Average KV Cache Utilization",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "sum by (name)(avg_over_time(inference_pool_average_kv_cache_utilization[${__interval}]))*100",
                        "unitOverride": "%"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 124,
              "height": 16,
              "width": 48,
              "widget": {
                "title": "Inference Pool",
                "collapsibleGroup": {
                  "collapsed": false
                },
                "id": ""
              }
            },
            {
              "yPos": 124,
              "xPos": 24,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Average Queue Size",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "sum by (name) (avg_over_time(inference_pool_average_queue_size[${__interval}]))",
                        "unitOverride": ""
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 140,
              "height": 4,
              "width": 48,
              "widget": {
                "title": "Model Server",
                "id": "",
                "sectionHeader": {
                  "dividerBelow": true,
                  "subtitle": "The following charts will only be populated if model server is exporting metrics."
                }
              }
            },
            {
              "yPos": 144,
              "height": 32,
              "width": 48,
              "widget": {
                "title": "vLLM",
                "collapsibleGroup": {
                  "collapsed": false
                },
                "id": ""
              }
            },
            {
              "yPos": 144,
              "xPos": 1,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Token Throughput",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "Prompt Tokens/Sec",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "sum by(model_name) (rate(vllm:prompt_tokens_total[${__interval}]))",
                        "unitOverride": ""
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "Generation Tokens/Sec",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "sum by(model_name) (rate(vllm:generation_tokens_total[${__interval}]))",
                        "unitOverride": ""
                      }
                    }
                  ],
                  "thresholds": []
                }
              }
            },
            {
              "yPos": 144,
              "xPos": 25,
              "height": 16,
              "width": 23,
              "widget": {
                "title": "Request Latency",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(vllm:e2e_request_latency_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(vllm:e2e_request_latency_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(vllm:e2e_request_latency_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 160,
              "xPos": 1,
              "height": 16,
              "width": 24,
              "widget": {
                "title": "Time Per Output Token Latency",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(vllm:time_per_output_token_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(vllm:time_per_output_token_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(vllm:time_per_output_token_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            },
            {
              "yPos": 160,
              "xPos": 25,
              "height": 16,
              "width": 23,
              "widget": {
                "title": "Time To First Token Latency",
                "id": "",
                "xyChart": {
                  "chartOptions": {
                    "displayHorizontal": false,
                    "mode": "COLOR",
                    "showLegend": false
                  },
                  "dataSets": [
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p95",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.95, sum by(le) (rate(vllm:time_to_first_token_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p90",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.9, sum by(le) (rate(vllm:time_to_first_token_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    },
                    {
                      "breakdowns": [],
                      "dimensions": [],
                      "legendTemplate": "p50",
                      "measures": [],
                      "plotType": "LINE",
                      "targetAxis": "Y1",
                      "timeSeriesQuery": {
                        "outputFullDuration": false,
                        "prometheusQuery": "histogram_quantile(0.5, sum by(le) (rate(vllm:time_to_first_token_seconds_bucket[${__interval}])))",
                        "unitOverride": "s"
                      }
                    }
                  ],
                  "thresholds": [],
                  "yAxis": {
                    "label": "",
                    "scale": "LINEAR"
                  }
                }
              }
            }
          ]
        }
      }
    
  2. Google Cloud Armor에 대시보드를 설치하려면 다음 명령어를 실행합니다.

    gcloud monitoring dashboards create --project $PROJECT_ID --config-from-file=dashboard.json
    
  3. Google Cloud Console에서 모니터링 페이지를 엽니다.

    Monitoring으로 이동

  4. 탐색 메뉴에서 대시보드를 선택합니다.

  5. 맞춤 대시보드 목록에서 추론 최적화 게이트웨이 (L7LB 포함) Prometheus 개요 대시보드를 선택합니다.

  6. 외부 애플리케이션 부하 분산기 섹션에는 다음과 같은 부하 분산 측정항목이 표시됩니다.

    • E2E 요청 지연 시간 p99 (코드별): 부하 분산기에서 제공하는 요청의 엔드 투 엔드 요청 지연 시간의 99번째 백분위수를 반환된 상태 코드별로 집계하여 보여줍니다.
    • 요청 수 (코드별): 부하 분산기에서 제공하는 요청 수를 반환된 상태 코드별로 집계하여 표시합니다.

GKE 추론 게이트웨이의 로깅 구성

GKE 추론 게이트웨이의 로깅을 구성하면 문제 해결, 감사, 성능 분석에 유용한 요청 및 응답에 관한 세부정보를 확인할 수 있습니다. HTTP 액세스 로그는 헤더, 상태 코드, 타임스탬프를 비롯한 모든 요청과 응답을 기록합니다. 이 정도의 세부정보를 통해 문제를 식별하고, 오류를 찾고, 추론 워크로드의 동작을 이해할 수 있습니다.

GKE 추론 게이트웨이의 로깅을 구성하려면 각 InferencePool 객체에 HTTP 액세스 로깅을 사용 설정하세요.

  1. 다음 샘플 매니페스트를 logging-backend-policy.yaml로 저장합니다.

    apiVersion: networking.gke.io/v1
    kind: GCPBackendPolicy
    metadata:
      name: logging-backend-policy
      namespace: NAMESPACE_NAME
    spec:
      default:
        logging:
          enabled: true
          sampleRate: 500000
      targetRef:
        group: inference.networking.x-k8s.io
        kind: InferencePool
        name: INFERENCE_POOL_NAME
    

    다음을 바꿉니다.

    • NAMESPACE_NAME: InferencePool가 배포되는 네임스페이스의 이름입니다.
    • INFERENCE_POOL_NAME: InferencePool 이름입니다.
  2. 샘플 매니페스트를 클러스터에 적용합니다.

    kubectl apply -f logging-backend-policy.yaml
    

이 매니페스트를 적용하면 GKE 추론 게이트웨이가 지정된 InferencePool의 HTTP 액세스 로그를 사용 설정합니다. Cloud Logging에서 이러한 로그를 볼 수 있습니다. 로그에는 요청 URL, 헤더, 응답 상태 코드, 지연 시간과 같은 각 요청 및 응답에 관한 세부정보가 포함됩니다.

자동 확장 구성

자동 확장은 부하 변동에 따라 리소스 할당을 조정하여 수요에 따라 포드를 동적으로 추가하거나 삭제하여 성능과 리소스 효율성을 유지합니다. GKE 추론 게이트웨이의 경우 각 InferencePool에서 포드의 수평 자동 확장이 필요합니다. GKE 수평형 포드 자동 확장 처리 (HPA)는 KVCache Utilization와 같은 모델 서버 측정항목을 기반으로 포드를 자동 확장합니다. 이렇게 하면 추론 서비스가 리소스 사용을 효율적으로 관리하면서 다양한 워크로드와 쿼리 볼륨을 처리할 수 있습니다.

GKE 추론 게이트웨이에서 생성된 측정항목을 기반으로 자동 확장되도록 InferencePool 인스턴스를 구성하려면 다음 단계를 따르세요.

  1. 클러스터에 PodMonitoring 객체를 배포하여 GKE 추론 게이트웨이에서 생성된 측정항목을 수집합니다. 자세한 내용은 관측 가능성 구성을 참고하세요.

  2. 커스텀 측정항목 Stackdriver 어댑터를 배포하여 HPA에 측정항목에 대한 액세스 권한을 부여합니다.

    1. 다음 샘플 매니페스트를 adapter_new_resource_model.yaml로 저장합니다.

      apiVersion: v1
      kind: Namespace
      metadata:
        name: custom-metrics
      ---
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: custom-metrics-stackdriver-adapter
        namespace: custom-metrics
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: custom-metrics:system:auth-delegator
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: system:auth-delegator
      subjects:
      - kind: ServiceAccount
        name: custom-metrics-stackdriver-adapter
        namespace: custom-metrics
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: custom-metrics-auth-reader
        namespace: kube-system
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: Role
        name: extension-apiserver-authentication-reader
      subjects:
      - kind: ServiceAccount
        name: custom-metrics-stackdriver-adapter
        namespace: custom-metrics
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole
      metadata:
        name: custom-metrics-resource-reader
        namespace: custom-metrics
      rules:
      - apiGroups:
        - ""
        resources:
        - pods
        - nodes
        - nodes/stats
        verbs:
        - get
        - list
        - watch
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: custom-metrics-resource-reader
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: custom-metrics-resource-reader
      subjects:
      - kind: ServiceAccount
        name: custom-metrics-stackdriver-adapter
        namespace: custom-metrics
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        run: custom-metrics-stackdriver-adapter
        k8s-app: custom-metrics-stackdriver-adapter
      spec:
        replicas: 1
        selector:
          matchLabels:
            run: custom-metrics-stackdriver-adapter
            k8s-app: custom-metrics-stackdriver-adapter
        template:
          metadata:
            labels:
              run: custom-metrics-stackdriver-adapter
              k8s-app: custom-metrics-stackdriver-adapter
              kubernetes.io/cluster-service: "true"
          spec:
            serviceAccountName: custom-metrics-stackdriver-adapter
            containers:
            - image: gcr.io/gke-release/custom-metrics-stackdriver-adapter:v0.15.2-gke.1
              imagePullPolicy: Always
              name: pod-custom-metrics-stackdriver-adapter
              command:
              - /adapter
              - --use-new-resource-model=true
              - --fallback-for-container-metrics=true
              resources:
                limits:
                  cpu: 250m
                  memory: 200Mi
                requests:
                  cpu: 250m
                  memory: 200Mi
      ---
      apiVersion: v1
      kind: Service
      metadata:
        labels:
          run: custom-metrics-stackdriver-adapter
          k8s-app: custom-metrics-stackdriver-adapter
          kubernetes.io/cluster-service: 'true'
          kubernetes.io/name: Adapter
        name: custom-metrics-stackdriver-adapter
        namespace: custom-metrics
      spec:
        ports:
        - port: 443
          protocol: TCP
          targetPort: 443
        selector:
          run: custom-metrics-stackdriver-adapter
          k8s-app: custom-metrics-stackdriver-adapter
        type: ClusterIP
      ---
      apiVersion: apiregistration.k8s.io/v1
      kind: APIService
      metadata:
        name: v1beta1.custom.metrics.k8s.io
      spec:
        insecureSkipTLSVerify: true
        group: custom.metrics.k8s.io
        groupPriorityMinimum: 100
        versionPriority: 100
        service:
          name: custom-metrics-stackdriver-adapter
          namespace: custom-metrics
        version: v1beta1
      ---
      apiVersion: apiregistration.k8s.io/v1
      kind: APIService
      metadata:
        name: v1beta2.custom.metrics.k8s.io
      spec:
        insecureSkipTLSVerify: true
        group: custom.metrics.k8s.io
        groupPriorityMinimum: 100
        versionPriority: 200
        service:
          name: custom-metrics-stackdriver-adapter
          namespace: custom-metrics
        version: v1beta2
      ---
      apiVersion: apiregistration.k8s.io/v1
      kind: APIService
      metadata:
        name: v1beta1.external.metrics.k8s.io
      spec:
        insecureSkipTLSVerify: true
        group: external.metrics.k8s.io
        groupPriorityMinimum: 100
        versionPriority: 100
        service:
          name: custom-metrics-stackdriver-adapter
          namespace: custom-metrics
        version: v1beta1
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole
      metadata:
        name: external-metrics-reader
      rules:
      - apiGroups:
        - "external.metrics.k8s.io"
        resources:
        - "*"
        verbs:
        - list
        - get
        - watch
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: external-metrics-reader
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: external-metrics-reader
      subjects:
      - kind: ServiceAccount
        name: horizontal-pod-autoscaler
        namespace: kube-system
      
    2. 샘플 매니페스트를 클러스터에 적용합니다.

      kubectl apply -f adapter_new_resource_model.yaml
      
  3. 프로젝트에서 측정항목을 읽을 수 있는 어댑터 권한을 부여하려면 다음 명령어를 실행합니다.

    $ PROJECT_ID=PROJECT_ID
    $ PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format="value(projectNumber)")
    $ gcloud projects add-iam-policy-binding projects/PROJECT_ID \
      --role roles/monitoring.viewer \
      --member=principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/$PROJECT_ID.svc.id.goog/subject/ns/custom-metrics/sa/custom-metrics-stackdriver-adapter
    

    PROJECT_ID를 Google Cloud 프로젝트 ID로 바꿉니다.

  4. InferencePool에 다음과 유사한 HPA를 하나 배포합니다.

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: INFERENCE_POOL_NAME
      namespace: INFERENCE_POOL_NAMESPACE
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: INFERENCE_POOL_NAME
      minReplicas: MIN_REPLICAS
      maxReplicas: MAX_REPLICAS
      metrics:
      - type: External
        external:
          metric:
            name: prometheus.googleapis.com|inference_pool_average_kv_cache_utilization|gauge
            selector:
              matchLabels:
                metric.labels.name: INFERENCE_POOL_NAME
                resource.labels.cluster: CLUSTER_NAME
                resource.labels.namespace: INFERENCE_POOL_NAMESPACE
          target:
            type: AverageValue
            averageValue: TARGET_VALUE
    

    다음을 바꿉니다.

    • INFERENCE_POOL_NAME: InferencePool 이름입니다.
    • INFERENCE_POOL_NAMESPACE: InferencePool의 네임스페이스입니다.
    • CLUSTER_NAME: 클러스터의 이름입니다.
    • MIN_REPLICAS: InferencePool의 최소 가용성 (기준 용량)입니다. HPA는 사용량이 HPA 타겟 임곗값 미만일 때 이 복제본 수를 유지합니다. 고가용성 워크로드는 포드 중단 중에 지속적인 가용성을 보장하기 위해 이 값을 1보다 큰 값으로 설정해야 합니다.
    • MAX_REPLICAS: InferencePool에 호스팅된 워크로드에 할당해야 하는 가속기 수를 제한하는 값입니다. HPA는 이 값을 초과하여 복제본 수를 늘리지 않습니다. 트래픽이 최대인 시간에는 복제본 수를 모니터링하여 MAX_REPLICAS 필드의 값이 워크로드가 선택한 워크로드 성능 특성을 유지하도록 확장할 수 있을 만큼 충분한 여유를 제공하는지 확인합니다.
    • TARGET_VALUE: 모델 서버별로 선택된 타겟 KV-Cache Utilization을 나타내는 값입니다. 이 값은 0~100 사이의 숫자이며 모델 서버, 모델, 가속기, 수신 트래픽 특성에 따라 크게 달라집니다. 로드 테스트를 통해 처리량과 지연 시간 그래프를 표시하여 이 타겟 값을 실험적으로 결정할 수 있습니다. 그래프에서 선택한 처리량 및 지연 시간 조합을 선택하고 해당 KV-Cache Utilization 값을 HPA 타겟으로 사용합니다. 선택한 가격 대비 성능 결과를 얻으려면 이 값을 미세 조정하고 면밀히 모니터링해야 합니다. GKE 추론 추천을 사용하여 이 값을 자동으로 결정할 수 있습니다.

다음 단계