실시간 추론을 위한 모델 배포(REST API)

참고

snowflake-ml-python 버전 1.25.0부터 일반 공급으로 제공됩니다.

대기 시간이 짧아야 하는 대화형 워크플로에는 실시간 추론을 사용합니다. 모델을 :doc:`Snowflake 모델 레지스트리 </developer-guide/snowflake-ml/model-registry/overview>`에서 전용 HTTP 엔드포인트를 사용하는 관리형 서비스로 배포할 수 있습니다. 관리형 서비스는 크기를 자동으로 조정하며 Snowflake 에코시스템 내에 완전히 통합되어 포괄적인 가시성을 제공합니다.

다음과 같은 경우 워크플로에 온라인 추론을 사용합니다.

  • 애플리케이션에서 즉각적으로 응답하기 위해 대기 시간이 짧아야 합니다.

  • 모델이 사용자에게 표시되는 웹 또는 모바일 애플리케이션의 백엔드 역할을 합니다.

  • 모델에 대한 입력이 요청의 HTTP 페이로드 내에 적합할 수 있습니다.

  • 서비스가 변동하는 요청 볼륨을 처리하기 위해 자동으로 수평 확장되어야 합니다.

작동 방법

Snowflake는 모델을 Snowpark Container Services(SPCS) 내의 HTTP 서버로 호스팅하여 배포 파이프라인을 간소화합니다. 이 아키텍처를 통해 다음을 수행할 수 있습니다.

  • 추상 복잡성: Docker 이미지 또는 Kubernetes 클러스터를 관리하지 않고도 정교한 모델을 배포할 수 있습니다.

  • 크기 조정 성능: 고성능 요구 사항을 충족하기 위해 분산 GPU 클러스터에서 대규모 모델을 실행합니다.

  • 신뢰성 보장: 원활한 모델 업그레이드를 위해 기본 제공되는 가시성, 트래픽 분할, 섀도우/카나리 배포를 활용합니다.

전제 조건

시작하기 전에 다음이 있는지 확인하십시오.

  • 상업용 AWS, Azure 또는 Google Cloud 리전의 Snowflake 계정입니다. 정부 리전은 지원되지 않습니다.

  • snowflake-ml-python Python 패키지 버전 1.8.0 이상입니다.

  • :doc:`Snowflake 모델 레지스트리 </developer-guide/snowflake-ml/model-registry/overview>`에 로그인한 모델입니다.

  • 컴퓨팅 풀 및 SPCS에 대한 관련 권한에 대해 이해하고 있습니다.

필수 권한

모델 서비스는 Snowpark Container Services 에서 실행됩니다. Model Serving을 사용하려면 다음 권한이 필요합니다.

  • 서비스가 실행되는 컴퓨팅 풀에 대한 USAGE 또는 OWNERSHIP 권한. 또는 기본 시스템 컴퓨팅 풀을 사용할 수 있습니다.

  • 공용 엔드포인트를 생성할 수 있는 계정에 대한 BIND SERVICE ENDPOINT 권한.

  • 모델에 대한 OWNER 또는 READ 권한

제한 사항

Snowpark Container Services에서 제공하는 온라인 모델에는 다음과 같은 제한이 적용됩니다.

  • 테이블 함수는 지원되지 않습니다. 모델에 Snowflake에 배포할 테이블 함수가 있어야 합니다.

  • Snowpark ML 모델링 클래스를 사용하여 개발된 모델은 GPU가 있는 환경에는 배포할 수 없습니다. 해결 방법으로 네이티브 모델을 추출하여 배포할 수 있습니다. 자세한 내용은 온라인 추론을 위한 모델 배포 섹션을 참조하십시오.

온라인 추론을 위한 모델 배포

Snowflake ML은 모델 버전 오브젝트를 사용하여 추론 요청을 처리하는 모델 서비스를 생성합니다. 모델 버전 오브젝트를 생성하려면 새 모델 버전을 기록하거나 기존 모델 버전에 대한 참조를 획득하면 됩니다. 모델 버전 오브젝트를 가져온 후 다음 Python 코드를 사용하여 모델 서비스를 생성하고 해당 서비스를 SPCS에 배포할 수 있습니다.

# reg is a snowflake.ml.registry.Registry object
example_mv_object = reg.get_model("mymodel_name").version("version_name") # a snowflake.ml.model.ModelVersion object

example_mv_object.create_service(service_name="myservice",
                  service_compute_pool="my_compute_pool",
                  ingress_enabled=True,
                  gpu_requests=None)
Copy

:code:`create_service`에는 다음 인자가 필요합니다.

  • service_name: 생성 중인 서비스의 이름입니다. 이 이름은 Snowflake 계정 내에서 고유해야 합니다.

  • service_compute_pool: 모델을 실행하는 데 사용하는 컴퓨팅 풀의 이름입니다. 컴퓨팅 풀은 기존에 있어야 합니다. 모델이 시스템 컴퓨팅 풀에 적합한 경우 SYSTEM_COMPUTE_POOL_GPU 또는 :code:`SYSTEM_COMPUTE_POOL_CPU`도 사용할 수 있습니다.

  • ingress_enabled: Snowflake 외부에서 온라인 추론을 호출하려면 이 값이 True여야 합니다.

  • gpu_requests: GPUs의 개수를 지정하는 문자열입니다. CPU 또는 여러 GPUs에서 실행할 수 있는 모델의 경우 이 인자에 따라 모델이 CPU에서 또는 GPUs에서 실행될지가 결정됩니다. 모델이 CPU에서만 실행할 수 있는 알려진 유형(예: scikit-learn 모델)인 경우, GPUs가 요청되면 이미지 빌드가 실패합니다. 새 모델을 배포하는 경우 CPU 기반 모델의 서비스를 생성하는 데 최대 10분, GPU 기반 모델의 서비스를 생성하는 데 20분이 소요될 수 있습니다. 컴퓨팅 풀이 유휴 상태이거나 크기를 조정해야 하는 경우 서비스를 생성하는 데 시간이 더 오래 걸릴 수 있습니다.

이전 예제에서는 필수 인자와 가장 일반적으로 사용되는 인자만 보여줍니다. 전체 인자 목록은 ModelVersion API 참조 섹션을 참조하세요.

기본 서비스 구성

배포한 모델을 실행하는 서버는 대부분의 사용 사례에 적합한 기본값을 사용합니다.

  • 워커 스레드 수: CPU 기반 모델의 경우 서버에서 사용하는 프로세스의 수는 CPUs 수의 2배에 하나를 더한 수입니다. GPU 기반 모델은 하나의 워커 프로세스를 사용합니다. create_service 호출에서 num_workers 인자를 사용하여 이를 재정의할 수 있습니다. 모델이 메모리에 들어갈 수 있는 가장 작은 GPU 노드를 지정하는 것을 **권장**합니다. 인스턴스 수를 늘려 확장합니다. 예를 들어, 모델이 GPU_NV_S(Azure에서는 GPU_NV_SM) 인스턴스 유형에 맞는 경우 gpu_requests=1을 사용하고 max_instances를 늘려 확장합니다. 그러나 사용 가능한 가장 작은 노드에 4개의 GPUs가 있는데 2개만 필요한 경우 :code:`num_workers=2`(즉, 사용 가능한 gpu/모델에 필요한 gpu)를 사용합니다.

  • 스레드 안전: 일부 모델은 스레드에 안전하지 않습니다. 따라서 서비스는 각 워커 프로세스에 대해 별도의 모델 복사본을 로드합니다. 이로 인해 대규모 모델의 경우 리소스 고갈이 발생할 수 있습니다.

  • 노드 사용률: 기본적으로 하나의 추론 서버 인스턴스는 실행 중인 노드의 모든 CPU 및 메모리를 요청하여 전체 노드에 요청합니다. 인스턴스당 리소스 할당을 사용자 지정하려면 cpu_requests, memory_requests, gpu_requests와 같은 인자를 사용합니다.

  • 엔드포인트: 추론 엔드포인트의 이름은 추론이며, 포트 5000을 사용합니다. 이들은 사용자 지정할 수 없습니다. 최적의 리소스 활용을 위해 모델을 메모리에 맞출 수 있는 가장 작은 GPU 노드를 지정합니다. 워크로드에 맞게 확장할 인스턴스 수를 늘립니다. 예를 들어, 모델이 GPU_NV_S(Azure에서는 GPU_NV_SM) 인스턴스 유형에 맞는 경우 gpu_requests=1을 사용하고 max_instances를 늘려 확장합니다.

컨테이너 이미지 빌드 동작

Snowflake conda 채널은 웨어하우스에서만 사용할 수 있으며 웨어하우스 종속성에 대한 유일한 소스입니다. 기본적으로 SPCS 모델에 대한 conda 종속성은 conda-forge에서 종속성을 가져옵니다.

기본적으로 Snowflake Model Serving은 모델을 실행하는 데 사용되는 것과 동일한 컴퓨팅 풀을 사용하여 컨테이너 이미지를 빌드합니다. 컴퓨팅 풀로 인해 이미지 빌드 프로세스에 대해 과부하가 발생할 수 있습니다(예: 컨테이너 이미지 빌드에 GPUs가 사용되지 않음). 대부분의 경우 이는 컴퓨팅 비용에 큰 영향을 미치지 않습니다. 그러나 우려된다면 image_build_compute_pool 인자로 이미지 빌드에 덜 강력한 컴퓨팅 풀을 지정할 수 있습니다.

create_service()를 여러 번 호출해도 호출할 때마다 빌드가 트리거되는 것은 아닙니다.

그러나 Snowflake가 종속 패키지의 취약점에 대한 수정 사항을 포함하여 추론 서비스를 업데이트한 경우 컨테이너 이미지가 다시 빌드될 수 있습니다. 이러한 상황이 발생하면 create_service가 이미지의 다시 빌드를 자동으로 트리거합니다.

사용자 인터페이스

배포된 모델은 Model Registry Snowsight UI 에서 관리할 수 있습니다. 자세한 내용은 모델 추론 서비스 섹션을 참조하십시오.

배포된 모델 호출하기

HTTP 엔드포인트

모든 서비스에는 내부 DNS 이름이 있습니다. ingress_enabled로 서비스를 배포하면 Snowflake 외부에서 사용할 수 있는 공용 HTTP 엔드포인트도 생성됩니다. 두 엔드포인트 중 하나를 사용하여 서비스를 호출할 수 있습니다.

SHOW ENDPOINTS 명령을 사용하여 수신이 활성화된 서비스의 공용 HTTP 엔드포인트를 찾을 수 있습니다. 출력에는 unique-service-id-account-id.snowflakecomputing.app 형식의 항목이 있는 ingress_url 열이 포함됩니다. 이는 서비스에 대해 공개적으로 사용 가능한 HTTP 엔드포인트입니다. 비공개 링크 사용자의 경우 ingress_url 대신 privatelink_ingress_url을 사용합니다.

Snowflake에서 내부 DNS 이름을 사용하려면 DESCRIBE SERVICE 명령을 사용합니다. 이 명령 출력의 dns_name 열에는 서비스의 내부 DNS 이름이 포함됩니다. 서비스의 포트를 찾으려면 SHOW ENDPOINTS IN SERVICE 명령을 사용합니다. 포트 또는 port_range 열에는 서비스에서 사용하는 포트가 포함됩니다. URL http://dns_name:*port*를 통해 서비스에 내부 호출을 수행할 수 있습니다.

모델의 특정 메서드를 호출하려면 메서드 이름을 URL의 경로로 사용합니다(예: https://unique-service-id-account-id.snowflakecomputing.app/method-name 또는 http://dns_name:port/<method-name>). URL에서 메서드 이름의 밑줄(_)은 URL에서 대시(-)로 대체됩니다. 예를 들어, 서비스 이름 predict_prob는 URL에서 predict-proba로 변경됩니다.

작업을 단순화하기 위해 Python에서 ModelVersion 오브젝트에 대해 list_services() API를 호출할 수 있습니다.

# mv: snowflake.ml.model.ModelVersion
mv.list_services()
Copy

공용 엔드포인트(inference_endpoint) 및 내부 엔드포인트(internal_endpoint)를 모두 출력합니다.

인증

Snowflake는 여러 인증 프로토콜을 지원합니다. 가장 간단한 방법은 :doc:`프로그래밍 방식 액세스 토큰(PAT) </user-guide/programmatic-access-tokens>`을 사용하는 것이며, 여기서 토큰은 :samp:`Authorization: Snowflake Token=”{your_pat_token}”`으로 요청 헤더에 간단히 전달할 수 있습니다.

참고

잘못된 토큰 또는 서비스에 대한 네트워크 경로 부족과 같은 모든 인증 실패에는 404 오류가 발생합니다. 현재, 인증 오류와 유효하지 않은 URLs를 구분할 방법이 없습니다.

인증

기본적으로 서비스 소유자만 엔드포인트를 사용할 수 있습니다. 다른 역할이 엔드포인트에 액세스할 수 있도록 허용하려면 서비스 소유자가 :doc:`서비스 역할을 부여 </sql-reference/sql/grant-service-role>`ALL_ENDPOINTS_USAGE해야 합니다.

요청 본문(또는 프로토콜 또는 데이터 형식)

Snowflake는 REST 요청에 대해 두 가지 유형의 데이터 형식을 지원합니다. 특히 Pandas Dataframe에서 영감을 받았는데, 이는 업계에서 잘 알려져 있고 고객이 Pandas Dataframe을 사용한 간단한 Python 스크립트로 검증할 수 있기 때문입니다.

메서드- URL 매핑: 요청 URL을 구성할 때, 모델의 메서드 이름에 있는 밑줄(_)은 자동으로 대시(-)로 대체됩니다. 예를 들어 모델 메서드가 predict_proba`인 경우 엔드포인트 URL 경로는 :code:/predict-proba`가 됩니다.

해당 형식에 대한 세부 정보는 다음과 같습니다.

  1. :code:`dataframe_split`은 압축된 인덱스/열/데이터 표현입니다.

  • :code:`pandas_df.to_json(orient=”split”)`을 미러링하는 표현입니다.

  1. :code:`dataframe_records`는 키/값(레코드 지향) 표현입니다.

  • :code:`df.to_json(orient=”records”)`을 미러링하는 표현입니다.

dataframe_split 형식을 사용하는 것을 **권장**합니다. :code:`dataframe_records`는 각 행에 대해 열 이름을 반복하기 때문에 일반적으로 :code:`dataframe_split`보다 큰 요청 본문을 생성합니다. 이는 대규모 배치 또는 빈번한 호출의 경우 성능에 영향을 줄 수 있습니다.

모델 엔드포인트는 사용하는 입력 형식에 관계없이 **단일 출력 형식**을 계속 반환합니다.

  1. dataframe_split 형식(권장)

이는 Pandas “분할” 방향에 의해 생성된 구조와 일치합니다. 요청 본문은 dataframe_split 키 아래에 다음 구조를 래핑합니다.

  • index: 행 인덱스의 목록입니다.

  • columns: 열 이름의 목록입니다.

  • data: 행 목록으로, 각 행은 열에 맞춰 정렬된 값 목록입니다.

cURL 요청 예제:

curl -X POST "<endpoint_url>" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -w "\n\n=== RESULT ===\nHTTP Status: %{http_code}\nTotal Time: %{time_total}s\nConnect Time: %{time_connect}s\nServer Processing: %{time_starttransfer}s\nResponse Size: %{size_download} bytes\nRequest Size: %{size_upload} bytes\n" \
 -d '{
       "dataframe_split": {
         "index": [0, 1],
         "columns": ["customer_id", "age", "monthly_spend"],
         "data": [
            [101, 32, 85.5],
            [102, 45, 120.0],
         ]
       }
     }'
Copy
  1. dataframe_records 형식

:code:`dataframe_records`는 Pandas 레코드 방향 에 의해 생성된 구조와 일치합니다.

  • 각 레코드가 **열 이름**을 **값**에 매핑하는 사전인 **레코드 목록**입니다.

요청 본문은 dataframe_records 키 아래에 이 목록을 래핑합니다.

cURL 요청 예제:

curl -X POST "<endpoint_url>" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -w "\n\n=== RESULT ===\nHTTP Status: %{http_code}\nTotal Time: %{time_total}s\nConnect Time: %{time_connect}s\nServer Processing: %{time_starttransfer}s\nResponse Size: %{size_download} bytes\nRequest Size: %{size_upload} bytes\n" \
 -d '{
       "dataframe_records": [
          {
            "customer_id": 101,
            "age": 32,
            "monthly_spend": 85.5,
          },
          {
            "customer_id": 102,
            "age": 45,
            "monthly_spend": 120.0,
          },
        ]
     }'
Copy

매개 변수 전달하기

모델의 서명에 ParamSpec </developer-guide/snowflake-ml/model-registry/model-signature>`으로 정의된 매개 변수가 포함된 경우 :code:`dataframe_split 또는 dataframe_records`와 함께 JSON 요청 본문에 최상위 :code:`params 키를 포함하여 매개 변수 값을 전달할 수 있습니다. 재정의하려는 매개 변수만 포함합니다. 지정되지 않은 매개 변수는 서명의 기본값을 사용합니다.

매개 변수가 있는 cURL 요청 예제:

curl -X POST "<endpoint_url>/predict" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -d '{
        "dataframe_split": {
            "index": [0],
            "columns": ["input_text"],
            "data": [["Hello, world!"]]
        },
        "params": {"temperature": 0.9, "max_tokens": 512}
      }'
Copy

params 키는 dataframe_records 형식과 동일한 방식으로 작동합니다.

curl -X POST "<endpoint_url>/predict" \
  -H 'Authorization: Snowflake Token="<pat_token>"' \
  -H 'Content-Type: application/json' \
  -d '{
        "dataframe_records": [
            {"input_text": "Hello, world!"}
        ],
        "params": {"temperature": 0.9, "max_tokens": 512}
      }'
Copy

Python 예제

  1. dataframe_split 형식

Snowflake는 요청을 보내기 전에 **Pandas JSON 직렬화**를 사용하여 페이로드를 생성한 다음 :code:`json.loads`로 역직렬화는 것을 권장합니다. 이를 통해 데이터 타입이 일관되게 처리됩니다.

import json
import pandas as pd
import requests

# Example DataFrame
df = pd.DataFrame(
    {
        "customer_id": [101, 102],
        "age": [32, 45],
        "monthly_spend": [85.5, 120.0],
    }
)

ENDPOINT_URL = "<your endpoint URL>"
HEADERS = {
    "Authorization": f'Snowflake Token="{PAT}"',
    "Content-Type": "application/json"
}

# Use Pandas to generate the JSON, then load it back to a Python dict
split_obj = json.loads(df.to_json(orient="split"))

payload = {
    "dataframe_split": split_obj
}

response = requests.post(
    ENDPOINT_URL,
    headers=HEADERS,
    json=payload,
    timeout=30,
)

result = response.json()
Copy

요점:

  • pd.Dataframe.to_json(예: df.to_json(orient="split"))을 사용하여 네이티브 json 직렬 변환기에 익숙하지 않은 타임스탬프, 부동 소수점, null, 카테고리 등의 유형을 올바르게 처리할 수 있습니다.

  • :code:`json.loads(…)`는 JSON 문자열을 Python 사전으로 변환하여 페이로드를 올바르게 구성할 수 있도록 합니다.

  • :code:`requests.post(…, json=payload)`는 HTTP 요청을 위해 사전을 다시 JSON으로 직렬화합니다.

매개 변수를 포함하려면 params 키를 페이로드 사전에 추가합니다.

payload = {
    "dataframe_split": split_obj,
    "params": {"temperature": 0.9, "max_tokens": 512}
}
Copy
  1. dataframe_records 형식

:code:`dataframe_split`과 마찬가지로, Pandas JSON 직렬화 및 :code:`json.loads`를 사용합니다.

import json
import pandas as pd
import requests

df = pd.DataFrame(
    {
        "customer_id": [101, 102],
        "age": [32, 45],
        "monthly_spend": [85.5, 120.0],
    }
)

ENDPOINT_URL = "<your endpoint invoke URL>"
HEADERS = {
    "Authorization": "Bearer <your token>",
    "Content-Type": "application/json",
}

records_obj = json.loads(df.to_json(orient="records"))

payload = {
    "dataframe_records": records_obj
}

response = requests.post(
    ENDPOINT_URL,
    headers=HEADERS,
    json=payload,
    timeout=30,
)

response.raise_for_status()
result = response.json()
Copy

다음 단계

추론 서비스를 최적화하고 관리하기 위한 자세한 가이드를 살펴보세요.

  • 워크플로 예제: XGBoost(CPU), Hugging Face(GPU), PyTorch 모델에 대한 엔드투엔드 코드를 참조하세요.

  • 서비스 관리 및 크기 조정: 자동 크기 조정, 수동 일시 중단, 하드웨어 구성에 대해 알아보세요.

  • 안정적인 엔드포인트 및 API 참조: Snowflake 게이트웨이, 인증 및 데이터 프로토콜(dataframe_split)에 대해 심층 분석합니다.

  • 자동 캡처 추론 로그: 모델 모니터링을 위한 자동화된 로깅을 설정합니다.

  • 문제 해결: 패키지 충돌, OOM 오류 및 빌드 실패에 대한 일반적인 수정 사항입니다.