저장과 불러오기
저장과 불러오기 (Saving and loading)
DSPy 프로그램을 저장하면 최적화기가 만든 것을 보존합니다. 다시 쓰인 시그니처 지시문, few-shot 데모, LM 설정, 그리고 각 predictor가 자기 동작을 재현하는 데 필요한 상태가 그것입니다. 두 가지 경로가 있어요 — 상태만 저장해 갓 인스턴스화한 프로그램에 다시 로드하거나, 전체 프로그램을 피클해서 로딩 측이 당신의 클래스 정의를 전혀 필요로 하지 않게 하거나.
출처: 문서
본문
최적화된 프로그램을 다른 팀에 배포하거나, 최적화기 출력을 버전 관리하거나, 왜 .json 저장은 왕복되는데 .pkl 저장은 로드에 추가 플래그가 필요한지 알아내려 할 때 이 문서를 읽으세요.
설계 결정
1. 두 경로: 상태 전용(state-only)과 전체 프로그램(full-program)
상태 전용은 최적화기의 작업 — 데모, 시그니처 지시문, LM 설정 — 을 저장하고 로딩 측이 당신의 클래스 정의를 가진다고 가정합니다. 전체 프로그램은 cloudpickle로 모듈 전체를 저장해 로더가 당신의 코드를 임포트할 필요를 없앱니다. 기본은 상태 전용이고, 소스가 없는 프로세스에 배포할 때 전체 프로그램을 쓰세요.
2. 기본은 JSON
상태 전용은 경로가 .json 으로 끝나면 JSON으로 저장합니다. 사람이 읽을 수 있고, 버전 관리에서 diff가 가능하며, 로드 시 코드 실행 위험이 없습니다. .pkl 상태 형태는 JSON으로 깨끗하게 직렬화되지 않는 상태(데모의 커스텀 Pydantic 객체 등)를 위해 있지만, 거의 항상 JSON 경로를 찾게 될 것입니다.
3. save_program=True 는 "로딩 측에 클래스가 없음" 경우를 위해 있다
로더가 class HaikuEnsemble(dspy.Module) 정의를 갖고 있지 않을 때 — 다른 저장소, 다른 팀, 다른 서비스 — 전체 프로그램 모드는 클래스를 상태와 함께 묶습니다. 로더는 dspy.load(path) 를 호출해 당신의 코드를 임포트하지 않고도 사용 가능한 모듈을 얻습니다. 대가는 cloudpickle이며, 로드 경로가 파일을 신뢰해야 합니다.
4. allow_pickle 은 의도적인 마찰이다
어떤 피클 — 상태 PKL이든 전체 프로그램이든 — 을 로드하려면 allow_pickle=True 가 필요합니다. 기본은 False 로, 무심한 호출자가 임의의 파일을 역직렬화해 조용히 코드를 실행하지 못하게 합니다. 이 플래그는 신뢰 결정을 기본값에 묻지 않고 호출 지점에서 명시하게 하는 강제 함수입니다.
5. allow_unsafe_lm_state 는 기본적으로 엔드포인트를 제거한다
상태를 로드할 때 옵트인하지 않으면 api_base, base_url, model_list 세 개의 LM 설정 키가 빠집니다. 이유: 저장된 프로그램이 로딩 측이 접촉해서는 안 되거나 더 이상 도달할 수 없는 내부 엔드포인트를 가리킬 수 있으니까요. 원래 엔드포인트 설정이 함께 가길 원하면 allow_unsafe_lm_state=True 를 넘기세요.
6. API 키는 결코 직렬화되지 않는다
LM.dump_state 는 저장된 kwargs에서 api_key 를 명시적으로 제외하고, 다시 활성화할 플래그도 없습니다. LM 클라이언트는 항상 로딩 측에서 신선하게 자격증명을 구성해야 합니다. 그것 외에는 기다리고 있는 자격증명 유출이 됩니다.
7. load_state 는 트랜잭션적이다
Module.load_state(state) 를 호출하면 DSPy는 먼저 모듈의 deep copy에 대해 로드를 실행하고, 시도가 성공했을 때만 변경을 라이브 모듈에 커밋합니다. 상태 파일이 손상되었거나 호환되지 않으면 원래 모듈은 그대로 남습니다 — 반쯤 로드된 상태도, 두 설정 사이에 끼인 모듈도 없습니다.
8. 콜백과 히스토리는 저장된 상태에서 제외된다
둘 다 런타임 전용입니다. 콜백은 호출자가 프로세스별로 등록하는 훅이고, 히스토리는 커지는 LM 호출 로그입니다. __getstate__ 에서 빠져 피클된 프로그램과 함께 가지 않고 상태 파일을 부풀리지 않습니다. 로드 후 필요하면 콜백을 다시 등록하고, 히스토리는 새로 시작됩니다.
9. 메타데이터는 별도 파일에 있다
전체 프로그램을 저장하면 디렉터리에 program.pkl 과 metadata.json 이 들어 있습니다. 메타데이터 파일은 Python, DSPy, cloudpickle 의존성 버전을 담아 언피클 없이 읽을 수 있습니다. 상태 전용 JSON 저장은 같은 메타데이터를 JSON dict의 "metadata" 키 아래에 넣습니다.
10. 버전 불일치는 막지 않고 경고한다
이전 DSPy로 저장된 프로그램을 로드하면 버전 차이와 함께 경고가 기록되지만 로드는 진행됩니다. 저장 형식은 백엔드 호환을 목표로 하며, 엄격한 버전 검사는 사용자가 옛 프로그램을 로드하려고 낡은 virtualenv를 유지하게 강제할 것입니다.
11. modules_to_serialize 는 사용자 정의 클래스를 값으로 포함시킨다
기본적으로 cloudpickle은 사용자 클래스를 임포트 경로(mymodule.MyClass)로 직렬화합니다. 로딩 측이 mymodule 을 임포트할 수 없으면 로드가 실패합니다. modules_to_serialize=[MyClass.__module__](또는 모듈 객체)를 넘기면 cloudpickle.register_pickle_by_value 로 등록해 클래스 코드를 피클에 포함시킵니다. 패키지가 아닌 스크립트에 정의된 프로그램을 저장할 때 유용합니다.
API 살펴보기
저장
세 가지 호출 형태; 경로 접미사(또는 그 부재)가 모드를 고릅니다.
Module.save("path.json")
상태 전용 JSON. Module.dump_state(json_mode=True) 에 의존성 버전 metadata 블록을 더해 씁니다. orjson 으로 예쁘게 출력. diff 친화적.
Module.save("path.pkl")
상태 전용 PKL. 같은 상태 dict의 cloudpickle입니다. 상태에 JSON으로 왕복되지 않는 객체가 있을 때 쓰세요. 저장 시점에 로드가 allow_pickle=True 를 요구한다는 경고를 기록합니다.
Module.save("path/", save_program=True)
전체 프로그램. 디렉터리에 program.pkl(self 의 cloudpickle)과 metadata.json(의존성 버전) 두 파일을 씁니다. 경로가 디렉터리 모양이어야 하며, path.suffix 를 넘기면 오류가 납니다. 디렉터리가 없으면 만듭니다.
modules_to_serialize=[...] (전체 프로그램 모드 전용)
피클 전에 각 항목을 cloudpickle.register_pickle_by_value 로 등록합니다. 당신의 커스텀 Module 서브클래스를 정의하는 모듈을 넘기세요. 그렇지 않으면 피클이 임포트 경로로 저장해 로더가 임포트할 수 없을 때 깨집니다.
로드
두 진입점, 두 저장 모드와 일치.
Module.load(path, allow_pickle=False, allow_unsafe_lm_state=False)
기존 모듈 인스턴스에 상태를 로드합니다. 프로그램을 만들 때와 같은 방식으로 인스턴스화한 뒤 .load() 를 호출합니다. JSON 경로는 자유롭게 로드되고, .pkl 경로는 allow_pickle=True 가 필요합니다. allow_unsafe_lm_state=True 는 api_base, base_url, model_list 를 제거 대신 유지합니다.
program = HaikuEnsemble(n=5) # same construction as when saved
program.load("haiku_ensemble.json") # state slots in
dspy.load(path, allow_pickle=False)
전체 프로그램 디렉터리를 로드하고 재수화된 모듈을 반환합니다. 사전 인스턴스화가 필요 없습니다. cloudpickle이 객체 그래프를 재구성합니다. 실제로는 항상 allow_pickle=True 가 필요하며, 이 플래그는 디렉터리가 신뢰할 만하다는 사용자의 확인입니다.
program = dspy.load("haiku_ensemble/", allow_pickle=True)
기본 상태 표면
이것들을 직접 호출할 일은 드뭅니다. Module.save/Module.load 가 사용자 대상 쌍이지만, 무슨 일이 왕복되는지 아는 것이 상태 파일 디버깅에 도움이 됩니다.
Module.dump_state(json_mode=True) → dict
트리의 모든 named parameter에 대해 {name: parameter.dump_state(...)} 를 반환합니다. json_mode=True(기본)는 JSON 직렬화 가능한 모양을 강제하고, False 는 피클 전용 객체를 통과시킵니다(.pkl 저장 경로가 내부적으로 사용).
Module.load_state(state, *, allow_unsafe_lm_state=False)
상태 dict를 적용합니다. 먼저 deep copy에 대해 로드를 실행해 검증한 뒤 라이브 모듈에 커밋합니다. 시도 실패 시 라이브 모듈은 그대로입니다.
Predict.dump_state(json_mode=True)
단일 predictor의 상태: {"traces": [...], "train": [...], "demos": [...], "signature": {...}, "lm": {...}}. 데모는 Pydantic 객체를 평범한 dict로 재귀 변환하는 serialize_object 헬퍼로 직렬화됩니다.
Signature.dump_state()
{"instructions": str, "fields": [{"prefix": str, "description": str}, ...]}. 지시문은 docstring으로, GEPA 같은 최적화기가 다시 쓰는 것입니다. 필드 메타데이터(prefix, description)도 왕복되고, 필드 이름·타입은 로드 시 라이브 Signature 클래스에서 재구성됩니다.
LM.dump_state()
모델 이름, model_type, 캐시 플래그, 재시도 횟수, kwargs(api_key 제외), 파인튜닝 관련 필드. api_key 생략은 하드코딩되어 있고, 다시 옵트인할 플래그가 없습니다.
보안 플래그
allow_pickle=False (기본) — .pkl 이나 전체 프로그램 디렉터리 로드를 거부합니다. 피클 로드는 임의 코드를 실행할 수 있습니다. Module.load 와 dspy.load 둘 다에 적용됩니다.
allow_unsafe_lm_state=False (기본) — 상태 로드 시 LM 설정에서 api_base, base_url, model_list 를 버립니다. True 를 넘기면 원래 엔드포인트 설정을 복원합니다. 저장된 프로그램의 엔드포인트가 로더가 돌리려는 곳과 안 맞을 수 있어서입니다.
어느 플래그도 API 키를 다시 활성화하지 않습니다. 로딩 측은 자격증명을 신선하게 구성합니다.
메타데이터와 버전 관리
모든 저장은 의존성 버전(Python, DSPy, cloudpickle)을 씁니다. 전체 프로그램 저장은 program.pkl 옆의 metadata.json 에, 상태 전용 저장은 JSON/PKL 상태의 "metadata" 키 아래에 넣습니다. 로드 시 런타임이 현재 프로세스와 버전을 비교합니다. 불일치는 경고를 기록하고 진행됩니다.
크로스링크
- 모듈: 나만의 모듈 조합하기 —
Module.save/load는BaseModule에서 상속되며, 상태를 모으는 트리 탐색은 최적화기가 쓰는 것과 같음. - 시그니처 심층 탐구 —
Signature.dump_state/load_state가 최적화기의 다시 쓴 지시문을 왕복시키는 것. - 설정과
context()—dspy.settings.save/dspy.load_settings가 settings 싱글턴의 병렬 표면.