이 가이드에서는 다양한 LLM 활용 사례에서 지연 시간을 줄이는 데 적용할 수 있는 핵심 원칙을 다룹니다. 여러 고객 및 개발자와 함께 실제 운영 환경의 애플리케이션을 개선하면서 얻은 기법들로, 개별 작업을 위한 워크플로우부터 채팅 애플리케이션 전체에 이르기까지 폭넓게 적용할 수 있습니다.
지연 시간을 줄이는 기법은 다양하지만, 접근 방식에 따라 크게 일곱 가지 원칙으로 정리할 수 있습니다.
마지막에는 예시를 통해 이 원칙들을 어떻게 적용하는지 살펴봅니다.
일곱 가지 원칙
- 토큰을 더 빠르게 처리하세요.
- 생성하는 토큰 수를 줄이세요.
- 입력 토큰 수를 줄이세요.
- 요청 횟수를 줄이세요.
- 병렬로 처리하세요.
- 사용자의 대기 시간을 줄이세요.
- LLM부터 선택하지 마세요.
토큰을 더 빠르게 처리하기
지연 시간을 줄이려 할 때 가장 먼저 떠올리는 요소는 아마 추론 속도일 것입니다. 물론 뒤에서 살펴보겠지만 고려할 요소는 더 많습니다. 추론 속도는 LLM이 실제로 토큰을 처리하는 속도를 뜻하며, 흔히 TPM(분당 토큰 수)이나 TPS(초당 토큰 수)로 측정합니다.
추론 속도에 가장 큰 영향을 주는 요소는 모델 크기입니다. 작은 모델은 대체로 더 빠르고 비용도 적게 들며, 적절히 활용하면 큰 모델보다 더 좋은 결과를 내기도 합니다. 작은 모델에서도 높은 품질을 유지하려면 다음 방법을 시도해 보세요.
예측 출력 기능과 같은 추론 최적화 기법도 활용할 수 있습니다. 코드 편집처럼 출력 내용의 대부분을 미리 아는 작업에서는 예측 출력을 사용하면 생성에 걸리는 시간을 크게 줄일 수 있습니다. 모델에 예상 출력을 제공하면 LLM은 그대로 유지할 내용보다 실제로 바꿔야 할 부분에 집중할 수 있습니다.
추론 속도는 사용 가능한
연산 자원의 양과 추가로 적용하는
추론 최적화 기법에 따라서도 달라집니다.
대부분의 사용자가 이런 요소를 직접 조정하기는 어렵습니다. 하지만 인프라를 어느 정도 제어할 수 있고 직접 시도해 보고 싶다면 더 빠른 하드웨어를 사용하거나
엔진 부하를 낮춰 여유 용량을 확보하면 TPM을 소폭 높일 수 있습니다. 추론 인프라를 직접 다룬다면 그 밖에도 다양한
추론 최적화 기법
중에서 적용할 방법을 찾아볼 수 있습니다. 이 가이드에서는 이 기법들을 자세히 다루지 않습니다.
생성하는 토큰 수 줄이기
LLM을 사용할 때 거의 항상 가장 오래 걸리는 단계는 토큰 생성입니다. 경험적으로 출력 토큰 수를 50% 줄이면 지연 시간도 약 50% 줄어들 수 있습니다. 출력 크기를 줄이는 방법은 출력 유형에 따라 다릅니다.
자연어를 생성한다면 “20단어 미만으로 답하세요”나 “짧게 답하세요”처럼 모델에 간결한 답변을 요청하는 것이 도움이 될 수 있습니다. 퓨샷 예시나 파인튜닝을 사용하거나 두 방법을 함께 적용해 모델이 짧게 답하도록 학습시킬 수도 있습니다.
구조화된 출력값을 생성한다면 가능한 한 출력 형식을 간결하게 만드세요. 함수 이름을 줄이거나, 이름 있는 인수를 생략하거나, 매개변수를 합치는 방법이 있습니다.
자주 쓰는 방법은 아니지만 max_tokens나 stop_tokens로 생성을 일찍 끝낼 수도 있습니다.
출력 토큰을 하나라도 줄이면 그만큼 생성 시간을 절약할 수 있습니다!
입력 토큰 수 줄이기
입력 토큰 수를 줄이면 지연 시간도 줄어들지만, 대개 효과는 크지 않습니다. 프롬프트 길이를 50% 줄여도 지연 시간은 1~5% 정도만 줄어들 수 있습니다. 문서나 이미지 등으로 컨텍스트가 매우 커지는 경우가 아니라면 다른 부분을 최적화하는 편이 나을 수 있습니다.
다만 실제로 매우 큰 컨텍스트를 다루거나, 성능을 조금이라도 더 높이고 싶은데 이미 다른 방법을 모두 시도했다면 다음 기법으로 입력 토큰 수를 줄일 수 있습니다.
- 모델을 파인튜닝하면 긴 지침이나 예시를 제공할 필요가 줄어듭니다.
- 입력 컨텍스트를 필터링하세요. 예를 들어 불필요한 RAG 결과를 제외하거나 HTML을 정리할 수 있습니다.
- 프롬프트 앞부분을 최대한 공통으로 유지하세요. RAG 결과나 대화 기록처럼 요청마다 달라지는 내용은 프롬프트 뒤쪽에 배치하세요. 그러면 대부분의 LLM 제공업체가 사용하는 KV 캐시를 더 잘 활용할 수 있어 요청마다 처리해야 하는 입력 토큰 수가 줄어듭니다.
프롬프트 캐싱이 어떻게 작동하는지 문서에서 자세히 알아보세요.
요청 횟수 줄이기
요청을 보낼 때마다 응답이 돌아오기까지 시간이 걸립니다. 요청이 반복되면 이 왕복 시간이 누적됩니다.
LLM이 여러 단계를 순서대로 수행해야 한다면 단계마다 요청을 보내는 대신 한 프롬프트에 모든 단계를 담아 응답 한 번으로 전체 결과를 받는 방법을 고려하세요. 추가 왕복 시간을 없앨 수 있고, 여러 응답을 처리하는 과정도 단순해질 수 있습니다.
한 가지 방법은 통합 프롬프트에 각 단계를 번호가 있는 목록으로 작성하고, 모델에 각 단계의 결과를 JSON 객체의 지정된 필드에 담아 반환하도록 요청하는 것입니다. 그러면 각 결과를 파싱하고 개별적으로 참조할 수 있습니다.
병렬 처리하기
LLM으로 여러 단계를 수행할 때는 병렬 처리가 효과적일 수 있습니다.
단계들을 반드시 순서대로 수행할 필요가 없다면, 여러 호출로 나눠 병렬로 실행할 수 있습니다. 셔츠 두 벌을 함께 널면 한 벌을 말릴 때와 같은 시간이 걸리는 것과 같습니다.
반대로 반드시 순서대로 수행해야 하는 단계라도 추측 실행을 활용할 여지가 있습니다. 특히 콘텐츠 검토처럼 분류 결과 중 하나가 다른 결과보다 나올 가능성이 높은 단계에서 효과적입니다.
- 1단계와 2단계를 동시에 시작합니다(예: 입력 콘텐츠 검토와 이야기 생성).
- 1단계의 결과를 확인합니다.
- 결과가 예상과 다르면 2단계를 취소하고 필요에 따라 다시 시도합니다.
1단계의 결과가 예상과 일치하면 사실상 추가 지연 시간 없이 1단계를 실행한 셈입니다!
사용자의 대기 시간 줄이기
기다리는 것과 진행 상황을 지켜보는 것은 사용자에게 전혀 다른 경험입니다. 사용자가 진행 상황을 확인할 수 있도록 다음 방법을 활용하세요.
- 스트리밍: 사용자가 기다리는 시간을 1초 이하로 줄여 주는 가장 효과적인 방법입니다. ChatGPT가 응답을 완성할 때까지 화면에 아무것도 표시하지 않는다면 사용 경험이 크게 달라질 것입니다.
- 청크 단위 처리: 사용자에게 출력을 보여 주기 전에 콘텐츠 검토나 번역 등 후처리가 필요하다면 전체 출력을 한꺼번에 처리하는 대신 청크 단위로 처리해 보세요. 출력을 백엔드로 스트리밍하고 처리가 끝난 청크부터 프런트엔드로 보내면 됩니다.
- 단계 표시: 여러 단계를 거치거나 도구를 사용한다면 사용자에게 그 과정을 보여 주세요. 실제 진행 상황을 구체적으로 보여 줄수록 좋습니다.
- 로딩 상태 표시: 로딩 스피너나 진행률 표시줄만으로도 사용자가 진행 상황을 파악하는 데 큰 도움이 됩니다.
단계와 로딩 상태를 표시하는 효과는 주로 심리적인 반면, 스트리밍과 청크 단위 처리는 앱의 처리 과정부터 사용자가 응답을 읽는 과정까지 고려하면 실제로 전체 지연 시간을 줄여 줍니다. 사용자가 응답을 더 일찍 읽기 시작해 더 빨리 다 읽을 수 있기 때문입니다.
LLM부터 선택하지 않기
언어 모델은 강력하고 쓰임새가 다양해서 더 빠른 기존 방식이 적합한 경우에도 쓰이곤 합니다. 이런 경우를 찾아내면 지연 시간을 크게 줄일 수 있습니다. 다음 예시를 살펴보세요.
- 하드코딩: 가능한 출력의 범위가 매우 좁다면 LLM으로 생성할 필요가 없을 수 있습니다. 작업 확인 메시지, 거절 메시지, 정해진 형식의 입력을 요청하는 문구는 하드코딩하기에 적합합니다. 예전부터 쓰던 방식대로 각 메시지의 문구를 몇 가지씩 준비해 두어도 됩니다.
- 사전 계산: 카테고리 선택처럼 가능한 입력이 제한적이라면 응답을 여러 개 미리 생성해 두고 같은 사용자에게 동일한 응답이 두 번 표시되지 않게 하면 됩니다.
- UI 활용: 요약 지표, 보고서, 검색 결과 등은 LLM이 생성한 텍스트보다 용도에 맞게 설계한 기존 UI 구성 요소로 더 잘 전달할 수 있습니다.
- 전통적인 최적화 기법: LLM 애플리케이션도 결국 애플리케이션입니다. 이진 탐색, 캐싱, 해시 맵을 활용하거나 시간 복잡도를 고려하는 방법은 언어 모델을 사용할 때도 여전히 유효합니다.
예시
예시 애플리케이션을 살펴보면서 지연 시간을 줄일 수 있는 부분을 찾아 해결 방법을 제안해 보겠습니다.
실제 운영 중인 애플리케이션을 참고해 만든 가상의 고객 지원 봇을 대상으로 아키텍처와 프롬프트를 분석합니다. 아키텍처와 프롬프트에서 봇의 작동 방식을 살펴본 다음, 분석과 최적화에서 지연 시간을 줄이는 과정을 따라가 보겠습니다.
실제 활용 사례마다 모든 기법을 적용할 필요는 없듯이, 이 예시에서도 일곱 가지 원칙을 모두 다루지는 않습니다.
아키텍처와 프롬프트
다음 초기 아키텍처는 가상의 고객 지원 봇을 위해 설계했습니다. 이후 이 구조를 개선해 나가겠습니다.

다이어그램에 나타난 전체 처리 과정은 다음과 같습니다.
- 사용자가 진행 중인 대화에서 메시지를 보냅니다.
- 마지막 메시지를 그 자체로 의미가 통하는 질의로 다시 작성합니다(프롬프트의 예시 참조).
- 해당 질의에 답하기 위해 추가 정보를 검색해야 하는지 판단합니다.
- 검색을 수행해 결과를 얻습니다.
- 어시스턴트가 사용자의 질의와 검색 결과를 바탕으로 추론하고 응답을 생성합니다.
- 사용자에게 응답을 보냅니다.
다음은 다이어그램의 각 단계에서 사용하는 프롬프트입니다. 설명을 위해 단순화한 가상의 예시지만, 구조와 표현은 실제 운영 환경에서 사용하는 프롬프트와 같습니다.
“[user input here]”와 같은 자리표시자는 실행 시 실제 데이터로 대체되는 부분을 나타냅니다.
분석과 최적화
1단계: 검색 관련 프롬프트 살펴보기
아키텍처에서 가장 먼저 눈에 띄는 부분은 GPT-4를 연속으로 호출하는 단계입니다. 연속 호출은 비효율이 발생할 수 있는 지점으로, 한 번의 호출로 합치거나 병렬로 실행할 수 있는 경우가 많습니다.

이 예시에서 검색 필요 여부를 확인하려면 컨텍스트를 반영한 질의가 필요합니다. 따라서 두 단계를 하나의 프롬프트로 합쳐 요청 횟수를 줄이겠습니다.

질의에 컨텍스트를 반영하고 검색 필요 여부를 판단하는 작업은 단순하고 범위가 명확합니다. 따라서 파인튜닝한 더 작은 모델로도 충분할 가능성이 높습니다. GPT-3.5로 전환하면 토큰을 더 빠르게 처리할 수 있습니다.

2단계: 어시스턴트 프롬프트 분석하기
이번에는 어시스턴트 프롬프트를 살펴보겠습니다. JSON 필드를 채우는 과정이 여러 개의 개별 단계로 나뉘어 있어 병렬 처리를 시도해 볼 만합니다.

하지만 테스트 결과 JSON의 추론 단계를 분리하면 응답 품질이 떨어진다고 가정해 보겠습니다. 그렇다면 다른 방법을 찾아야 합니다.
GPT-4 대신 파인튜닝한 GPT-3.5를 사용할 수 있을까요? 가능할 수도 있습니다. 다만 어시스턴트가 다양한 질문에 자유롭게 답해야 한다면 일반적으로 더 폭넓은 상황에 잘 대응하는 GPT-4에 맡기는 편이 좋습니다. 반면 개별 추론 단계를 보면 모든 단계에 GPT-4 수준의 추론이 필요한 것은 아닐 수 있습니다. 작업 범위가 좁고 명확해 파인튜닝을 시도하기에 적합합니다.
{
message_is_conversation_continuation: "True", // <-
number_of_messages_in_conversation_so_far: "1", // <-
user_sentiment: "Aggravated", // <-
query_type: "Hardware Issue", // <-
response_tone: "Validating and solution-oriented", // <-
response_requirements: "Propose options for repair or replacement.", // <-
user_requesting_to_talk_to_human: "False", // <-
enough_information_in_context: "True", // <-
response: "...", // X -- benefits from GPT-4
};여기에는 두 가지 선택지가 있습니다. GPT-4에 한 번 요청해 모든 필드를 생성할까요, 아니면 요청을 순차적으로 두 번 보내고 최종 응답을 제외한 모든 필드는 GPT-3.5에 맡길까요? 이 경우에는 두 원칙이 충돌합니다. 첫 번째 방법은 요청 횟수를 줄일 수 있고, 두 번째 방법은 토큰을 더 빠르게 처리할 가능성이 있습니다.
다른 최적화 선택지와 마찬가지로 어느 쪽이 나은지는 구체적인 조건에 따라 달라집니다. 예를 들어 다음 요소를 살펴보세요.
response필드와 나머지 필드가 각각 차지하는 토큰 비율- 대부분의 필드를 더 빠르게 처리할 때 평균적으로 줄어드는 지연 시간
- 요청을 한 번에서 두 번으로 늘릴 때 평균적으로 늘어나는 지연 시간
결론은 사례마다 다릅니다. 가장 확실한 판단 방법은 실제 운영 사례로 테스트하는 것입니다. 이 예시에서는 테스트 결과 프롬프트를 둘로 나눠 토큰을 더 빠르게 처리하는 편이 유리했다고 가정하겠습니다.

참고: response와 enough_information_in_context는 두 번째 프롬프트에서 함께 처리합니다. 이렇게 하면 검색으로 얻은 컨텍스트를 두 프롬프트 모두에 전달할 필요가 없습니다.
이제 추론 프롬프트에는 검색으로 얻은 컨텍스트가 필요하지 않습니다. 따라서 병렬 처리를 적용해 검색 관련 프롬프트와 동시에 실행할 수 있습니다.

3단계: 구조화된 출력값 최적화하기
추론 프롬프트를 다시 살펴보겠습니다.

추론 결과를 담는 JSON을 자세히 보면 필드 이름 자체가 상당히 깁니다.
{
message_is_conversation_continuation: "True", // <-
number_of_messages_in_conversation_so_far: "1", // <-
user_sentiment: "Aggravated", // <-
query_type: "Hardware Issue", // <-
response_tone: "Validating and solution-oriented", // <-
response_requirements: "Propose options for repair or replacement.", // <-
user_requesting_to_talk_to_human: "False", // <-
};필드 이름을 줄이고 설명을 주석으로 옮기면 생성하는 토큰 수를 줄일 수 있습니다.
{
cont: "True", // whether last message is a continuation
n_msg: "1", // number of messages in the continued conversation
tone_in: "Aggravated", // sentiment of user query
type: "Hardware Issue", // type of the user query
tone_out: "Validating and solution-oriented", // desired tone for response
reqs: "Propose options for repair or replacement.", // response requirements
human: "False", // whether user is expressing want to talk to human
};
이 작은 변경으로 출력 토큰이 19개 줄었습니다. GPT-3.5에서는 몇 밀리초 정도만 단축될 수 있지만, GPT-4에서는 최대 1초까지 줄어들 수 있습니다.

모델의 출력이 길어지면 같은 방법으로 훨씬 더 큰 효과를 볼 수 있습니다.
한 걸음 더 나아가 JSON 필드 이름을 한 글자로 줄이거나 모든 값을 배열에 넣을 수도 있지만, 이 경우 응답 품질이 떨어질 수 있습니다. 이때도 가장 확실한 판단 방법은 테스트입니다.
예시 정리
고객 지원 봇 예시에 적용한 최적화를 정리하면 다음과 같습니다.

- 질의에 컨텍스트를 반영하는 단계와 검색 필요 여부를 확인하는 단계를 하나로 합쳐 요청 횟수를 줄였습니다.
- 새 프롬프트에는 파인튜닝한 더 작은 모델인 GPT-3.5를 사용해 토큰을 더 빠르게 처리했습니다.
- 어시스턴트 프롬프트를 둘로 나누고 추론에는 파인튜닝한 더 작은 모델인 GPT-3.5를 사용해 이 단계에서도 토큰을 더 빠르게 처리했습니다.
- 병렬 처리를 적용해 검색 필요 여부 확인과 추론을 동시에 수행했습니다.
- 추론 필드 이름을 줄이고 설명 주석을 프롬프트로 옮겨 생성하는 토큰 수를 줄였습니다.