안녕하세요.
9기 HuggingFace 파트너 겸 뾰족한 개발자 청강생 정정민입니다. 😁
8기에서 랭체인을 공부하면서,
LCEL을 깊이 있게 이해해야 함을 느끼게 되었습니다. (LCEL이 미래다!!)
얼마나 될지 모르겠지만 LCEL을 알아가는 과정을 담아보려 합니다.
오늘은 첫 시간으로, LCEL를 찾아보면 항상 맨 앞에 나오는 말이죠.
invoke, batch, stream을 사용해보고 각각의 특징을 살펴보려고 합니다.
LCEL 설명 페이지를 찾아보면…
이런 이야기가 있습니다.
앞으로 살펴 볼 많은 이야기가 담겨있네요.
일단 오늘 살펴볼 내용만 초점을 맞춰보면,
LCEL을 활용해 구성한 chain은 일반적으로 많이 사용하는 명령 함수를 사용할 수 있다고 하네요.
거기에는 아래의 것이 포함됩니다.
invoke
batch
stream
이게 좋은지, 어떤 장점이 있는지, 어떤 특징이 있는지 등등을 알아야
LCEL을 이해할 수 있겠네요.
(참고로, 앞에 a가 붙은 비동기 명령문(ainvoke, abatch, astream)들도 있습니다만, 이는 추후에 살펴보죠!)
이 포스트에서 사용할 간단한 chain을 만들어보죠. (공식 페이지의 예시 코드를 활용)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template(
"Tell me a short joke about {topic}"
)
output_parser = StrOutputParser()
model = ChatOpenAI(model="gpt-3.5-turbo", api_key=OPENAI_API)
chain = (
{"topic": RunnablePassthrough()}
| prompt
| model
| output_parser
)
invoke
invoke란, 구성한 chain에 단 하나의 입력을 넣어 출력을 반환하는 함수입니다.
저는 invoke 라는 용어를 사용해본 경험이 없었는데요..
네이버 영어 사전을 보니 invoke의 6번째 의미를 이용한 것 같네요 ^^;;
위 chain에 입력으로 ‘ice cream’을 넣어볼까요?
이때 사용하는 메소드가 invoke 입니다.
chain.invoke('ice cream')
결과는 아래와 같습니다.
Why did the ice cream go to the gym?
Because it wanted to be a little "cone"dy!
시간은 얼마나 걸릴까요?
시간 측정 함수를 작성해 결과를 살펴보면 아래와 같습니다.
def measure_execution_time(func, *args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"{func.__name__} 실행 시간: {execution_time:.3f}초")
return result
measure_execution_time(chain.invoke, "ice cream") # 1.294 초
사용하는 모델과 인터넷 상황에 따라 다르겠지만 대략 1.294초가 걸렸네요.
batch
batch란, 구성한 chain에 복수의 입력을 넣어 각 입력에 따른 출력을 반환하는 함수입니다.
위 예를 사용했을 때,
ice cream에 따른 결 과 뿐 아니라
chocolate의 결과도 그리고 vanilla 결과도 같이 보고싶으면 어떻게 할까요??
가장 쉽게 생각할 수 있는 것은 앞서 살펴본 invoke를 원하는 입력 수 만큼 넣어주면 되겠네요.
마치 아래와 같은 pseudo code 처럼요!
results = []
for 입력 in 원하는 입력들 :
res = chat.invoke(입력)
results.append()
하지만 여기에는 문제(?)가 있는데,
직렬화 방식으로 인한 작업 시간의 증가입니다.
s = time.time()
for item in ["ice cream", "chocolate", "vanilla"]:
chain.invoke(item)
duration = time.time() - s
print(f"Duration: {duration:.3f} sec") # 3.097 sec
앞선 invoke 에서 약 1.3초 걸렸는데
3개 했을 때 3.1초 정도 걸렸네요.
어느정도 선형적으로 증가하네요.
좀 더 실험적으로 보면
선형성이 더 잘 보이네요.
(4개 무슨일이죠?? ㅎㅎ;;)
batch 메소드는
복수개의 입력을 한번에 넣을 수 있는 기능을 제공합니다.
코 드를 쭉 따라가보면 (링크)
invoke 함수를 map 함수로 감싸 병렬로 처리를 하네요
with get_executor_for_config(configs[0]) as executor:
return cast(List[Output], list(executor.map(invoke, inputs, configs)))
chatGPT의 답에 따르면 executor.map은 이런 함수라고 합니다.
» executor.map이 무슨 함수야?
» executor.map 함수는 concurrent.futures 모듈의 Executor 클래스(또는 그 하위 클래스인 ThreadPoolExecutor와 ProcessPoolExecutor)의 메서드입니다. 이 메서드는 여러 작업을 동시에 실행하려고 할 때 사용됩니다. Python의 map 함수와 유사하게, executor.map은 주어진 함수를 순회 가능한(iterable) 각 요소에 대해 병렬로 적용합니다. 하지만 executor.map은 작업을 병렬로 실행하여 CPU 사용을 최적화하거나 I/O 작업을 비동기적으로 수행할 때 이점을 제공합니다.
3개의 입력을 동시에 처리하면 아래와 같은 결과와 시간이 걸립니다.
measure_execution_time(chain.batch, ["ice cream", "chocolate", "vanilla"]) # 1.33초
>> ['Why was the ice cream so bad at tennis? \n\nBecause it had a soft serve!', 'Why did the chocolate chip cookie go to the doctor?\n\nBecause it was feeling crumby!', "Why did the vanilla go to therapy?\n\nBecause it had serious identity issues - it couldn't decide if it wanted to be ice cream or a candle scent!"]
결과도 원하는 방식으로 잘 나왔네요!
직렬 처리 시 3.1초가 걸린 것에 비해 매우 짧게 걸렸네요!
마치 1개를 처리하는 시간과 비슷합니다!!
어느 정도의 fluctuate가 존재하지만
invoke를 활용한 for문보다는
시간 측면에서 훨씬 이득이 많네요.
Stream
우리가 chatGPT를 쓰다보면
한 단어, 한 단어씩 글이 만들어집니다.
이 효과(?)의 장점은
답변을 실시간으로 만드는 것 같은 느낌
작업 속도를 간접적으로 확인
답변의 내용을 답변 중에 습득 가능
등등
이 있어보입니다.
이런 방식을 streaming 이라고 하고
이는 특히, 답변에 긴 경우 유용하다고 합니다.
LCEL에서도 이 기능을 제공하며
아래와 같은 코드로 구현 가능하다고 합니다.
for chunk in chain.stream("ice cream"):
print(chunk, end="", flush=True)
chunk는 LLM에서 제공하는 token 의 단위입니다.
즉, 하나 하나의 token이 출력되고 그것을 바로 보여줍니다.
아래는 chunk 단위를 구분하는 구분자를 넣어 출력한 결과입니다.
for chunk in chain.stream("ice cream"):
print('!!', chunk, end="", flush=True)
>> !! !! Why!! did!! the!! ice!! cream!! go!! to!! therapy!! ?!! Because!! it!! had!! a!! rocky!! road!! !!! %
구현할 서비스에 따라 streaming 방식이 유용할 것 같네요!
#9기랭체인
작성자 : 정정민
블로그 : 링크