ModelAdapter 실행 인터페이스 참조
ModelAdapter는 게시한 Python 라이브러리에서 모델 산출물과 Foundry를 연결하는 기반 클래스입니다. 저장과 로드, API 및 추론 로직을 제공하여 모델을 초기화하고 실행합니다.
어댑터 메서드
| 메서드 | 입력, 반환값과 호출 시점 |
|---|---|
| load(cls, state_reader, container_context=None, external_model_context=None) | 모델 산출물을 읽고 초기화한 ModelAdapter를 반환하는 클래스 메서드입니다. 컨테이너 및 외부 모델에는 각각 해당 컨텍스트가 전달됩니다. |
| save(self, state_writer) | 학습 또는 재학습한 모델의 가중치와 메타데이터를 ModelStateWriter에 기록합니다. 반환값은 없습니다. |
| api(cls) | 모델 API를 반환합니다. 간편 정의에서는 이름 있는 입력 및 출력 사전을 쌍으로 반환합니다. |
| predict(self, *args, **kwargs) | 입력 하나 이상에서 단일 표 또는 단일 매개변수 출력을 계산하여 반환합니다. 인자 이름은 API 입력 이름과 같아야 하며 순서는 달라도 됩니다. |
| run_inference(self, inputs, outputs) | 여러 출력 또는 파일시스템 출력을 직접 기록합니다. 입력과 출력은 API 이름을 필드로 가진 NamedTuple입니다. 반환값은 없습니다. |
기본 직렬화기를 사용하는 @auto_serialize 어댑터는 save와 load를 별도로 구현하지 않아도 됩니다. predict로 단일 출력을 구현하면 run_inference를 함께 정의하지 않습니다. 일반 모델은 load에서 파일을 읽으며 컨테이너 모델은 시작한 서비스 주소를, 외부 모델은 접속 설정과 클라이언트를 초기화합니다.
ModelStateWriter
ModelAdapter.save에 전달하며 모델 저장소에 기록할 산출물을 제공합니다.
| 인터페이스 | 동작 |
|---|---|
| open(asset_file_path: str, mode="wb") | 파일처럼 쓸 수 있는 IOBase 컨텍스트 관리자를 엽니다. 기본 모드는 바이너리 쓰기입니다. |
| put_file(local_file_path, asset_file_path=None) | 로컬 파일을 모델 저장소에 넣습니다. 대상 경로를 생략하면 저장소 루트에 둡니다. |
| put_directory(local_root_path, asset_root_path="/") | 로컬 디렉터리와 내용을 모델 저장소의 지정한 루트 상대 경로에 넣습니다. |
| mkdir(name) | 디렉터리에 저장하는 직렬화기가 쓸 로컬 디렉터리를 만듭니다. 제공 직렬화기는 반환한 경로에 모델 파일을 기록합니다. |
변환에서 모델을 publish하면 save가 실행됩니다. Writer에 기록한 내용은 ZIP으로 묶여 새 모델 버전이 참조하는 산출물 저장소에 보관됩니다.
ModelStateReader
ModelInput으로 로드하거나 모델 기반 온라인 또는 배치 배포를 시작할 때 load에 전달합니다. save가 기록한 같은 산출물 저장소를 읽으며 변환과 추론 로직보다 먼저 초기화합니다.
| 인터페이스 | 동작 |
|---|---|
| open(asset_file_path: str, mode="rb") | 저장한 파일을 읽는 IOBase 컨텍스트 관리자를 엽니다. 기본 모드는 바이너리 읽기입니다. |
| extract_to_temp_dir(root_dir=None) | 모델 산출물을 임시 디렉터리로 추출합니다. root_dir를 지정하면 해당 루트를 추출합니다. |
| extract(destination_path=None) | 지정한 로컬 디렉터리로 저장소를 추출합니다. |
| dir(name) | 디렉터리 기반 직렬화기가 저장한 경로를 반환합니다. |
컨테이너 모델에도 사용자가 올린 ZIP을 읽는 ModelStateReader가 제공됩니다.
파일 저장과 로드 예제
다음 어댑터는 model.pkl에 모델을 저장하고 같은 파일로 복원합니다. model은 input_feature라는 특성 열을 받아 행별 수치 예측을 반환하는 실제 모델입니다. 해당 클래스와 의존성은 어댑터 환경에 있어야 합니다.
import pickle
import palantir_models as pm
class ExampleModelAdapter(pm.ModelAdapter):
def __init__(self, model):
self.model = model
def save(self, state_writer: pm.ModelStateWriter):
with state_writer.open("model.pkl", "wb") as model_outfile:
pickle.dump(self.model, model_outfile)
@classmethod
def load(cls, state_reader: pm.ModelStateReader):
with state_reader.open("model.pkl", "rb") as model_infile:
return cls(pickle.load(model_infile))
@classmethod
def api(cls):
return (
{"df_in": pm.Pandas([("input_feature", float)])},
{"df_out": pm.Pandas([("prediction", float)])},
)
def predict(self, df_in):
df_in["prediction"] = self.model.predict(df_in[["input_feature"]])
return df_in
컨테이너 초기화 컨텍스트
ContainerizedApplicationContext는 컨테이너 이미지로 실행하는 모델의 load에만 제공됩니다. 어댑터 코드에서 이 타입을 반드시 가져올 필요는 없습니다.
services는 컨테이너 이름에서 서비스 주소 목록으로 가는 맵입니다. 한 컨테이너에 여러 포트를 열 수 있으므로 주소도 여러 개일 수 있습니다. 다음은 설명용 값입니다.
{
"container1": ["localhost:8080"],
"container2": ["localhost:8080", "localhost:8081"],
}
shared_empty_dir_mount_path는 모든 컨테이너와 어댑터가 함께 읽고 쓸 수 있는 빈 디렉터리의 마운트 경로입니다. 실제 컨텍스트는 container_context.services["container1"][0]와 container_context.shared_empty_dir_mount_path처럼 접근합니다. 이를 어댑터 인스턴스에 저장하여 추론 요청을 보낼 호스트와 포트 및 공유 파일 경로로 사용합니다.
외부 모델 초기화 컨텍스트
ExternalModelContext는 외부 호스팅 모델의 load에만 제공됩니다. load의 실제 타입 표기는 models_api.models_api_executable.ExternalModelExecutionContext를 사용할 수 있습니다.
| 필드 | 값과 용도 |
|---|---|
| external_model.base_url | 사용자가 지정한 외부 모델 기본 URL입니다. |
| external_model.connection_configuration | 암호화하지 않은 설정 사전입니다. 모델 이름, 추론 매개변수나 임계값 같은 설정을 둡니다. |
| resolved_credentials | 사용자가 지정한 자격 증명을 복호화한 이름과 문자열 값의 맵입니다. |
예를 들어 어댑터는 connection_configuration의 model_name 및 model_parameter, resolved_credentials의 secret_key를 실제 외부 클라이언트 초기화에 전달할 수 있습니다. 연결 클라이언트의 생성 계약에 맞춰 URL과 설정 및 자격 증명을 전달하고 해당 클라이언트를 추론에서 사용합니다.
모델 API의 저수준 입력 선언
여기의 palantir_models.models.api.ModelInput 및 ModelOutput은 API 자료형 선언입니다. 변환에서 모델 리소스를 가져오는 palantir_models.transforms.ModelInput과 구분합니다. 일반적인 어댑터는 pm.Pandas와 pm.Parameter 등의 간편 선언을 사용합니다.
| 선언 | 인자와 동작 |
|---|---|
| TabularInput(name, df_type=DFType.SPARK, columns) | 표 입력의 이름, 어댑터 내부 DataFrame 종류와 ModelApiColumn 목록입니다. |
| ParameterInput(name, type, default=None) | 상수 매개변수의 이름, 자료형과 생략 시 값입니다. |
| FileSystemInput(name) | 데이터셋 파일시스템 입력 이름입니다. |
| MediaReferenceInput(name) | 미디어 참조 입력 이름입니다. |
| ObjectInput(object_type) | 생성한 Python OSDK 객체 타입입니다. |
| ObjectSetInput(object_type) | 집합에 들어갈 Python OSDK 객체 타입입니다. |
| ModelApiColumn(name: str, type: type, required=True) | 열 이름과 Python 자료형 및 필수 여부입니다. |
| DFType.SPARK / DFType.PANDAS | 각각 wire 값 spark와 pandas입니다. |
표 입력에는 TransformInput, Pandas DataFrame 및 Spark DataFrame을 전달할 수 있습니다. 직접 전달한 Spark DataFrame은 DFType.SPARK 입력에만 지원하며 DFType.PANDAS 입력으로 자동 변환하지 않습니다. TransformInput은 API에 지정한 표 형식으로 읽어 전달합니다.
표 열은 str, int, float, bool, list, dict, datetime.date, datetime.time, datetime.datetime, typing.Any 및 MediaReference를 지원합니다. typing의 List[str]와 Dict[str, float] 같은 구체 타입도 사용할 수 있습니다.
Parameter 입력은 str, int, float, bool, list, dict, typing.Any 및 pm.NDArray를 지원합니다. 생략하면 default를 사용하고 자료형이 맞지 않으면 오류가 발생합니다. FileSystemInput에는 TransformInput만 지원합니다. Object는 해당 객체 인스턴스 또는 기본 키, ObjectSet은 해당 집합 인스턴스 또는 객체 집합 RID로 전달합니다.
검증의 차이
배치 추론에서는 표 열의 API를 엄격하게 강제하지 않으며 선언된 열이 일부 없어도 API 검증 자체가 오류를 내지는 않습니다. 실제 예측 코드가 없는 열을 참조하면 그 코드에서 실패할 수 있습니다. 선언하지 않은 추가 열은 predict에 전달하므로 평가용 정답 열을 유지할 수 있습니다.
직접 온라인 배포는 선언한 자료형으로 값을 변환하며 필수 열이 없거나 변환할 수 없으면 오류가 발생합니다. Modeling Objective 온라인 배포에는 같은 자동 자료형 변환이 없습니다. model.transform의 Parameter 입력은 자료형을 검사합니다. MediaReference는 배치에서도 각 참조 문자열을 미디어 참조 객체로 변환합니다. Functions는 강한 타입 계약을 사용하므로 게시 조건을 별도로 충족해야 합니다.
출력 선언
TabularOutput(name)은 Pandas 또는 Spark DataFrame을 받습니다. 입력과 달리 df_type을 지정하지 않습니다. ParameterOutput(name, type, default=None)은 단일 값 출력을 선언하며 run_inference에서 쓰지 않으면 default를 사용합니다. 기본 매개변수 타입은 str, int, float, bool, list와 dict이며 NDArray 같은 확장 타입은 간편 API에서 정의합니다.
파일시스템 출력은 쓰기용 open 메서드를 제공하고 run_inference에서 기록한 뒤 transform_write로 소비합니다. 단일 표 또는 매개변수 출력은 predict가 값을 반환하면 그 출력 이름에 연결합니다.
여러 입력과 여러 출력
predict는 입력 이름으로 값을 받습니다. 예를 들어 API에 input_1과 input_2를 선언하면 predict(self, input_1, input_2)에서 두 표를 사용하고 단일 output_dataframe에 해당하는 DataFrame을 반환합니다.
run_inference의 inputs와 outputs는 이름 있는 객체입니다. 입력 df_in에 input_feature(float)를, 출력 df_out_one에 feature_one(int)를, df_out_two에 output_feature_1(int) 및 output_feature_2(float)를 선언했다면 inputs.df_in으로 읽고 각 출력 객체에 맞는 표를 씁니다. 출력의 write는 해당 출력 값을 지정합니다.
다음 완전한 어댑터는 같은 특성을 쓰는 두 분류 모델을 받습니다. first_model과 second_model은 input_feature 표에 대해 정수 분류 결과를 반환해야 합니다. 각각 계산한 결과를 이름이 다른 두 출력으로 기록합니다.
import pandas as pd
import palantir_models as pm
class TwoOutputAdapter(pm.ModelAdapter):
@pm.auto_serialize
def __init__(self, first_model, second_model):
self.first_model = first_model
self.second_model = second_model
@classmethod
def api(cls):
return (
{"input_dataframe": pm.Pandas([("input_feature", float)])},
{
"output_dataframe_one": pm.Pandas([("output_feature", int)]),
"output_dataframe_two": pm.Pandas([("output_feature", int)]),
},
)
def run_inference(self, inputs, outputs):
features = inputs.input_dataframe[["input_feature"]]
first = pd.DataFrame({"output_feature": self.first_model.predict(features)})
second = pd.DataFrame({"output_feature": self.second_model.predict(features)})
outputs.output_dataframe_one.write(first)
outputs.output_dataframe_two.write(second)
이를 변환에서 소비하면 transform 결과의 같은 이름으로 각 DataFrame에 접근합니다. 다음 예시 경로는 실제 리소스로 바꾸며 경량 sidecar에 필요한 palantir_models 0.2010.0 이상을 사용합니다.
from transforms.api import Input, Output, transform
from palantir_models.transforms import ModelInput
@transform.using(
my_input_data=Input("/Project/data/input"),
my_output_data_one=Output("/Project/data/prediction_one"),
my_output_data_two=Output("/Project/data/prediction_two"),
my_model=ModelInput("/Project/models/two_output_model", use_sidecar=True),
)
def compute(my_input_data, my_output_data_one, my_output_data_two, my_model):
result = my_model.transform(my_input_data)
my_output_data_one.write_pandas(result.output_dataframe_one)
my_output_data_two.write_pandas(result.output_dataframe_two)
MediaReference
미디어 참조 입력과 MediaReference 열은 참조 문자열을 런타임 객체로 변환합니다. 참조의 리소스 관계는 미디어 참조에서 설명합니다.
| 멤버 | 동작 |
|---|---|
| media_reference | 원래 미디어 참조 문자열입니다. |
| media_item_rid | 참조에서 추출한 미디어 항목 RID입니다. |
| get_media_item() | 원본 미디어를 파일처럼 읽을 수 있는 객체로 반환합니다. |
| get_media_item_via_access_pattern(access_pattern_name, access_pattern_path) | 지정한 접근 패턴 결과를 파일 객체로 반환합니다. 집합의 지속 정책에 따라 계산 결과를 캐시할 수 있습니다. |
| transform_media_item(output_path, transformation) | 미디어 변환을 적용하고 파일 객체를 반환합니다. output_path를 변환에 전달하며 계산은 Spark 모듈이 아닌 Mio가 수행합니다. |
| get_media_item_metadata() | 폭과 높이 같은 미디어 메타데이터를 반환합니다. |
| transform_and_wait(transformation, output_path="", poll_interval_seconds=3.0, poll_timeout_seconds=30.0) | 변환 후 미디어 서비스에 결과를 확인합니다. 제한 시간 안에 끝나지 않으면 TimeoutError가 발생합니다. |
| transformation_calculation_hint(transformation, output_paths) | 지정한 출력 경로의 변환 계산을 미리 준비하여 이후 transform_and_wait에서 준비되거나 캐시한 결과를 사용하도록 합니다. |
다음 어댑터의 model은 실제 미디어 바이트를 받아 문자열 분류를 반환하는 classify(bytes) 메서드를 제공해야 합니다. 이미지 열의 참조를 열어 읽은 뒤 그 결과를 반환합니다.
import palantir_models as pm
class ImageClassifierAdapter(pm.ModelAdapter):
@pm.auto_serialize
def __init__(self, model):
self.model = model
@classmethod
def api(cls):
return (
{"df_in": pm.Pandas(columns=[("image", pm.MediaReference)])},
{"df_out": pm.Pandas(columns=[("prediction", str)])},
)
def predict(self, df_in):
predictions = []
for media_reference in df_in["image"]:
with media_reference.get_media_item() as media_item:
predictions.append(self.model.classify(media_item.read()))
df_in["prediction"] = predictions
return df_in