[Open WebUI] JS transition을 CSS 애니메이션으로 교체하여 스트리밍 렌더링 최적화
PR 링크: open-webui/open-webui#23257 상태: Merged | 변경: +15 / -21
들어가며
Open WebUI에서 LLM 응답을 스트리밍할 때, 각 단어가 화면에 나타나면서 fade-in 효과가 적용됩니다. 기존에는 Svelte의 transition:fade를 사용했는데, 이는 JS 기반으로 매 단어마다 메인 스레드에서 인라인 스타일을 적용하고 라이프사이클 콜백을 실행합니다. 이 PR은 동일한 시각 효과를 CSS @keyframes로 대체하여 메인 스레드 부하를 제거합니다.
핵심 코드 분석
Before: Svelte JS transition
<script lang="ts">
import { fade } from 'svelte/transition';
export let token;
export let done = true;
let texts = [];
$: texts = (token?.raw ?? '').split(' ');
</script>
{#if done}
{token?.raw}
{:else}
{#each texts as text}
<span class="" transition:fade={{ duration: 100 }}>
{text}{' '}
</span>
{/each}
{/if}
transition:fade는 양방향(bidirectional)이므로, done이 true로 바뀌면 모든 단어 span이 fade-out되면서 일반 텍스트가 렌더링됩니다. 이는 의도하지 않은 시각적 결함입니다.
After: CSS @keyframes 애니메이션
<script lang="ts">
export let token;
export let done = true;
</script>
{#if done}
{token?.raw}
{:else}
{#each (token?.raw ?? '').split(' ') as text}
<span class="fade-in-token">
{text}{' '}
</span>
{/each}
{/if}
/* app.css */
@keyframes fade-in-token {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in-token {
animation: fade-in-token 100ms ease-out;
}
CSS 애니메이션은 브라우저의 컴포지터 스레드에서 처리되므로 메인 스레드를 차단하지 않습니다. 또한 intro-only 애니메이션이므로 fade-out 버그도 자연스럽게 해결됩니다.
왜 이게 좋은가
1. 스트리밍 중 성능 영향
LLM이 초당 수십 개의 토큰을 생성할 때, 각 단어마다 Svelte의 JS transition이 실행되면 메인 스레드가 반복적으로 차단됩니다. CSS 애니메이션으로 전환하면 이 오버헤드가 완전히 제거됩니다.
2. 리액티브 변수 제거
$: texts 리액티브 선언을 제거하고 {#each} 블록 안에서 직접 split()을 호출합니다. 이로써 불필요한 리액티브 구독 오버헤드가 줄어들고, fade import도 제거됩니다.
3. CodespanToken 중복 제거
기존에 CodespanToken.svelte에 컴포넌트 스코프로 정의되어 있던 동일한 fade-in-token 키프레임을 전역 app.css로 이동하여 중복을 제거했습니다.
참고 자료
- CSS vs JS Animations - web.dev — CSS와 JS 애니메이션의 성능 차이
- Svelte transition 문서 — Svelte transition의 동작 방식
관련 포스트
PR Analysis 의 다른글
- 이전글 [Ray] Parquet 배치 크기를 C++ 32비트 정수 범위로 클램핑하여 OverflowError 수정
- 현재글 : [Open WebUI] JS transition을 CSS 애니메이션으로 교체하여 스트리밍 렌더링 최적화
- 다음글 [Open WebUI] 스트림 청크 핸들러에서 yield 호출 횟수 절반으로 줄이기
댓글