SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-13

자체 호스팅 LLM 동시 접속 시 속도 저하 해결 방법

사용자가 5명일 때 LLM 서버가 느려지는 이유는 기본 병렬 처리 설정이 1로 제한되어 있기 때문입니다. 배치 처리와 KV 캐시 최적화, 그리고 메모리 대역폭 한계를 이해하여 동시 요청 처리 성능을 개선하는 구체적인 방법을 설명합니다.

사용자가 늘어나면 자체 호스팅 LLM의 속도가 느려지는 이유는 무엇입니까?

자체 호스팅 LLM에서 동시 사용자 5명이 접속했을 때 지연이 발생하는 이유는 서버가 한 번에 하나의 응답만 생성할 수 있기 때문입니다. 나머지 4명은 대기열에서 기다리게 됩니다. Ollama 문서에서는 기본값에 대해 다음과 같이 명시합니다. OLLAMA_NUM_PARALLEL는 "각 모델이 동시에 처리할 최대 병렬 요청 수이며, 기본값은 1입니다." 시스템에 문제가 있는 것이 아닙니다. 5명 중 4명은 차례를 기다리고 있는 상태입니다.

이 문제를 해결하기 위해 더 큰 서버를 도입하는 경우는 드뭅니다. 해결책은 여러 요청을 동일한 순방향 패스(forward pass)로 모델에 밀어 넣는 서빙 엔진을 사용하고, 동시에 모든 사용자의 대화 내용을 유지할 수 있는 충분한 여유 메모리를 확보하는 것입니다. 두 가지 요소 모두 중요하며, 특히 후자가 실제 서비스의 한계를 결정합니다.

모든 요청이 거치는 두 단계

Prefill은 프롬프트 전체를 한 번에 읽고 이에 대한 어텐션 캐시를 생성합니다. 모든 프롬프트 토큰이 모델을 함께 통과하므로, prefill은 하나의 거대한 행렬 곱셈 연산이며 산술 처리량(arithmetic throughput)에 의해 제한됩니다. 반면 Decode는 답변을 한 번에 한 토큰씩 작성합니다. 각 토큰을 생성할 때마다 모델의 전체 가중치를 메모리에서 다시 읽어야 하지만, 단일 토큰에 수행되는 산술 연산량은 매우 적습니다. 따라서 Decode는 메모리 대역폭(memory bandwidth)에 의해 제한됩니다.

이러한 비대칭성이 배치 처리(batching)가 효과적인 이유입니다. 한 사용자를 위해 디코딩할 때 토큰당 약 5 GB의 가중치를 읽지만, 산술 연산 장치의 대부분은 유휴 상태로 남습니다. 이때 두 번째 요청을 추가하면 엔진은 동일한 5 GB를 한 번만 읽고 두 개의 토큰을 계산합니다. 두 번째 사용자를 처리하는 데는 거의 추가 시간이 들지 않습니다. 요청을 엄격하게 순차적으로 처리하면 이러한 효율성을 잃게 됩니다.

사용자가 체감하는 성능은 두 가지 수치로 설명됩니다. TTFT(time to first token)는 대기열 시간과 prefill 시간을 합친 것입니다. ITL(inter-token latency)은 스트리밍되는 토큰 사이의 간격이며, 이는 decode 단계에 의해 결정됩니다. 서버가 느리다면 보통 이 두 단계 중 하나에서 문제가 발생하는 것이며, 해결 방법 또한 서로 다릅니다.

정적 배칭은 가장 느린 응답을 기다리게 만듭니다

정적 배칭은 가장 단순한 방식이며, 애플리케이션 코드에서 직접 요청을 그룹화할 때 나타나는 형태입니다. 엔진은 N개의 요청을 모아 한꺼번에 실행하며, 그룹 내에서 가장 긴 생성 작업이 끝날 때까지 모든 슬롯을 점유합니다.

1,200 토큰 요약을 요청한 사용자 한 명 때문에 한 줄짜리 답변 4개가 배치 내에 묶이게 됩니다. 배치 내에서 가장 느린 작업이 완료되기 전까지는 어떤 슬롯도 해제되지 않기 때문입니다.

이로 인해 두 가지 비용이 발생합니다. 완료된 시퀀스가 유용한 연산을 수행하지 않으면서 슬롯을 계속 점유하므로, 출력 길이가 달라질 때마다 실질적인 처리량(throughput)이 감소합니다. 특히 채팅 출력 길이는 변동 폭이 매우 큽니다. 배치가 구성된 직후에 도착한 요청은 전체 배치가 비워질 때까지 기다려야 프리필(prefill)을 시작할 수 있습니다. 즉, 해당 요청의 TTFT(Time To First Token)가 다른 사용자의 긴 글에 의해 결정되는 것입니다.

Continuous batching은 각 토큰마다 요청을 수락하고 종료합니다

Continuous batching은 단일 디코딩 단계 수준에서 스케줄링을 수행합니다. 각 단계가 끝날 때마다 스케줄러는 정지 토큰을 방출한 시퀀스를 제거하고, 빈 슬롯에 대기 중인 요청을 수락합니다. 40단계에서 종료되는 응답은 배치가 끝날 때가 아니라 40단계에서 즉시 슬롯을 비웁니다.

이는 특이한 방식이 아닙니다. llama-server-cb, --cont-batching을 "continuous batching(동적 배치라고도 함) 활성화 여부(기본값: enabled)"로 정의하며, vLLM은 이 개념을 중심으로 구축되었습니다. Ollama 또한 병렬 요청을 처리합니다. 기본 설정은 이 수를 1로 제한할 뿐이며, 많은 사용자가 자신의 하드웨어로는 동시성 처리가 불가능하다고 결론짓는 이유는 설정에서 이를 거부했기 때문입니다.

공개된 continuous batching 결과는 일반적으로 여유 연산 자원과 수십 GB의 캐시 메모리를 갖춘 데이터센터용 카드에서 측정됩니다. 해당 결과의 형태는 귀하의 장비에도 동일하게 적용되지만, 그 규모는 그렇지 않으며 그 이유는 아래의 메모리 섹션에서 설명합니다.

Prefill과 decode는 동일한 연산 자원을 두고 경쟁합니다

4개의 응답이 스트리밍되는 도중에 새로운 요청이 들어오면, 해당 요청의 프롬프트를 먼저 prefill해야 하며 prefill은 연산 집약적인 작업입니다. 스케줄러가 이 prefill 작업에 별도의 단계를 할당하면, 스트리밍 중인 4명의 사용자는 그동안 토큰을 하나도 받지 못합니다. 프롬프트가 길 경우 모든 열린 창에서 눈에 띄는 멈춤 현상이 발생합니다. 누군가 전송 버튼을 누를 때마다 서버가 멈칫한다고 말할 때의 그 끊김 현상이 바로 이것입니다.

Chunked prefill은 긴 프롬프트를 여러 조각으로 나누어 각 조각을 실행 중인 decode 작업과 동일한 단계에서 섞어서 처리합니다. vLLM 튜닝 가이드는 이 트레이드오프를 명확히 설명합니다. chunk 예산이 작을수록 "decode를 느리게 만드는 prefill 작업이 줄어들어 ITL(Inter-Token Latency)이 개선"되지만, 값이 클수록 "배치 내에서 더 많은 prefill 토큰을 처리할 수 있어 TTFT(Time To First Token)가 개선"됩니다. 즉, 응답 시작을 기다리는 사용자와 텍스트가 스트리밍되는 것을 지켜보는 사용자 중 누구의 경험을 우선할지 선택해야 합니다.

이 현상이 얼마나 심각한지는 프롬프트 길이에 따라 결정됩니다. 6,000 토큰의 프롬프트에 200 토큰의 답변이 달리는 경우, 6,000 토큰의 prefill 작업이 200번의 decode 단계와 맞서게 됩니다. RAG(Retrieval-augmented generation) 기반 채팅이나 긴 시스템 프롬프트는 모두 이러한 상황을 유발하므로, prefill은 더 이상 무시할 수 없는 수준이 되어 사용자가 대기하게 만드는 주원인이 됩니다. 긴 프롬프트의 앞부분이 반복되는 경우 Prefix caching이 도움이 됩니다. vLLM은 --enable-prefix-caching 기능을 제공하며, 이는 요청마다 프롬프트 접두사를 재계산하는 대신 공유된 접두사에 대해 캐시를 재사용합니다.

가장 먼저 고갈되는 메모리는 KV 캐시입니다

활성 대화의 모든 토큰은 모델의 각 레이어에 키 벡터와 값 벡터를 남깁니다. 이것이 KV 캐시(key/value cache)이며, 디코딩 과정에서 새로운 토큰마다 전체 프롬프트를 다시 계산하지 않게 해주는 핵심 요소입니다. 토큰당 캐시 크기는 모델의 구조에 따라 고정됩니다. 즉, 2(키 1개, 값 1개)에 레이어 수, 키/값 헤드 수, 헤드 차원, 값당 바이트 수를 곱한 값입니다. 이 수치들은 모델의 config.json에서 확인할 수 있습니다.

한 번 계산해 보면 메모리 한계가 더 이상 미스터리가 아닙니다. 36개 레이어, 8개 키/값 헤드, 128 헤드 차원을 가진 일반적인 8B 모델을 16비트로 캐싱할 경우, 토큰당 2 36 8 128 2 바이트가 소모됩니다. 이는 147,456 바이트, 즉 약 144 KiB입니다. 따라서 8,192 토큰 대화 하나당 약 1.2 GB의 캐시가 필요합니다. 5개의 대화라면 모델 가중치 외에 약 6 GB가 추가로 필요하며, 이것이 수용 가능한 사용자 수를 결정하는 실질적인 해답입니다.

동시성은 컨텍스트를 배가시키며, 도구들은 이를 명시적으로 밝히고 있습니다. Ollama의 FAQ는 다음과 같이 설명합니다: "특정 모델에 대한 병렬 요청 처리는 병렬 요청 수만큼 컨텍스트 크기를 증가시킵니다. 예를 들어, 4개의 병렬 요청이 있는 2K 컨텍스트는 8K 컨텍스트와 추가적인 메모리 할당을 유발합니다." 필요한 RAM은 OLLAMA_NUM_PARALLELOLLAMA_CONTEXT_LENGTH를 곱한 값에 비례합니다. llama-server에서 -c로 요청한 컨텍스트는 -np 슬롯 전체에 분할되므로, 슬롯 수를 늘리는 것만으로도 각 요청이 가질 수 있는 컨텍스트 크기가 줄어듭니다. 추측하지 말고 시작 로그에서 슬롯당 컨텍스트 크기를 확인하십시오.

vLLM은 대신 미리 할당(preallocate)하는 방식을 사용합니다. --gpu-memory-utilization(기본값 0.92)은 "모델 실행기에 사용할 GPU 메모리의 비율"입니다. 가중치를 로드하고 남은 모든 메모리는 페이징된 KV 풀이 되며, 이 풀이 부족해지면 스케줄러는 요청을 실패시키는 대신 해당 요청을 제거(evict)합니다:

WARNING 05-09 00:49:33 scheduler.py:1057] Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.

vLLM의 V1 엔진에서 기본 선점(preemption) 모드는 RECOMPUTE이므로, 제거된 요청은 캐시를 버리고 다시 진입할 때 프리필(prefill) 과정을 다시 수행합니다. 이 작업은 두 번 수행됩니다. 문서에서는 "선점과 재계산이 종단 간 지연 시간에 악영향을 미칠 수 있음"을 경고하며, 이 로그 라인은 평균 지연 시간은 정상인데 특정 사용자만 유독 오래 기다려야 했던 이유를 설명하는 가장 좋은 근거입니다. disable_log_stats=False을 설정하여 누적 횟수를 기록하거나, vLLM이 노출하는 Prometheus 메트릭에서 선점 카운터를 확인하십시오.

동시 사용자 2명, 5명, 20명일 때의 변화

사용자 2명. 캐시 여유가 있는 GPU에서는 거의 영향이 없습니다. 두 번째 디코딩 스트림이 첫 번째 스트림에 얹혀서 처리되므로 추가 시간이 거의 들지 않기 때문입니다. 반면 4~8 GB RAM을 갖춘 CPU 전용 VPS에서는 비용이 발생합니다. 두 스트림이 소수의 vCPU와 RAM 대역폭을 공유하므로, 각 사용자는 초당 토큰 처리량이 절반으로 줄어드는 것을 체감하게 되며, 훨씬 적은 자원 예산 안에서 캐시 요구량은 두 배로 늘어납니다.

사용자 5명. 기본 설정만으로는 부족해지며 대기열 문제가 시작되는 지점입니다. OLLAMA_NUM_PARALLEL 값이 1일 때, 4명은 긴 답변을 요청한 사람이 끝날 때까지 기다려야 하며, 자신의 차례가 오면 정상적인 속도를 경험합니다. 병렬 처리 수를 늘리면 문제의 양상이 바뀝니다. 8K 컨텍스트를 사용하는 슬롯 5개는 총 40K 토큰의 캐시 공간을 필요로 합니다. 이 용량이 VRAM에 들어가지 않으면 엔진은 레이어를 시스템 RAM으로 오프로드하며, RAM에도 들어가지 않으면 시스템은 스왑(swap)을 발생시켜 초당 토큰 처리량이 급격히 하락합니다.

사용자 20명. 채팅 UI를 사용하는 사람 20명이 항상 20개의 동시 요청을 보내는 것은 아닙니다. 하드웨어를 구매하기 전에 이 점을 이해하는 것이 가장 중요합니다. 사용자는 답변을 읽고 다음 질문을 하기까지 20~60초 동안 생각하므로, 세션의 대부분은 유휴 상태입니다. 하지만 20개의 에이전트나 20개의 문서 요약 작업은 유휴 시간 없이 20개의 실제 스트림이 동시에 돌아가는 상황을 의미합니다. 이는 완전히 다른 사양의 장비가 필요한 영역입니다.

사용자가 동시 접속 상태입니까, 아니면 단순히 로그인만 되어 있습니까?

무엇이든 규모를 산정하기 전에 처리 중인 요청(in flight) 수를 계산하십시오. 산술은 간단합니다. 처리 중인 요청 수는 사용자 수에 턴당 생성에 소요되는 초를 곱하고, 턴 사이의 간격(초)으로 나눈 값입니다.

  1. 먼저 자신의 단일 스트림 속도를 측정하십시오. 프리필(prefill)과 디코딩(decode)을 모두 포함해야 합니다. 다른 사람의 카드에서 수치를 빌려오지 마십시오. 자신의 장비에서 초당 토큰 수 측정하기를 수행하고 얻은 결과를 사용하십시오.
  2. 듀티 사이클(duty cycle)을 추정하십시오. 20명의 채팅 사용자가 있고, 턴당 12초 동안 생성하며, 90초마다 한 번씩 턴이 발생한다면 20 * 12 / 90을 계산하여 약 2.7개의 요청이 동시에 처리 중인 것으로 볼 수 있습니다.
  3. 슬롯 수를 그보다 약간 높게 설정한 다음 메모리 용량과 비교하십시오. '슬롯 수 * 요청당 컨텍스트'가 실제 보유한 캐시 토큰 용량 내에 들어와야 합니다.
  4. 큐를 짧게 유지하여 오버플로가 발생할 경우 즉시 눈에 띄게 실패하도록 하십시오.

사용 가능한 캐시 토큰은 가중치를 제외한 여유 메모리를 위 섹션에서 계산한 토큰당 비용으로 나눈 값입니다. 16비트 정밀도로 8B 모델을 실행하는 24 GB 카드는 가중치에 약 16 GB를 사용하며, 기본 활용도 기준으로 약 6 GB의 사용 가능한 캐시를 가집니다. 이는 8K 대화 5개 정도에 해당합니다. 더 많은 대화를 수용하려면 요청당 컨텍스트를 줄이거나 캐시를 8비트로 저장하십시오(llama-server--cache-type-k q8_0을 사용합니다). 두 방법 모두 무언가를 포기하는 대신 동시성을 확보하는 것이며, 하드웨어에 비용을 투자하기 전에 해당 거래의 실질적인 의미를 읽어보는 것이 좋습니다: GPU VPS가 API 토큰 대비 손익분기점을 넘는 지점.

Ollama의 기본 설정이 충분하지 않은 경우

systemd로 관리되는 데몬에는 셸에서 export한 환경 변수가 전달되지 않으므로, 서비스 유닛을 통해 병렬 처리 개수를 늘려야 합니다.

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_MAX_QUEUE=64"
sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment
ollama ps

systemctl show 명령을 실행하면 방금 설정한 세 가지 변수가 출력되어야 합니다. 만약 출력되지 않는다면 드롭인 설정이 저장되지 않은 것이며, 이후의 작업은 아무런 효과가 없습니다. ollama ps 명령은 로드된 모델을 나열하는데, 이때 표시되는 크기는 가중치 자체보다 큽니다. 이는 8,192 토큰을 처리하는 슬롯 4개가 32,768 토큰의 캐시를 별도로 점유하기 때문입니다. 모든 모델을 GPU에 올릴 것으로 예상했음에도 PROCESSOR 열에 모델의 일부가 CPU에 할당된 것으로 나타난다면, 그래픽 카드 메모리보다 많은 캐시를 요청한 것입니다. 두 숫자 중 하나를 낮추십시오.

대기열(queue) 기본값은 다시 고려해 볼 필요가 있습니다. Ollama는 최대 OLLAMA_MAX_QUEUE개의 요청을 대기시키며, "기본값은 512"입니다. 이 수치를 넘어서면 서버는 "과부하 상태임을 나타내는 503 오류"를 반환합니다. 4개의 요청을 동시에 처리하는 서버에서 512개 깊이의 대기열을 유지하는 것은 지킬 수 없는 약속입니다. 300번째 순서에 있는 클라이언트는 자신의 차례가 오기 훨씬 전에 타임아웃되기 때문입니다. 대기열을 짧게 설정하면 애플리케이션이 재시도하거나 오류를 보고할 수 있으며, 이는 응답 없이 멈춰 있는 상태보다 훨씬 낫습니다.

실제로 테스트하십시오. 두 개의 터미널에서 동시에 요청을 보내고 두 응답을 모두 확인하십시오. 첫 번째 요청이 끝날 때까지 두 번째 요청에서 아무런 반응이 없다면, 병렬 처리 설정이 적용되지 않은 것입니다.

실제 서빙 엔진이 비용 효율을 내는 시점

vLLM은 GPU에 여유 자원이 있고 4개 이상의 요청이 동시에 처리되는 상황에서 추가적인 설정 비용을 상쇄합니다. vLLM의 스케줄러는 토큰 단위로 작동하며, 캐시를 페이징하여 빈 조각을 재사용하고, 남는 VRAM을 유휴 상태로 두는 대신 동시성 확보에 활용합니다. 2026년 8월 기준, 문서화된 설치 및 실행 명령어는 다음과 같습니다.

uv pip install vllm --torch-backend=auto
vllm serve Qwen/Qwen2.5-1.5B-Instruct
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "messages": [{"role": "user", "content": "Say hello."}]
    }'

choices 배열이 포함된 응답은 서버가 정상 작동 중이며 모델이 로드되었음을 의미합니다. 부하가 걸린 상황에서 중요한 설정값은 두 가지입니다. --max-num-seqs는 "단일 반복에서 처리할 최대 시퀀스 수"를 의미하며, --max-num-batched-tokens은 "단일 반복에서 처리할 최대 토큰 수"를 의미합니다. 전자는 동시성을 제한하고, 후자는 앞서 설명한 청크 단위 프리필(chunked prefill) 예산을 결정합니다.

동시 요청이 4개 미만이거나 지원되는 GPU가 없는 환경에서 vLLM을 사용하는 것은 복잡도만 높일 뿐 이득이 거의 없습니다. vLLM은 CUDA 기반 카드를 요구하며 시작 시 대부분의 메모리를 점유하므로, 4~8 GB 용량의 VPS에서는 적절하지 않습니다. 이러한 환경에서는 더 짧은 컨텍스트를 가진 작은 모델과 직접 제어 가능한 큐를 사용하는 것이 좋습니다. Ollama와 vLLM의 서빙 엔진 차이에서 선택 기준을 상세히 다루며, VPS에서 Qwen 3 8B 실행하기에서는 사용자를 추가하기 전 중형 모델이 요구하는 자원 수준을 확인할 수 있습니다.

통념이 감추고 있는 트레이드오프

Continuous batching은 전체 처리량(throughput)을 높이며, 대기 중인 요청이 더 빨리 시작되므로 일반적으로 중간 지연 시간(median latency)도 개선합니다. 하지만 꼬리 지연 시간(tail latency)은 반대 방향으로 움직이며, 이 절반의 진실은 거의 언급되지 않습니다.

단계마다 시퀀스가 추가될 때마다 작업량이 조금씩 늘어나므로, 배치가 채워질수록 모든 사용자의 ITL(Inter-Token Latency)은 상승합니다. 새로 도착한 요청의 prefill은 스트리밍 중인 사용자가 사용했을 단계의 일부를 점유합니다. 캐시 압박이 발생하면 스케줄러는 선점(preempt)을 수행하며, 이로 인해 절반쯤 생성된 요청은 다시 prefill 시작 단계로 돌아갑니다.

채팅 UI는 평균이 아닌 꼬리 지연 시간을 보여줍니다. 문장 중간에 2초간 멈추는 스트림은 전체 완료 시간이 짧더라도 사용자에게는 고장 난 것처럼 보입니다. 예상되는 부하 상황에서 p95 TTFT와 p95 ITL을 측정하십시오. 초당 토큰 평균값은 사용자 경험을 설명하는 지표가 아니라 용량 산정용 수치로 취급해야 합니다.

실무적인 설정은 여기서 도출됩니다. 메모리가 허용하는 수준보다 동시성(concurrency)을 약간 낮게 제한하여 엔진이 선점을 수행할 필요가 없도록 만드십시오. 짧고 예측 가능한 대기열이 스래싱(thrashing)이 발생하는 깊은 배치보다 낫습니다. 4초를 기다린 뒤 매끄럽게 스트리밍되는 사용자가, 즉시 시작했다가 두 번 멈추는 사용자보다 더 만족하기 때문입니다.

속도가 느릴 때 확인해야 할 사항

사용자별 응답은 정상이지만 대기 시간이 긴 경우. 이는 속도 문제가 아니라 큐(queue) 문제입니다. 먼저 병렬 처리 설정을 확인하십시오. 모델은 한 번에 하나의 요청을 올바르게 처리하고 있습니다.

Ollama에서 HTTP 503 오류가 발생하는 경우. 큐가 가득 찬 상태입니다. 서버가 실제로 용량 한계에 도달했거나, 부하를 분산하기 위해 OLLAMA_MAX_QUEUE 설정이 의도적으로 낮게 지정된 경우입니다. 이는 의도한 대로 동작하는 것입니다.

CPU 서버에서 부하가 걸리면 초당 토큰 처리량(tokens per second)이 급락하는 경우. 부하가 발생하는 동안 vmstat 1을 실행하십시오. siso 열의 값이 0이 아니라면 시스템이 스왑(swapping)을 수행 중이며, 매 토큰마다 디스크에서 가중치를 읽어오고 있다는 의미입니다. 설정 변경으로는 해결할 수 없습니다. 모델 크기를 줄이거나 슬롯 수를 줄이십시오.

10명 중 1명의 사용자만 다른 사용자보다 훨씬 오래 대기하는 경우. vLLM 로그에서 preempted을 검색하십시오. 일반적으로 선점(preemption)과 그에 따른 재계산이 원인이며, 이는 허용된 컨텍스트 길이에 비해 캐시가 과도하게 할당되었음을 의미합니다.

서버가 유휴 상태인데도 TTFT(Time To First Token)가 나쁜 경우. 이는 동시성 문제가 아니라 프리필(prefill) 문제입니다. 긴 프롬프트는 첫 번째 토큰이 나타나기까지 실제 시간이 소요되므로, 하드웨어를 확인하기 전에 프롬프트 크기와 프리픽스 캐싱(prefix caching)을 먼저 살펴보십시오.

FAQ

왜 두 번째 사용자가 접속하면 자체 호스팅한 LLM이 느려지나요?

대부분의 경우 속도가 느려지는 것이 아니라 대기열이 발생하는 것입니다. Ollama는 기본적으로 OLLAMA_NUM_PARALLEL를 1로 설정하여 배포하므로, 두 번째 요청은 첫 번째 요청이 마지막 토큰을 생성할 때까지 기다려야 합니다. 한 사용자가 스트리밍하는 동안 다른 사용자가 대기하게 하여 두 경우를 구분하십시오. 만약 토큰 생성이 시작된 후 초당 토큰 처리 속도가 정상이라면 대기열 문제이므로 병렬 처리 수를 늘려 해결할 수 있습니다. 반면 두 스트림 모두 절반의 속도로 동작한다면 메모리 대역폭을 실제로 공유하고 있는 것이며, 이는 하드웨어적인 한계입니다.

소형 GPU 한 대로 몇 명의 동시 사용자를 처리할 수 있나요?

사용자 수가 아닌 메모리 용량을 기준으로 계산하십시오. 먼저 모델 가중치를 고려하고, 그다음 KV 캐시를 계산합니다. KV 캐시는 (2 레이어 수 키/값 헤드 수 헤드 차원 바이트 수)로 계산되며, 토큰당 그리고 활성 대화당 소모됩니다. 36개 레이어, 8개 키/값 헤드, 128 헤드 차원을 가진 일반적인 8B 모델은 16비트 기준 토큰당 약 144 KiB를 소모하므로, 8,192 토큰 대화에는 약 1.2 GB가 필요합니다. 해당 모델을 16비트로 올린 24 GB 카드에는 캐시용으로 약 6 GB가 남으며, 이는 전체 컨텍스트를 사용할 경우 약 5개의 대화를, 컨텍스트를 줄일 경우 더 많은 대화를 처리할 수 있음을 의미합니다.

연속 배치(continuous batching)를 사용하면 각 사용자의 응답이 느려지나요?

요청이 전체 배치가 완료될 때까지 기다릴 필요가 없으므로 중앙값 지연 시간은 보통 개선됩니다. 하지만 꼬리 지연 시간(tail latency)은 나빠집니다. 시퀀스가 추가될 때마다 모든 디코딩 단계에서 작업량이 늘어나고, 새로 도착한 요청의 프리필(prefill) 작업이 스트리밍 중인 사용자의 처리 시간을 일부 점유하며, 선점된 요청은 프리필을 두 번 수행해야 하기 때문입니다. 평균값은 지연을 숨기므로, 채팅창에서 체감되는 멈춤 현상을 정확히 파악하려면 평균이 아닌 p95 토큰 간 지연 시간을 측정하십시오.

OLLAMA_NUM_PARALLEL을 높여야 할까요, 아니면 vLLM으로 전환해야 할까요?

먼저 병렬 처리 수를 높이십시오. 이는 비용이 들지 않고 드롭인(drop-in) 파일 하나로 설정할 수 있으며, 긴 답변 하나를 기다리는 4명의 사용자가 대기열에 쌓이는 일반적인 문제를 해결합니다. 메모리가 한계점입니다. 병렬 요청은 유지해야 할 컨텍스트를 곱절로 늘리므로 레이어가 CPU로 밀려나지 않는지 확인하십시오. VRAM 여유가 있고 실제로 4개 이상의 요청이 동시에 처리되는 상황이라면 vLLM으로 전환하십시오. 페이징된 캐시와 토큰 단위 스케줄링이 비용보다 더 큰 이득을 제공하는 시점이 바로 그때입니다.

CPU 코어를 늘리면 LLM 서버 속도가 빨라지나요?

사용자가 가장 크게 체감하는 부분에서는 그렇지 않습니다. 디코딩은 토큰마다 메모리에서 모델 전체를 읽어오므로 RAM 대역폭에 제한을 받으며, 대역폭이 포화 상태에 이르면 코어를 늘려도 도움이 되지 않습니다. 프리필은 코어 수에 비례하여 확장되므로 코어가 많을수록 긴 프롬프트의 첫 토큰 생성 시간은 단축됩니다. 4~8 GB RAM의 VPS에서는 메모리 용량이 병목 현상의 주원인이므로, vCPU를 늘리는 것보다 더 작은 모델을 사용하거나 컨텍스트 길이를 줄이는 것이 실질적인 해결책입니다.