Azure PTU 플랫 비용 귀속

Azure PTU 플랫 비용 귀속 (Flat Cost Attribution)

Azure의 프로비저닝 처리량(provisioned throughput)은 예약된 용량에 대해 시간 단위로 청구되지, 토큰 단위로 청구되지 않아요. 자체 PTU 배포를 운영하는 팀은 요청을 하나 보내든 백만 개를 보내든 시간당 요금을 내죠. 그래서 LiteLLM의 토큰당 비용 추적만으로는 인보이스와 일치하는 결과를 얻을 수 없어요. PTU 플랫 비용 귀속은 이 불일치를 해결해 줘요. 배포가 예약하는 용량과 시간당 비용을 LiteLLM에 알려주면, 일일 작업이 그 비용을 소유 팀에게 귀속시켜 줘요.

PTU 배포는 예약된 용량 자체로만 청구돼요. LiteLLM은 여기에 토큰당 가격을 저장하지 않으므로, 그 용량이 처리하는 트래픽에 플랫 비용이 추가로 청구되지 않아요.

활성화하기

이 기능은 기본적으로 꺼져 있고, 옵트인하기 전까지는 동작하지 않아요.

    export LITELLM_ENABLE_PTU_COST_ATTRIBUTION=True  
    

변수를 설정하지 않으면 일일 작업이 예약되지 않고, 모델 엔드포인트가 PTU 구성을 거부하며, 사용량 조회 경로가 플랫 비용을 0으로 보고하고, PTU 입력란은 모델 폼에 숨겨진 채로 남아요.

배포 구성하기

PTU 구성은 배포의 model_info에 들어 있어요. Admin UI에서 Models + Endpoints를 열고 배포를 선택한 뒤 설정을 편집하면 돼요. PTU 입력란은 LiteLLM이 0으로 유지하는 토큰당 비용 바로 아래에 있어요.

POST /model/new로도 같은 작업을 할 수 있어요:

    curl -X POST http://localhost:4000/model/new \  
      -H "Authorization: Bearer $LITEL..._KEY" \  
      -H 'Content-Type: application/json' \  
      -d '{  
        "model_name": "gpt-4o-ptu",  
        "litellm_params": {  
          "model": "azure/<your-deployment-name>",  
          "api_key": "os.environ/AZURE_API_KEY",  
          "api_base": "os.environ/AZURE_API_BASE"  
        },  
        "model_info": {  
          "team_id": "<the owning team id>",  
          "ptu_count": 100,  
          "cost_per_ptu_per_hour": 0.02,  
          "ptu_effective_from": "2026-01-01T00:00:00Z"  
        }  
      }'  
    

config.yaml에서도 가능해요:

    model_list:  
      - model_name: gpt-4o-ptu  
        litellm_params:  
          model: azure/<your-deployment-name>  
          api_key: os.environ/AZURE_API_KEY  
          api_base: os.environ/AZURE_API_BASE  
        model_info:  
          id: gpt-4o-ptu-team-a  
          team_id: <the owning team id>  
          ptu_count: 100  
          cost_per_ptu_per_hour: 0.02  
          ptu_effective_from: "2026-01-01T00:00:00Z"  
    

이렇게 선언한 배포는 model_info.id가 필수예요. 없으면 프록시가 로드하지 않고 시작 로그에서 해당 배포 이름을 짚어줘요. id를 두지 않으면 모델 이름과 해석된 litellm_params에서 id가 파생되는데, 이때 자격증명을 교체하면 새 정체성이 생겨서 예약이 그 아래에서 두 번 청구되고, 나중에 아무도 되돌려주지 않아요. 안정적인 문자열이면 무엇이든 되지만, 배포 전체에서 유일해야 해요.

이미 비용이 쌓인 기존 예약을 업그레이드할 때는 새 이름 대신 지금 쓰고 있는 id로 id를 설정해야 해요. 그래야 이미 기록된 청구가 옛 정체성에 남고 새 정체성이 그 옆에서 새로 시작돼요. 시작 시 거부 메시지는 현재 id를 인용하므로 그대로 복사하면 돼요.

team_id는 용량이 청구되는 대상이라, 없이 선언하면 아무것도 쌓이지 않아요.

필드 필수 의미
id config.yaml에서 배포의 안정적인 정체성. API나 UI에서는 자동으로 저장되므로 필요 없음
team_id 용량이 속한 팀. 배포 하나는 팀 하나에 매핑됨
ptu_count 예약된 프로비저닝 처리량 단위
cost_per_ptu_per_hour 계약한 시간당 단위 요금
ptu_effective_from 예약이 비용을 쌓기 시작하는 시점
ptu_effective_to 아니요 종료 시점. 열린 예약이면 비워둠

ptu_countcost_per_ptu_per_hour는 반드시 함께 설정해야 하고, ptu_effective_from은 플랫 비용이 그 순간부터 쌓이기 때문에 필수예요. 없으면 오늘 구성한 배포가 존재하지 않던 기간에 대해 청구될 거예요.

cost_per_ptu_per_hour는 목록 가격이 아니라 Azure 계약에서 가져와야 해요. PTU 요금은 협상된 값이고 리전과 약정 기간에 따라 달라지거든요.

비용 계산 방식

00:15 UTC에 작업이 실행되어 팀·모델별로 전날 하루치 행 하나를 써요:

    flat cost = ptu_count x cost_per_ptu_per_hour x hours active that day  
    

활성 시간은 그날과 예약 창의 겹침이에요. 정오에 시작한 예약은 첫날 12시간, 이후에는 24시간이 쌓여요. 행들은 예약 키인 __ptu_flat_cost__ 아래 LiteLLM_DailyTeamSpend에 기록되는데, 이를 통해 플랫 비용은 실제 API 키에 기록되는 요청별 지출과 분리돼요.

작업이 예약을 처음 보기 전에 시작된 예약도 채워져요. 캐치업 패스가 각 경과 일을 ptu_effective_from까지 소급해 최대 91일을 가격 산정해요. 그래서 오늘 소급 시작일로 구성한 배포는 첫 실행에 전체 창을 쌓아요.

플랫 비용은 팀이나 키 예산에 집계되지 않아요. 예약 용량은 이미 지불된 것이므로, 팀이 예약한 용량을 사용해 예산을 소진할 수 없어요.

넘침(spillover) 요청

PTU 배포가 가득 차면 Azure가 초과분을 넘침 대상으로 구성한 종량제(pay-as-you-go) 배포로 보내고, 그 요청들을 토큰 단위로 청구할 수 있어요. LiteLLM도 똑같이 동작해요. x-ms-is-spilled-over: true 응답은 서빙된 모델의 표준 요금으로 가격이 매겨지고, PTU가 처리한 요청은 0으로 남아요. 시간당 플랫 비용은 어느 쪽이든 변하지 않아요.

넘침 요청은 지출 로그 메타데이터에 태그되므로 Logs 페이지에서 구분할 수 있어요:

    "azure_spillover": {"from_deployment": "<your-deployment-name>"}  
    

이 기능은 LITELLM_ENABLE_PTU_COST_ATTRIBUTION이 설정돼 있고 서빙 모델의 가격 맵 항목이 있어야 해요(배포 이름이 모델과 일치하지 않으면 base_model을 설정하세요). 둘 중 하나라도 없으면 넘침 요청도 0으로 기록돼요.

비용 읽어오기

/team/daily/activity는 일별 flat_cost와 기간의 total_flat_cost를 평소의 토큰당 spend와 함께 보고해요:

    curl -s "http://localhost:4000/team/daily/activity?team_ids=<team-id>&start_date=2026-01-01&end_date=2026-01-31" \  
      -H "Authorization: Bearer $LITEL..._KEY" | jq '{  
        total_spend: .metadata.total_spend,  
        total_flat_cost: .metadata.total_flat_cost  
      }'  
    

Admin UI의 Usage 페이지에서도 Team Usage 아래 같은 수치를 보여줘요. 플랫 비용을 요청 비용과 분리해 차트로 그리고, CSV 내보내기에도 포함돼요.

설정하면 안 되는 요금

LiteLLM은 PTU 배포에 토큰당·초당·캐시 요금을 거부하고, 거부한 필드를 이름으로 알려줘요. 0을 보내거나, 전부 0인 표를 보내거나, 아예 값을 보내지 않는 것은 허용돼요:

    A PTU deployment bills by reserved capacity, so input_cost_per_token cannot be charged on top  
    of it. Send 0 or no value, or remove ptu_count and cost_per_ptu_per_hour to bill per token.  
    

PTU 구성을 추가할 때 이미 배포에 저장돼 있던 요금은 거부되지 않고 0으로 초기화돼요. 그래서 기능 활성화 전에 가격이 매겨진 배포는 다음 저장 때 자동으로 고쳐져요. ptu_countcost_per_ptu_per_hour를 제거하면 그 0들이 풀리고, 배포는 다시 토큰 단위로 청구돼요.

웹 검색 요금도 같은 방식으로 처리돼요. 다만 xAI 모델은 가격 판독기가 0 요금을 무시하기 때문에, 호출당 목록 가격을 그대로 청구한다는 점을 유의하세요.

제한 사항

레거시 POST /model/update는 위 규칙들을 실행하지 않아요. 그래서 이 경로로 구성한 PTU 배포는 토큰 단위로 계속 청구되고 플랫 비용이 쌓이지 않아요. POST /model/new, PATCH /model/{model_id}/update, Admin UI, 또는 config.yaml을 사용하세요.

Python Router를 단독으로 쓰면 등록 시 토큰당 가격을 0으로 만들지만, 프록시 밖에서는 일일 작업을 예약하는 것이 없어서 플랫 비용은 쌓이지 않아요.

출처: 문서

더 알아보기 (Learn more)

  • Azure의 프로비저닝 처리량(PTU)과 예약 용량 청구 방식 이해하기
  • LiteLLM_DailyTeamSpend 테이블의 팀별 일일 지출 구조 확인하기