https://arxiv.org/pdf/2310.03714


Abstract:

  • 최근 머신러닝(ML) 커뮤니티에서는 대형 언어 모델을 프롬프트(prompt)만으로 다양한 복잡한 작업을 수행하게 하는 방법들이 활발히 연구되고 있음. 그리고 대표적인 방법으로 여러 단계(스텝)로 구성된 작업을 해결하기 위해 언어 모델을 직렬 혹은 병렬로 쌓아놓은 “파이프라인” 이 많이 등장하고 있다.
  • 이런 파이프라인에서는 프롬프트 설계 비용이 많이 들어간다는 단점이 있음.
  • 그래서 이런 문제를 해결하기 위해 프롬프트를 프로그래밍 언어처럼 설계하는 최적화 기법이 등장하고 있다.
  • 이 기법은 DSPy 임.
  • 논문에서는 두 가지 사례 연구(case study)를 통해 DSPy의 성능을 확인하고 있음:
    • 수학 문제 풀이, 멀티홉(Multi-hop) 검색, 복잡한 질문 answering, 에이전트(Agent) 제어 등 다양한 고난도 작업을 DSPy 파이프라인으로 표현할 수 있음.
    • DSPy로 작성한 파이프라인을 GPT-3.5, Llama2-13B 같은 대형 언어 모델에 적용했을 때, 단순 Few-shot 프롬프트 방식 대비 큰 성능 향상(최소 수십 %) 이 있었고 전문가가 직접 작성한 프롬프트 체인보다도 최대 5–46%(GPT-3.5 기준), 16–40%(Llama2-13B 기준) 더 높은 성능을 달성함.

 

Introduction:

  • DSPy 소개: 선언적 프로그래밍 모델:
    • “프롬프트 = 긴 문자열”에서 벗어나, 선언적 모듈(Declarative Modules)로 추상화:
      • 프롬프트 및 추론 기법(예: Chain-of-Thought, ReAct)을 “모듈” 형태로 캡슐화 됨.
      • 각 모듈은 자연어를 입력으로 받아서 원하는 자연어를 출력한다는 식의 명제를 가진다.
    • 텍스트 변환 그래프(Text Transformation Graph) 구성:
      • DSPy에서 파이프라인을 “노드(모듈)와 간선(데이터 흐름)으로 이루어진 그래프”로 표현됨.
      • define-by-run 방식을 채택해, if/else 분기나 반복(for/while) 같은 논리적 흐름을 코드로 작성하면 그대로 “언어 모델 호출 파이프라인”이 됨. (마치 PyTorch에서 연산 그래프를 구성하듯이)
      • “Define-by-run” 방식이란, 프로그래밍 시점에 실행 흐름(로직)에 따라 그래프(혹은 모델 구조)를 동적으로 구성하는 방법을 말함.
      • 다음과 같이 DSPy 모듈을 작성하고 실행하면 그래프로 만들어지고 알아서 잘 됨.
    • 파라미터화된(Parametrized) 모듈과 자동 데모(샘플) 수집:
      • 각 모듈은 “어떤 프롬프트/전략을 써야 가장 성능이 좋은가?”를 직접 학습할 수 있도록 설계되어 있음. (사용할 수 있는 프롬프트는 정해져있지만)
      • 파이프라인을 실행하면서 생성되는 데모(demonstration) 예시들을 모으고, 이를 바탕으로 few-shot 프롬프트나 소규모 모델 파인튜닝 등에 활용해 점차 자기 개선(self-improving) 해나가는 식으로 실행이 됨.
  • DSPy 컴파일러(Compiler)와 최적화:
    • DSPy 컴파일러는 프로그램(파이프라인) + 적은 양의 학습 데이터 + 검증 지표(metric) 를 입력으로 받아들여서 최적화함.
    • 파이프라인의 각 모듈 실행 과정을 시뮬레이션하며 모듈별로 “모범 예시”를 쌓아나가고, 이를 이용해 few-shot 프롬프트나 소규모 파인튜닝 방법을 자동으로 결정함.
    • Teleprompters:
      • 최적화를 총괄하는 전략(optimizer)을 저자들은 teleprompters 라고 함.
      • 이는 DSPy 모듈이 “어떤 prompting 기법, reasoning 전략, augmentation을 조합해서 사용할지” 등을 학습하여, 최적 조합을 찾는 역할을 한다.
    • 자동화된 프롬프트 및 파이프라인 최적화가 이루어져, 기존처럼 사람이 일일이 긴 프롬프트를 작성·튜닝할 필요가 없어짐.

 

DSPy 예시 코드:

# DSPy에서 가상의 예시 코드 (pseudo-code)
question_module = dspy.Module("question_answering")
retrieval_module = dspy.Module("retrieval")
reason_module = dspy.Module("chain_of_thought")

for question in question_list:
    evidence = retrieval_module(question)
    if len(evidence) > 0:
        reasoning = reason_module(question, evidence)
        answer = question_module(question, reasoning)
    else:
        # 근거가 전혀 없으니 다른 전략을 쓸 수도 있음
        answer = some_other_module(question)
    print(answer)

 

 

THE DSPY PROGRAMMING MODEL:

  • 구성 요소:
    • Signature:
      • 입력과 아웃풋을 주는 것으로 모듈의 행동을 결정 지어주는 요소
      • 기존에는 “긴 문자열 형태의 프롬프트”를 직접 작성하여, “어떻게” 모델을 불러야 하는지를 구체적으로 명시해야 했다면, DSPy는 이러한 “어떻게(how)” 대신, “무엇을(what)” 해야 하는지를 간결한 선언형 방식으로 표현하면 됨.
      • 시그니처는 입력 필드(예: question), 출력 필드(예: answer), 그리고 옵션으로 추가 지시사항(instruction) 으로 구성됨.
    • Modeuls:
      • Signature 를 바탕으로 Prompt 로 변경하고, 파이프라인을 구성하는 요소
      • 가장 기본적인 모듈은 Predict 모듈:
        • 특정 시그니처(예: "question -> answer")를 수행하는 함수처럼 동작함.
        • 내부적으로는 다음 정보들을 보관합니다:
          • 시그니처(signature): “입력 필드와 출력 필드” 구조(예: question, answer)
          • LM(언어 모델) 정보: 사용할 LM (처음엔 None이며, 설정 시 그 LM으로 override)
          • 데모(데몬스트레이션) 목록: few-shot 예시로 쓰일 시나리오들(초기엔 빈 리스트)
        • 호출(예: predict_module(question="..."))하면, 이 입력을 바탕으로 자동으로 프롬프트를 구성하고 LM에 전달한 뒤, 모델의 출력을 파싱(parsing)하여 출력 필드를 채움.
        • 컴파일 모드(compile mode)에서는 입력/출력 사례(trace)를 내부적으로 기록하여 teleprompter가 데모를 학습(bootstrapping)할 수 있도록 도와줌. (어떤 데모가 더 성능에 도움이 되는지)
      • 다른 빌트인 모듈들: 프롬프트 기법의 모듈화:
        • ChainOfThought, ProgramOfThought, MultiChainComparison, ReAct 등이 있음.
        • ChainOfThought 모듈 내부를 보면, 결국은 사용자가 정의한 시그니처를 확장해서 여러 번 Predict를 호출하거나, 프롬프트에 “이유(Reasoning)” 단계를 삽입하는 로직을 간단히 추가한 정도로 이루어짐
      • 파라미터화(Parameterization) 개념:
        • DSPy는 이들 모듈(Predict, ChainOfThought 등)을 파라미터화할 수 있음:
          • 어떤 LM을 쓸 것인지(예: GPT-3.5, Llama2-13B 등)
          • 프롬프트에 들어갈 구체적 지시사항(Instruction)이나 서식(format)
          • few-shot 학습용 데모(데몬스트레이션) 혹은 파인튜닝 데이터
        • 이 모두를 DSPy가 자동으로 관리·최적화하는 구조. 특히 데모(demonstration)를 “어떻게 생성하고 어떤 순서로 넣을지”가 프롬프트 성능에 중요한데, DSPy의 컴파일러(teleprompter)가 이를 자동으로 부트스트랩(bootstrapping)해줌.
          • Tools(도구) 모듈:
          • dspy.Retrieve (검색: ColBERTv2, Pyserini, Pinecone 등 다양한 검색 엔진을 지원), dspy.SQL (SQL 쿼리 실행), dspy.PythonInterpreter (코드 실행: 파이썬 코드를 샌드박스 형태로 실행)
    • Teleprompters:
      • 여러 조합을 비교해보면서 최적의 성능을 내는 전략을 찾는 도구
      • 그래서 Training Set과 Metric 설정을 해야함.

 

 

DSPy - Compiler:

  • DSPy 컴파일러는 “프로그램(= 파이프라인)”을 입력받아, 각 모듈(특히 Predict 모듈)의 파라미터(프롬프트 지시사항, 데모 예시, 등)를 자동으로 최적화함.
  • 이 과정을 수행하는 주체가 바로 Teleprompter로, “프롬프트/파인튜닝 전략”을 다양하게 활용해 모듈들을 개선한다.
  • 목표는 주어진 평가지표를 최대화하는게 목표임.
  • 동작 과정은 다음과 같다:
    • Stage 1: Candidate Generation (후보 생성 단계)
      • (1) 프로그램 내 Predict 모듈 찾기: 파이프라인 전체(내부에 중첩된 모듈까지)에서 Predict 모듈을 모조리 찾아냄.
      • (2) 각 모듈의 파라미터 후보 생성: Teleprompter가 “지시문(instruction)”, “필드(시그니처) 설명”, 그리고 가장 중요하게 “데모 예시(입출력 쌍)” 후보를 여러 형태로 만들어냄.
        • 예) BootstrapFewShot 텔레프롬프터는 일종의 “거절 샘플링”(rejection-sampling) 방식을 이용해, 파이프라인을 반복 실행하고 성공적인 결과만 모으는 식으로 예시를 생성함.
        • 거절 샘플링은 특정 조건을 만족하는 샘플만 선택하고, 나머지 샘플은 버리는 방식.
        • 파이프라인(teacher 프로그램이 있으면 그것을 사용, 없으면 zero-shot 모드)을 주어진 학습 예시로 시뮬레이션 실행 → “중간 결과(Trace)”를 전부 수집 → metric에 부합하는(좋은 성능을 낸) 입력-출력 쌍만 골라서 데모 예시로 활용.
    • Stage 2: Parameter Optimization (파라미터 최적화 단계):
      • (1) 파라미터 후보 중 최적 조합 찾기
        • 각 Predict 모듈에 대해, “데모 예시 세트 후보”, “지시문 후보” 등 여러 옵션이 존재할 때, 랜덤 서치 또는 Optuna/HyperOpt 같은 기법으로 최적 조합을 찾음.
      • (2) 파인튜닝(BootstrapFinetune):
        • Few-shot prompting 대신 파인튜닝 사용:
          • BootstrapFinetune는 데모 예시를 LM 자체의 학습 데이터로 써서, 가중치(Weights)를 업데이트 함. (LoRA 등의 경량 파인튜닝일 수도 있음).
          • 파인튜닝된 모델을 각 모듈에 배정 → 결과적으로 “모듈의 LM 파라미터”가 교체됨.
        • 레이블이 거의 없어도 가능: 최종 출력만 맞으면, 중간 단계 레이블 없이도 학습이 가능(“self-improving” 구조).
    • Stage 3: Higher-Order Program Optimization (고차원적 프로그램 최적화):
      • DSPy 컴파일러는 모듈 파라미터 조정뿐 아니라, 프로그램의 제어 구조를 바꾸는 형태의 최적화도 지원함.
      • 예시: 앙상블(ensemble) - 일한 프로그램을 여러 복사본으로 부트스트랩 → 동시에 실행 → 최종 출력을 Majority Voting 등으로 합성. 이 방법으로 성능이 향상되었다고 함.
      • 이후에는 “실행 중 동적으로(테스트 타임에) 백트래킹”하거나 “자동 분기”를 생성하는 등, 더 복잡한 최적화 로직도 쉽게 포함할 수 있다고 언급

+ Recent posts