튜토리얼: DSPy 프로그램 저장 및 로드
튜토리얼: DSPy 프로그램 저장 및 로드 (Saving and Loading your DSPy program)
이 가이드에서는 DSPy 프로그램을 저장하고 불러오는 방법을 알아볼게요. 큰 그림에서 보면 DSPy 프로그램을 저장하는 방법은 두 가지가 있어요:
- 프로그램의 상태(state)만 저장하기 — PyTorch에서 weights만 저장하는 방식과 비슷해요.
- 아키텍처와 상태를 모두 포함한 프로그램 전체 저장하기 —
dspy>=2.6.0에서 지원해요.
출처: 문서
본문
상태만 저장하기 (State-only Saving)
상태(state)는 DSPy 프로그램의 내부 상태를 나타내는데, 여기에는 signature, 데모(few-shot 예제), 그리고 프로그램의 각 dspy.Predict에서 사용할 lm 같은 정보가 포함돼요. 또한 dspy.retrievers.Retriever의 k처럼 다른 DSPy 모듈의 설정 가능한 속성도 포함합니다.
프로그램의 상태를 저장하려면 save 메서드를 사용하고 save_program=False로 설정하면 돼요. 상태를 JSON 파일이나 pickle 파일로 저장할 수 있는데, JSON 파일이 더 안전하고 읽기 쉽기 때문에 권장해요. 하지만 프로그램에 dspy.Image나 datetime.datetime 같은 직렬화할 수 없는 객체가 포함된 경우에는 pickle 파일로 저장해야 합니다.
컴파일된 프로그램을 저장하고 싶다고 가정해 볼게요:
import dspy
from dspy.datasets.gsm8k import GSM8K, gsm8k_metric
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
gsm8k = GSM8K()
gsm8k_trainset = gsm8k.train[:10]
dspy_program = dspy.ChainOfThought("question -> answer")
optimizer = dspy.BootstrapFewShot(metric=gsm8k_metric, max_bootstrapped_demos=4, max_labeled_demos=4, max_rounds=5)
compiled_dspy_program = optimizer.compile(dspy_program, trainset=gsm8k_trainset)
프로그램의 상태를 JSON 파일로 저장하려면:
compiled_dspy_program.save("./dspy_program/program.json", save_program=False)
프로그램의 상태를 pickle 파일로 저장하려면:
!!! danger "보안 경고: Pickle 파일은 임의의 코드를 실행할 수 있음"
.pkl 파일을 로드하면 임의의 코드가 실행될 수 있고 위험할 수 있어요. 안전한 환경의 신뢰할 수 있는 소스에서만 pickle 파일을 로드하세요. 가능하면 .json 파일을 사용하세요. pickle 파일을 반드시 사용해야 한다면 소스를 신뢰할 수 있는지 확인하고, 로드할 때 allow_pickle=True 매개변수를 사용하세요.
compiled_dspy_program.save("./dspy_program/program.pkl", save_program=False)
저장된 상태를 로드하려면 같은 프로그램을 다시 만든 다음 load 메서드로 상태를 불러와야 해요.
loaded_dspy_program = dspy.ChainOfThought("question -> answer") # Recreate the same program.
loaded_dspy_program.load("./dspy_program/program.json")
assert len(compiled_dspy_program.demos) == len(loaded_dspy_program.demos)
for original_demo, loaded_demo in zip(compiled_dspy_program.demos, loaded_dspy_program.demos):
# Loaded demo is a dict, while the original demo is a dspy.Example.
assert original_demo.toDict() == loaded_demo
assert str(compiled_dspy_program.signature) == str(loaded_dspy_program.signature)
또는 pickle 파일에서 상태를 로드할 수도 있어요:
!!! danger "보안 경고"
pickle 파일을 로드할 때는 반드시 allow_pickle=True를 사용하고, 신뢰할 수 있는 소스에서만 로드하세요.
loaded_dspy_program = dspy.ChainOfThought("question -> answer") # Recreate the same program.
loaded_dspy_program.load("./dspy_program/program.pkl", allow_pickle=True)
assert len(compiled_dspy_program.demos) == len(loaded_dspy_program.demos)
for original_demo, loaded_demo in zip(compiled_dspy_program.demos, loaded_dspy_program.demos):
# Loaded demo is a dict, while the original demo is a dspy.Example.
assert original_demo.toDict() == loaded_demo
assert str(compiled_dspy_program.signature) == str(loaded_dspy_program.signature)
프로그램 전체 저장하기 (Whole Program Saving)
!!! warning "보안 공지: 프로그램 전체 저장은 Pickle을 사용"
프로그램 전체 저장은 직렬화에 cloudpickle을 사용하는데, 이는 pickle 파일과 동일한 보안 위험이 있어요. 안전한 환경에서 신뢰할 수 있는 소스의 프로그램만 로드하세요.
dspy>=2.6.0부터 DSPy는 아키텍처와 상태를 포함한 프로그램 전체 저장을 지원해요. 이 기능은 Python 객체를 직렬화·역직렬화하는 라이브러리인 cloudpickle로 구현됩니다.
프로그램 전체를 저장하려면 save 메서드를 사용하고 save_program=True로 설정한 뒤, 파일 이름 대신 디렉터리 경로를 지정해야 해요. 프로그램 자체와 함께 의존성 버전 같은 메타데이터도 저장하기 때문에 디렉터리 경로가 필요해요.
compiled_dspy_program.save("./dspy_program/", save_program=True)
저장된 프로그램을 로드하려면 dspy.load 메서드를 직접 사용하면 돼요:
loaded_dspy_program = dspy.load("./dspy_program/")
assert len(compiled_dspy_program.demos) == len(loaded_dspy_program.demos)
for original_demo, loaded_demo in zip(compiled_dspy_program.demos, loaded_dspy_program.demos):
# Loaded demo is a dict, while the original demo is a dspy.Example.
assert original_demo.toDict() == loaded_demo
assert str(compiled_dspy_program.signature) == str(loaded_dspy_program.signature)
프로그램 전체 저장을 사용하면 프로그램을 다시 만들 필요 없이 아키텍처와 상태를 함께 직접 로드할 수 있어요. 필요에 따라 적절한 저장 방식을 선택하면 됩니다.
가져온 모듈 직렬화 (Serializing Imported Modules)
save_program=True로 프로그램을 저장할 때, 프로그램이 의존하는 커스텀 모듈을 포함해야 할 수도 있어요. 프로그램이 이 모듈들에 의존하지만, 로드 시점에 dspy.load를 호출하기 전에 이 모듈들이 import되지 않는 경우에 필요합니다.
save를 호출할 때 modules_to_serialize 매개변수로 어떤 커스텀 모듈을 프로그램과 함께 직렬화할지 지정할 수 있어요. 이렇게 하면 프로그램이 의존하는 모든 의존성이 직렬화에 포함되고, 나중에 프로그램을 로드할 때 사용할 수 있게 됩니다.
내부적으로 이는 cloudpickle의 cloudpickle.register_pickle_by_value 함수를 사용해 모듈을 by value로 picklable하게 등록해요. 이렇게 등록된 모듈은 참조(reference)가 아닌 값(by value)으로 직렬화되어, 모듈 내용이 저장된 프로그램과 함께 보존됩니다.
예를 들어, 프로그램이 커스텀 모듈을 사용한다면:
import dspy
import my_custom_module
compiled_dspy_program = dspy.ChainOfThought(my_custom_module.custom_signature)
# Save the program with the custom module
compiled_dspy_program.save(
"./dspy_program/",
save_program=True,
modules_to_serialize=[my_custom_module]
)
이렇게 하면 필요한 모듈이 올바르게 직렬화되고, 나중에 프로그램을 로드할 때 사용할 수 있어요. modules_to_serialize에는 원하는 만큼 많은 모듈을 전달할 수 있지만, 지정하지 않으면 추가 모듈은 등록되지 않습니다.
하위 호환성 (Backward Compatibility)
dspy<3.0.0에서는 저장된 프로그램의 하위 호환성을 보장하지 않아요. 예를 들어 dspy==2.5.35로 프로그램을 저장했다면, 로드할 때도 같은 버전의 DSPy를 사용해야 하고, 그렇지 않으면 프로그램이 예상대로 동작하지 않을 수 있어요. 다른 버전의 DSPy에서 저장 파일을 로드해도 오류가 발생하지 않을 가능성이 크지만, 성능은 저장 당시와 다를 수 있습니다.
dspy>=3.0.0부터는 주요 릴리스(major release) 범위에서 저장된 프로그램의 하위 호환성을 보장해요. 즉, dspy==3.0.0에서 저장한 프로그램은 dspy==3.7.10에서도 로드 가능해야 합니다.