llama.cpp b11871은 지나치게 깊게 중첩된 JSON 요청을 처리하기 전에 거절하도록 바꿨다. 서버를 운영하는 개발자는 요청 하나의 오류 때문에 전체 서버가 종료되는 상황을 줄일 수 있고, 깊이가 기준을 넘으면 HTTP 400 응답을 받는다.

1. 재귀 처리가 서버를 멈추게 한 원인

문제는 JSON을 읽은 뒤 안쪽 구조를 따라 같은 함수를 거듭 호출하는 재귀 처리에서 발생했다. 겹겹이 쌓인 구조를 따라가다가 HTTP 요청을 처리하는 데 쓰는 메모리 공간인 스택이 소진되면 서버 전체가 종료될 수 있었다.

릴리스 노트에 따르면 입력 형식을 정의하는 도구 스키마 요청은 macOS에서 몇백 단계부터, Linux에서 약 1만 단계부터 서버 종료를 일으켰다.

변경 제안서(PR)의 아래 코드는 깊이를 1000으로 설정한 도구 정의를 서버에 보내는 재현 절차다. 실행하면 기존 서버를 종료시킬 수 있는 예시로, macOS에서는 해당 깊이에서 종료됐으며 Linux에서는 깊이를 30000으로 높이라고 설명한다.

llama-server -hf LiquidAI/LFM2.5-VL-450M-GGUF -hff LFM2.5-VL-450M-F16.gguf --port 8080 &
python3 -c "d=1000; s='{\"type\": \"array\", \"items\": '*d+'{\"type\": \"string\"}'+'}'*d; print('{\"messages\": [{\"role\": \"user\", \"content\": \"hi\"}], \"max_tokens\": 1, \"tools\": [{\"type\": \"function\", \"function\": {\"name\": \"f\", \"description\": \"d\", \"parameters\": {\"type\": \"object\", \"properties\": {\"x\": '+s+'}}}}]}')" > deep.json
curl -s localhost:8080/v1/chat/completions -H 'Content-Type: application/json' --data-binary @deep.json

2. 128단계를 넘기면 처리 전에 거절

변경은 파싱, 문자열을 구조로 읽는 처리, 한 JSON의 깊이를 먼저 확인해 이후 재귀 처리가 과도하게 진행되지 않도록 하는 방식이다. PR의 구현 설명에 따르면 검사 함수 자체의 재귀 호출도 최대 129회로 제한된다.

릴리스 노트는 128단계를 넘는 입력에 HTTP 400을 돌려준다고 설명한다. 호출자는 서버가 종료됐는지 확인하는 대신, 깊이가 과도한 요청을 거절했다는 응답을 받을 수 있다.

검사 대상은 채팅 요청의 도구 정의와 도구 호출 인자, 도구에 전달하는 실행값, 템플릿 변수, 틀에 값을 끼워 넣는 항목, 스키마다. 완료 요청의 스키마와 상태 관리 요청의 본문 전체도 검사한다. 깊이가 너무 깊은 gemma4 도구 결과는 구조화된 결과로 해석하지 않고 텍스트로 남긴다.

이로써 정상 범위의 요청 흐름을 유지하면서 깊은 입력이 서버 스택을 소진하는 경로를 차단한다. 반면 128단계를 넘는 JSON은 더 이상 처리되지 않아 해당 깊이에 의존하는 호출자는 요청 구조를 줄여야 한다.

3. 기존 코드와 적용본의 응답 차이

아래는 PR 작성자가 macOS M3 Ultra에서 변경 전 코드와 적용본을 비교한 결과다. 200은 정상 처리, 400은 깊이 초과로 거절된 응답이며, 적용본은 측정한 요청 유형에서 130단계부터 400을 반환했다.

표: 중첩 단계별 HTTP 응답

조건: macOS M3 Ultra, PR의 변경 전·적용본 비교. 상태 관리는 d1-omni, 도구 호출 인자는 문자열 입력.

단위: HTTP 상태 코드. —는 서버 종료로 응답 없음.

기존 코드와 적용본의 응답 차이
중첩 단계변경 전 상태 관리변경 전 도구 파라미터변경 전 응답 형식변경 전 도구 호출 인자적용본 전체
100200200200200200
120200200200200200
130200200200200400
700500——200400
1000500——200400
2000————400
10000————400
30000————400
1000000————400

변경 전에도 100단계와 120단계의 응답은 적용본과 같았다. 상태 관리 요청의 700단계와 1000단계에서 기록된 500은 서버 종료가 아니라 배치 크기 오류, 한 번에 처리할 묶음 크기 오류다.

PR은 Linux에서 기존 스키마 요청은 1만 단계, 상태와 인자 요청은 3만 단계부터 종료됐으며, 변경 적용본은 130단계부터 400을 돌려줬다고 덧붙였다.

4. 검사 부담과 남은 범위

PR 작성자는 M3 Max에서 검사 비용을 측정했다. 0.33MB 채팅 요청은 0.02ms로 JSON 파싱 시간 1.4ms보다 짧았고, 10MB 요청은 0.54ms(파싱 35ms)였다. 정수 100만 개 배열은 3.8ms(파싱 41ms)였다. 이 수치는 PR의 해당 환경 측정이며, 입력이 커지면 검사 시간도 늘지만 보고된 세 사례에서는 파싱 시간보다 작았다.

PR은 macOS에서 일부 템플릿이 128단계 미만에서도 스택을 넘을 수 있다고 밝혔다. 어시스턴트 메시지로 끝나는 요청에 모델 응답 앞에 미리 넣는 사전 입력과 중첩 이외의 원인으로 발생하는 JSON 스키마 변환의 재귀 문제도 이번 변경 대상에서 제외했다.

b11871의 핵심은 깊은 입력의 실패를 서버 전체 종료에서 요청 단위 거절로 옮겨 운영 부담을 줄인 데 있다.