다중 독립 질문: 하나의 요청으로 배치할까, 여러 개로 나눌까? — LLM 동시 처리 분석

발행: (2026년 5월 3일 AM 09:19 GMT+9)
10 분 소요
원문: Dev.to

Source: Dev.to

위에 제공된 Source 링크 외에 번역할 텍스트가 포함되어 있지 않습니다. 번역을 원하는 본문을 제공해 주시면 한국어로 번역해 드리겠습니다.

어느 것이 더 빠른가?

짧은 답변:
질문을 여러 독립적인 병렬 요청으로 나누는 것이 거의 항상 더 빠릅니다.

왜? – LLM이 “글을 쓰는” 방식을 첫 원리에서 바라보기

단계일어나는 일지연에 미치는 영향
Autoregressive generation모델이 한 번에 하나의 토큰을 생성하고, 이를 프롬프트에 추가한 뒤 다음 토큰을 생성합니다.N 토큰 → N 번의 포워드 패스
Prefill전체 입력 프롬프트를 한 번 처리하여 KV‑cache를 구축합니다.입력 길이에 비례하는 선형 시간
Decode토큰이 순차적으로 (하나씩) 생성됩니다.전체 지연을 주도함

결과

  • 100‑토큰 답변 ≈ 100번의 추론 단계.
  • 500‑토큰 답변 ≈ 500번의 추론 단계.
  • 전체 출력 길이가 직접적으로 전체 지연을 결정합니다.

Source:

시나리오: 5개의 독립 질문, 각각 ~200 토큰

접근법 A – 모든 질문을 하나의 요청으로 결합

Please answer the following questions separately:
1. …
2. …
3. …
4. …
5. …

모델이 해야 할 일

  • Prefill: 연결 프롬프트(5개 질문 전체)를 처리합니다.
  • Decode: ≈ 5 × 200 = 1000 토큰을 순차적으로 생성합니다.
  • 추가 오버헤드:
    • 컨텍스트 전환(“이제 질문 3에 답변합니다”).
    • KV‑cache가 커져서 단계당 더 많은 어텐션 연산이 필요합니다.
    • 포맷팅/전환 텍스트가 토큰 수를 1000을 초과하게 만들기도 합니다.

예상 지연 시간 ≈ 1000 × (토큰당 생성 시간).

접근법 B – 5개의 독립 요청을 병렬로 전송

각 요청은 하나의 질문만 포함하고 ≈ 200 토큰을 생성합니다.

서버가 수행하는 일

  • Prefill: 각 요청마다 짧은 프롬프트를 사용합니다.
  • Decode: 5개의 별도 디코드 스트림이 동시에 실행됩니다(또는 함께 배치됩니다).
  • 최신 추론 엔진(vLLM, TensorRT‑LLM, TGI 등)은 연속 배치를 사용합니다: 하나의 GPU 포워드 패스가 5개의 요청 각각에 대해 한 토큰씩 동시에 출력할 수 있습니다.

예상 지연 시간 ≈ max(개별 요청 지연 시간) ≈ 200 × (토큰당 생성 시간).

직접 비교

접근법총 출력 토큰 수예상 지연 시간 (비교)
결합 요청~1000+~1000 디코드 단계(순차)
5개 병렬 요청각각 ~200~200 디코드 단계(병렬)

이론적 속도 향상: 약 5배(질문 수와 동일).

서버 측에서 병렬 요청이 더 빠른 이유

  1. Continuous Batching – GPU는 병렬 행렬 연산에 강점이 있다.

    • 5개의 짧은 요청 → 5‑way batched 전방 패스, 단계당 5개의 토큰을 생성.
    • 1개의 긴 요청 → 단일 시퀀스 전방 패스, 단계당 1개의 토큰만 생성.
  2. Higher GPU Utilization – 많은 짧은 시퀀스를 배치하면 GPU를 지속적으로 활용할 수 있지만, 단일 긴 시퀀스는 병렬 처리 능력을 낭비한다.

  3. Prefill vs. Decode

    • Combined: 프리필이 길어지고 디코드도 길어진다.
    • Split: 각 요청마다 짧은 프리필; 모든 프리필을 파이프라인하거나 동시에 실행할 수 있으며, 각 디코드도 짧다.

속도 그 이상의 품질 고려사항

이슈결합 프롬프트분할 요청
주의 집중 희석관련 없는 맥락이 답변 품질을 낮출 수 있음 (“중간에 빠짐”).단일 질문에 전념.
포맷 오류번호 매기기/누락 실수가 더 자주 발생.독립된 출력 → 더 깔끔한 포맷.
오류 전파Q2의 실수가 Q3‑Q5에 영향을 줄 수 있음(자동 회귀 관성).오류가 해당 요청에만 제한됨.

결합이 여전히 합리적일 수 있는 경우

상황이유
숨겨진 상관관계질문들이 관련되어 있는 경우(예: 같은 보고서의 일부), 공유된 컨텍스트가 일관성을 향상시킬 수 있다.
엄격한 API 호출 제한초당 3회 호출만 가능하다면 묶어야 할 수도 있다.
네트워크 지연이 지배적왕복 지연이 매우 높을 경우(예: > 2 초) 5개의 별도 호출보다 하나의 결합 호출이 더 빠를 수 있다. 최신 API는 보통 100‑300 ms이므로 드물다.
극히 짧은 답변각 답변이 한두 단어에 불과할 때, 프리필 오버헤드가 지배적이다; 단일 요청으로 중복 프리필을 줄일 수 있다.

빠른 실증 벤치마크 (Async Python)

import asyncio
import time
import aiohttp

API_URL = "https://api.your-llm.com/v1/completions"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}

async def ask_single(session, prompt):
    start = time.time()
    async with session.post(
        API_URL,
        json={"model": "gpt-4o-mini", "prompt": prompt, "max_tokens": 300},
        headers=HEADERS,
    ) as resp:
        await resp.json()          # ignore content, just wait for response
    return time.time() - start

async def benchmark():
    questions = [
        "Question 1: …",
        "Question 2: …",
        "Question 3: …",
        "Question 4: …",
        "Question 5: …",
    ]

    async with aiohttp.ClientSession() as session:
        # ---- Approach A: Combined -------------------------------------------------
        combined_prompt = "Please answer each question separately:\n" + "\n".join(questions)
        t_combined = await ask_single(session, combined_prompt)

        # ---- Approach B: Parallel -------------------------------------------------
        tasks = [ask_single(session, q) for q in questions]
        t_parallel = max(await asyncio.gather(*tasks))

        print(f"Combined request latency : {t_combined:.2f}s")
        print(f"Parallel requests latency: {t_parallel:.2f}s")
        print(f"Speed‑up factor          : {t_combined / t_parallel:.2f}×")

if __name__ == "__main__":
    asyncio.run(benchmark())

스크립트를 몇 번 실행하면 일반적으로 독립적인 중간 길이 답변에 대해 병렬 버전이 약 4‑5× 빠른 것을 확인할 수 있습니다.

TL;DR

  • Speed: 5개의 병렬 요청은 하나의 결합된 요청보다 약 5배 빠릅니다 (서비스가 이를 배치할 수 있다고 가정할 때).
  • Quality: 병렬 요청은 모델의 주의를 집중시켜 질문 간 오염을 방지합니다.
  • Exceptions: 질문이 실제로 상호 의존적이거나, 엄격한 속도 제한에 의해 제한을 받거나, 네트워크 지연 시간이 생성 시간을 압도할 때만 결합을 고려하세요.

Bottom line: 질문이 서로 관련이 없을 때는 별도의 동시 요청을 보내세요. 🚀

병렬 vs. 결합 요청

# Parallel execution
start = time.time()
await asyncio.gather(*[ask_single(session, q) for q in questions])
time_parallel = time.time() - start

print(f"Combined: {time_combined:.2f}s")
print(f"Parallel: {time_parallel:.2f}s")
print(f"Speedup: {time_combined / time_parallel:.1f}x")

실제로, 5개의 중간 복잡도 독립 질문은 일반적으로 병렬 요청으로 3–5배 속도 향상을 달성합니다.

비교

DimensionCombined requestSplit parallel requests
Generation speedSlow (sequential output of all answers)Fast (parallel generation, latency = slowest)
GPU utilizationLow (single‑sequence inference)High (batched parallel inference)
Answer qualityMay degrade (attention dilution)Better (isolated context)
API calls1N (one per question)
Best forRate‑limited / extremely short answersIndependent questions needing detailed answers

핵심 원리 (한 문장)
LLM의 자기회귀 메커니즘은 출력이 순차적임을 의미한다; 요청을 결합하면 모든 출력을 하나의 직렬 스트림으로 강제하고, 요청을 분할하면 서버 측 병렬성을 활용해 여러 출력을 동시에 생성한다—더 많은 동시 슬롯(공간)을 사용해 시간을 절약하는 고전적인 트레이드오프이다.

0 조회
Back to Blog

관련 글

더 보기 »