Skip to content
게으른 엔지니어의 기술 블로그
Go back

LLM 요청 동시성을 2에서 4로 올렸더니 다른 버그 두 개가 나왔다

Read in English →

두 변수를 하나로 착각할 뻔했다 · 6/7편

5편에서 한/영 중복 감지 문제를 고쳤다. 그 직후 나온 질문은 전혀 다른 방향이었다 — “노트 정리(ingest) 처리 속도를 높이려고 병렬 처리 개수(ingestConcurrency)를 2에서 4로 올려도 괜찮을까?”

병렬 처리를 늘리면 품질이 떨어질까 — LLM API 병렬 요청과 순차 쓰기

ingestConcurrency는 원본 여러 개를 동시에 몇 개씩 AI에 보낼지 정하는 값이다(기본값 2). 숫자를 올리면 같은 작업을 더 짧은 시간에 끝낼 수 있지만, 여러 요청이 동시에 처리되는 과정에서 서로 영향을 주고받아 결과가 꼬일 위험은 없는지 먼저 확인이 필요했다.

코드를 열어보니 답이 명확했다. AI에게 요청을 보내는 부분은 여러 개를 동시에 쏘지만, 그 응답을 받아 실제로 노트 파일에 쓰는 부분은 언제나 한 번에 하나씩, 순서대로 처리하도록 짜여 있었다. 즉 여러 요청이 동시에 나가더라도 서로 다른 요청의 결과가 파일을 쓰는 시점에서 뒤섞일 방법 자체가 없었다. ingestConcurrency는 순수하게 “얼마나 빨리 끝내는가”만 좌우하는 손잡이지, 결과의 정확도나 노트를 얼마나 잘게 쪼갤지와는 구조적으로 무관하다는 뜻이었다.

실제로 4개로 올려서 전수 감사까지 해봤다 — 병렬 처리 속도와 품질

이론만으로 끝내지 않고 실제로 병렬 개수를 4로 올려 같은 원본 30개를 다시 돌렸다. 결과는 노트 225개, 소요 시간 17분 46초 — 같은 조건에서 병렬 2로 돌렸을 때(25분 35초)보다 확실히 빨라졌다. 원본 30개를 wiki 노트와 전수 대조한 결과 fidelity 문제는 사실상 0건이었다. 병렬 처리를 늘려도 품질에 영향이 없다는 가설이 실측으로 확인됐다.

여기서 끝났으면 좋았겠지만, 이 전수 감사를 진행하는 과정에서 전혀 다른 곳에서 버그 두 개가 새로 드러났다.

버그 1 — 임계값을 고쳤는데도 여전히 안 합쳐지는 쌍이 있었다 (중복 노트 감지 범위)

5편에서 한/영 중복 감지 문턱값을 0.70으로 낮췄는데도, 이번 감사에서는 “클로드 코드”(한글)와 “Claude Code”(영어)라는 명백히 같은 실체를 가리키는 노트 쌍이 여전히 합쳐지지 않고 있었다. 실제 임베딩 캐시로 이 쌍의 유사도를 다시 계산해보니 0.72~0.77 — 이미 새 문턱값(0.70)을 넘는 값이었다. 문턱값 문제가 아니라면 대체 무엇이 이 쌍을 걸러내고 있는 걸까.

원인은 노트의 “분류” 방식에 있었다. 이 플러그인은 노트를 만들 때 그 노트가 추상적인 개념(concept)인지 구체적인 실체(entity — 제품명·회사명·인물명 등)인지 함께 분류해 둔다. 그런데 5편에서 고친 중복 감지 로직은 concept로 분류된 노트끼리만 비교 대상으로 삼고 있었다. “Claude Code”, “Uber”, “Anthropic” 같은 제품·회사명은 전부 entity로 분류되기 때문에, 유사도 문턱값을 아무리 낮춰도 애초에 비교 대상 목록에 들어가지도 못했던 것이다.

사실 이 부분은 원래 설계 단계에서 “entity 쌍이 실제로 문제를 일으키는 걸 본 적이 없어서” 의도적으로 범위 밖에 뒀던 부분이었다. 이번에 정확히 그 실측 사례가 나온 셈이다. 비교 범위를 “concept든 entity든, 단 두 파일이 같은 분류일 때만 비교”로 넓히는 것으로 정리했다. concept과 entity를 서로 비교하는 건 계속 제외했다 — 특정 제품과 추상적인 개념은 애초에 같은 대상일 수 없기 때문이다.

버그 2 — 100% 영어 원문인데 소제목만 한글로 나왔다 (프롬프트 예시를 따라 한 LLM)

같은 감사 도중 더 이상한 현상도 하나 나왔다. 완전히 영어로만 쓰인 원문(C++ 관련 기술 제안 글)에서 나온 노트 네 개 전부, 본문은 정확한 영어인데 소제목만 한글로 달려 있었다(“배경과 목적”, “기술적 접근” 같은 식으로).

처음엔 이번 편 앞부분에서 다룬 병렬 처리(concurrency)를 의심했다. 동시에 처리되는 여러 요청 중 하나가 한국어 원문이었고, 그 언어가 영어 원문 처리에 섞여 들어간 게 아닐까 하는 가설이었다. 하지만 확인해보니 성립하지 않았다. AI에게 요청을 보내는 코드에는 동시 요청끼리 공유하는 상태가 전혀 없어서 이런 식으로 섞이는 게 구조적으로 불가능했고, 같은 원문을 병렬 2로 처리했던 이전 결과와 비교해도 그때는 이 문제가 아예 없었다.

진짜 원인은 프롬프트 안에 있었다. 노트 정리와 노트 분할·병합 프롬프트가 공통으로 쓰는 “본문을 소제목으로 구조화하라”는 지시문에, 형식을 보여주기 위한 예시로 “핵심 개념”, “실제 사례”, “한계점” 같은 한글 소제목이 박혀 있었다. 이 예시 자체는 순전히 “이런 식으로 나누라”는 형식 설명용이었을 뿐인데, 모델이 소제목 언어는 원문을 따르라는 별도의 명시적 규칙보다 프롬프트 안에 구체적으로 박힌 예시의 언어를 더 강하게 따라 한 것으로 보였다. 그래서 이 예시 문구는 구조를 보여주기 위한 것일 뿐이고, 실제 소제목의 언어는 예시와 무관하게 원문을 따르라는 단서를 붙여 바로 고쳤다.

정리

이 편에서 다룬 버그 둘 다, 원래 목적이었던 병렬 처리 검증과는 전혀 무관한 곳에서 튀어나왔다. 공통점이 있다면 둘 다 “전수 감사를 게을리하지 않았기 때문에” 잡을 수 있었다는 것 — 속도만 재고 끝냈다면 둘 다 그냥 지나쳤을 문제들이었다.

이제 남은 건 하나였다. 조사 초반부터 미뤄뒀던 그 질문 — 노트북 2의 실제 reasoning effort 값은 정말 무엇이었을까.

7편에서 이어진다.


Share this post:

두 변수를 하나로 착각할 뻔했다

  1. 1. LLM이 정리한 노트 189개가 어느 날 63개가 됐다
  2. 2. LLM 청크 크기를 낮추기만 했는데 fidelity 문제가 사라졌다
  3. 3. 실험을 다시 준비하던 중에 git 충돌 마커가 그대로 커밋됐다
  4. 4. OpenAI reasoning effort는 fidelity 문제의 범인이 아니었다
  5. 5. 한국어 노트와 영어 노트가 안 합쳐지는 이유를 임베딩 유사도로 확인했다
  6. 6. LLM 요청 동시성을 2에서 4로 올렸더니 다른 버그 두 개가 나왔다
  7. 7. 노트북 2의 OpenAI reasoning effort 값이 Medium으로 확인됐다