[Open WebUI] KaTeX 유니코드 정규식 사전 컴파일로 마크다운 렌더링 87% 병목 제거
PR 링크: open-webui/open-webui#22196 상태: Merged | 변경: +25 / -36
들어가며
Open WebUI의 마크다운 렌더링에서 수학 수식을 감지하는 katexStart 함수가 렌더링 시간의 약 87%를 차지하고 있었습니다. 원인은 유니코드 속성 이스케이프(\p{Script=Han}, \p{Script=Hangul} 등)를 포함한 정규식을 매 호출마다 새로 컴파일하고 있었기 때문입니다. 유니코드 정규식 컴파일은 일반 정규식 대비 훨씬 비용이 큽니다.
핵심 코드 분석
정규식 사전 컴파일
Before:
const f = index === 0 ||
indexSrc.charAt(index - 1).match(
new RegExp(`[${ALLOWED_SURROUNDING_CHARS}]`, 'u')
);
After:
// Pre-compile the surrounding character regex once at module load time.
const ALLOWED_SURROUNDING_CHARS_REGEX = new RegExp(
`[${ALLOWED_SURROUNDING_CHARS}]`, 'u'
);
// 함수 내에서는 사전 컴파일된 정규식 사용
if (i === 0 || ALLOWED_SURROUNDING_CHARS_REGEX.test(src.charAt(i - 1))) {
return i;
}
katexStart 함수 재작성
기존에는 구분자 목록을 반복하면서 indexOf와 정규식 매칭을 중첩 수행했지만, 변경 후에는 문자 단위로 순회하면서 $와 \ 문자만 확인하는 방식으로 변경되었습니다.
Before:
function katexStart(src, displayMode: boolean) {
let indexSrc = src;
while (indexSrc) {
// 각 구분자에 대해 indexOf 반복
for (const delimiter of DELIMITER_LIST) { ... }
// 매번 new RegExp 컴파일
indexSrc.charAt(index - 1).match(new RegExp(`[${...}]`, 'u'));
// 문자열 잘라내기 반복
indexSrc = indexSrc.substring(...);
}
}
After:
function katexStart(src, displayMode: boolean) {
for (let i = 0; i < src.length; i++) {
const ch = src.charCodeAt(i);
if (ch === 36 /* $ */) {
if (displayMode && src.charAt(i + 1) !== '$') continue;
if (i === 0 || ALLOWED_SURROUNDING_CHARS_REGEX.test(src.charAt(i - 1))) {
return i;
}
} else if (ch === 92 /* \ */) {
// 빠른 문자 검사로 불필요한 정규식 매칭 건너뛰기
const next = src.charAt(i + 1);
if (displayMode) {
if (next !== '[' && next !== 'b') continue;
} else {
if (next !== '(' && next !== 'c' && next !== 'p') continue;
}
if (i === 0 || ALLOWED_SURROUNDING_CHARS_REGEX.test(src.charAt(i - 1))) {
return i;
}
}
}
}
왜 이게 좋은가
- 정규식 컴파일 비용 제거:
\p{Script=Han}등 유니코드 속성 이스케이프를 포함한 정규식은 컴파일 비용이 매우 높습니다. 모듈 로드 시 한 번만 컴파일하여 이 비용을 완전히 제거했습니다. - 알고리즘 개선: 구분자 목록 순회와 문자열 잘라내기를 제거하고, 단일 루프의 문자 코드 비교로 교체하여 불필요한 메모리 할당과 검색을 줄였습니다.
- charCodeAt 활용: 문자열 비교 대신 정수 비교(
ch === 36)를 사용하여 미세하지만 일관된 성능 이득을 얻습니다. - 마크다운 렌더링 전체 성능 향상: 수식이 포함된 LLM 응답의 스트리밍 렌더링이 체감될 정도로 빨라집니다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [feast] Feast 성능 최적화: Timestamp 변환 비용 절감으로 온라인 피처 서빙 가속화
- 현재글 : [Open WebUI] KaTeX 유니코드 정규식 사전 컴파일로 마크다운 렌더링 87% 병목 제거
- 다음글 [triton] Multi-CTA 예제에서 Program ID를 Shared Memory에 저장하여 재계산 방지
댓글