memory-optimization

4 개의 포스트

google원문

TurboQuant: 극한의 압축으로 AI 효율성을 재정의하다 (새 탭에서 열림)

Google Research가 발표한 **TurboQuant**는 대규모 언어 모델(LLM)과 벡터 검색 엔진의 효율성을 극대화하기 위해 설계된 이론 기반의 압축 알고리즘입니다. 이 기술은 기존 양자화 방식의 고질적인 문제였던 메모리 오버헤드를 완전히 해결하여, 모델 성능 저하 없이 KV(Key-Value) 캐시 크기를 6배 이상 줄이고 추론 속도를 최대 8배까지 향상시킵니다. 결과적으로 TurboQuant는 추가적인 파인튜닝 없이도 초거대 AI 모델의 메모리 병목 현상을 해결하는 실질적인 솔루션을 제시합니다. ### 기존 양자화 방식의 한계와 메모리 오버헤드 * 전통적인 벡터 양자화는 데이터 크기를 줄이는 데 효과적이지만, 각 데이터 블록마다 정밀한 양자화 상수를 별도로 계산하고 저장해야 하는 '메모리 오버헤드'가 발생합니다. * 이러한 상수는 숫자당 보통 1~2비트의 추가 용량을 차지하며, 이는 전체 압축 효율을 떨어뜨리는 주요 원인이 됩니다. * 고차원 벡터를 사용하는 AI 모델에서는 이러한 오버헤드가 누적되어 KV 캐시의 병목 현상을 심화시키고 전체 시스템의 메모리 비용을 증가시킵니다. ### PolarQuant: 극좌표계를 활용한 혁신적 압축 * PolarQuant는 벡터를 기존의 데카르트 좌표계(X, Y, Z) 대신 극좌표계(반지름과 각도)로 변환하여 처리하는 새로운 접근 방식을 취합니다. * 데이터의 각도가 특정 패턴으로 집중되어 있다는 점을 활용하여, 경계값이 계속 변하는 사각형 그리드 대신 고정된 원형 그리드에 데이터를 매핑합니다. * 이를 통해 매번 정규화 단계를 거칠 필요가 없어져 기존 양자화 방식이 가졌던 메모리 오버헤드를 근본적으로 제거합니다. * 반지름 쌍을 재귀적으로 변환하여 최종적으로는 단 하나의 반지름과 데이터의 의미를 담은 여러 각도로 데이터를 압축합니다. ### QJL: 1비트의 마법을 통한 오차 제거 * QJL(Quantized Johnson-Lindenstrauss) 알고리즘은 데이터의 필수적인 거리와 관계를 유지하면서 고차원 데이터를 1비트 부호(+1 또는 -1)로 압축합니다. * TurboQuant의 두 번째 단계에서 사용되며, 첫 번째 단계(PolarQuant)에서 발생한 미세한 잔차 오차를 제거하는 수학적 오류 체크 역할을 수행합니다. * 고정밀 쿼리와 저정밀 데이터를 전략적으로 결합하는 특수 추정기(Estimator)를 사용하여 모델이 어텐션 스코어를 계산할 때 편향 없는 정확한 결과를 도출하게 돕습니다. ### 실험 결과 및 성능 지표 * **성능 유지:** LongBench, RULER 등 다양한 벤치마크에서 Gemma와 Mistral 모델을 테스트한 결과, KV 캐시를 3비트로 양자화해도 성능 저하가 거의 없는 것으로 나타났습니다. * **압축 효율:** 추가적인 학습이나 파인튜닝 없이도 KV 캐시 메모리 사용량을 최소 6배 이상 절감합니다. * **속도 향상:** H100 GPU 환경에서 4비트 TurboQuant를 적용할 경우, 양자화되지 않은 32비트 키 값을 사용할 때보다 어텐션 로짓 계산 속도가 최대 8배 빨라집니다. TurboQuant는 긴 컨텍스트(Long-context) 처리가 필요한 현대 AI 서비스에서 비용과 성능이라는 두 마리 토끼를 잡을 수 있는 강력한 도구입니다. 특히 하드웨어 자원이 제한된 환경에서 대규모 모델을 운영하거나, 실시간 응답 속도가 중요한 검색 서비스에 도입했을 때 가장 큰 효과를 기대할 수 있습니다.

datadog원문

Go 1.24의 스위스 테이블로 수백 기가바이트를 절약한 방법 (새 탭에서 열림)

Go 1.24에서 도입된 새로운 맵(map) 구현체인 '스위스 테이블(Swiss Tables)'은 대규모 인메모리 데이터를 다루는 서비스에서 획기적인 메모리 절감 효과를 제공합니다. Datadog의 실제 서비스 적용 사례에 따르면, 특정 고부하 환경에서 라이브 힙(Live Heap) 사용량이 500 MiB 감소했으며, 가비지 컬렉터(GOGC)의 영향을 고려할 때 전체 물리 메모리(RSS)는 약 1 GiB까지 절약되었습니다. 이는 Go 1.24의 다른 런타임 오버헤드를 상쇄하고도 남는 수준의 성능 향상을 보여줍니다. **실서비스에서의 메모리 절감 수치** * `ShardRouter` 패키지 내의 `shardRoutingCache`라는 대형 맵에서 약 500 MiB의 라이브 힙 사용량이 감소했습니다. * Go의 기본 GOGC 설정(100)을 기준으로 계산하면, 힙 사용량 감소는 실제 물리 메모리(RSS)에서 약 1 GiB(500 MiB x 2)의 절감으로 이어집니다. * Go 1.24의 다른 회귀 문제(mallocgc 이슈)로 인해 예상되는 400 MiB의 RSS 증가를 고려하더라도, 결과적으로 600 MiB의 순 메모리 감소가 확인되었습니다. **데이터 구조와 메모리 추정** * 해당 맵은 `string`을 키로, `Response` 구조체를 값으로 가집니다. * `Response` 구조체는 `ShardID`(int32), `ShardType`(int), `RoutingKey`(string header), `LastModified`(*time.Time)로 구성됩니다. * 64비트 아키텍처 기준으로 키-값 쌍 하나당 패딩을 포함해 약 56바이트를 차지하며, 서비스 시작 시 대량으로 생성된 후 런타임 중에는 거의 변경되지 않는 특성을 보입니다. **Go 1.23의 버킷 기반 맵 방식과 한계** * 기존 Go 1.23은 8개의 슬롯을 가진 '버킷' 배열로 해시 테이블을 관리했으며, 버킷 수는 항상 2의 거듭제곱으로 유지되었습니다. * 데이터 삽입 시 버킷 내부의 모든 요소를 순차적으로 스캔해야 하므로 CPU 오버헤드가 발생하며, 버킷이 가득 차면 '오버플로우 버킷'을 체이닝 방식으로 추가했습니다. * 평균 로드 팩터(Load Factor)가 13/16(약 81%)을 초과하면 버킷 배열의 크기를 2배로 늘리는 재할당이 발생하는데, 이 과정에서 점진적 복사(Evacuation) 방식을 사용하여 지연 시간을 관리했습니다. **결론 및 권장사항** 대규모 맵 데이터를 메모리에 유지하는 Go 애플리케이션은 Go 1.24로의 업그레이드만으로도 상당한 메모리 효율성 개선을 기대할 수 있습니다. 특히 읽기 중심의 거대 캐시 시스템이나 데이터 라우팅 테이블을 운영하는 경우, 스위스 테이블 기반의 최적화된 메모리 레이아웃이 비용 절감과 성능 향상에 큰 기여를 할 것입니다.

figma3분 읽기큐레이션 요약

Rust의 메모리 최적화

Figma는 실시간 협업 파일의 서버 측 로딩 성능과 메모리 사용량을 개선하기 위해 Rust 자료구조를 최적화했다. 특히 노드 속성을 저장하던 `BTreeMap`을 작고 정렬된 벡터로 바꾸어 대용량 파일의 메모리 사용량을 약 25% 줄이고 역직렬화 속도도 높였다. 또한 포인터의 사용되지 않는 상위 비트에 필드 ID를 저장하는 비트 패킹 방식도 검토했지만, 이는 아직 프로덕션에 적용되지 않았다. ## 파일 로딩과 메모리 사용량의 문제 - Figma의 멀티플레이어 시스템은 파일 로딩, 협업자 업데이트 전파, 파일 상태 스냅샷 저장을 담당한다. - 복잡한 노드 그래프를 실시간으로 처리하기 위해 파일의 상당 부분을 메모리에 올린다. - 동적 페이지 로딩을 도입한 뒤 서버에서 디코딩해야 하는 파일 수가 약 30% 증가했다. - 이에 따라 로딩 경로에 있는 Rust 자료구조와 메모리 배치를 최적화할 필요가 생겼다. ## `BTreeMap`이 차지한 메모리 - Figma 파일은 삼각형, 프레임, 텍스트 등 다양한 노드의 집합으로 표현된다. - 각 노드는 색상, 타입, 부모 노드, 위치 같은 속성을 가진다. - 기존에는 속성을 다음과 같은 형태로 저장했다. ```rust BTreeMap<u16, pointer> ``` - `u16`은 속성 ID, 포인터는 속성 값의 위치를 의미한다. - 이 맵은 파일 로딩의 핵심 경로에 있었고, 전체 파일 메모리 사용량의 60% 이상을 차지했다. - 스키마의 속성 수는 200개 미만이며, 하나의 노드에 실제로 존재하는 속성은 평균 약 60개에 불과했다. - 속성 키가 작고 제한적이며, 대부분 특정 노드 유형에만 묶여 있다는 점에서 범용 맵이 과도하다고 판단했다. ## 정렬된 벡터로 자료구조 변경 - `BTreeMap` 대신 속성 ID와 포인터를 저장하는 정렬된 평탄 벡터를 사용했다. ```rust Vec<(u16, pointer)> ``` - 이론적인 복잡도만 보면 벡터가 불리하다. - 검색: `O(n)` - 중간 삽입: `O(n)` - 수정: 위치 탐색 비용 발생 - `BTreeMap`은 일반적으로 `O(log n)` 수준의 연산 제공 - 하지만 실제 파일 로딩에서는 벡터의 연속적인 메모리 배치가 더 유리했다. - CPU는 작은 선형 메모리 영역을 순차적으로 읽고 계산할 때 캐시 효율이 높다. - 결과적으로 역직렬화 속도가 향상됐고, 대용량 파일의 메모리 사용량은 약 25% 감소했다. - 이 사례는 Big O 복잡도만으로 실제 성능을 판단하기보다 데이터 크기와 메모리 지역성까지 고려해야 함을 보여준다. ## 포인터에 필드 ID를 함께 저장하는 비트 패킹 - 일반적인 포인터는 64비트지만, x86 시스템에서는 실제 주소 지정에 하위 48비트만 사용하는 경우가 많다. - 따라서 상위 16비트가 사용되지 않는다는 점에 착안해, 여기에 속성 필드 ID를 저장하는 방법을 검토했다. - 기존에는 속성 ID와 포인터를 별도 값으로 저장했지만, 비트 패킹 후에는 하나의 `u64`에 둘을 함께 담을 수 있다. ```rust Vec<u64> { [field_id_u16, pointer_u48], } ``` - 속성 ID가 정확히 16비트로 표현 가능하다는 점과 포인터의 남는 상위 비트가 맞아떨어졌다. - 이 방식은 메모리 접근과 데이터 구조의 크기를 더 줄일 가능성이 있다. - 다만 포인터의 상위 비트가 항상 사용 가능하다는 보장은 없으며, 하드웨어나 운영체제의 주소 체계가 바뀔 수 있다. - 따라서 해당 최적화는 조사 단계이며 아직 프로덕션에는 적용되지 않았다. 작은 데이터 집합에서는 범용 트리나 맵보다 단순한 연속 배열이 더 빠르고 효율적일 수 있다. Rust에서 성능을 최적화할 때는 이론적 복잡도뿐 아니라 실제 데이터 크기, CPU 캐시 지역성, 포인터 표현 방식, 플랫폼 호환성까지 함께 측정하고 판단하는 것이 중요하다.

원문 읽기(새 탭에서 열림)
figma3분 읽기큐레이션 요약

점진적 프레임 로

Figma는 프로토타입 전체를 메모리에 올리던 기존 방식을 버리고, 현재 화면과 곧 이동할 수 있는 화면만 단계적으로 불러오는 ‘증분 프레임 로딩’을 도입했습니다. 이를 통해 대형 프로토타입의 초기 로딩 시간을 줄이고, 특히 메모리가 제한된 모바일 환경에서 발생하던 앱 종료와 충돌을 완화했습니다. 핵심은 필요한 문서 트리 일부만 실시간으로 구독하고, 사용자의 탐색에 따라 구독 범위를 확장하는 것입니다. ## 전체 프로토타입 로딩의 한계 - 초기 프로토타이핑 기능은 프로토타입이 포함된 문서 전체를 메모리에 로드한 뒤 첫 화면을 표시했습니다. - Figma 문서가 커지면서 페이지, 디자인 시스템, 컴포넌트 변형 수가 급증했고 프로토타입도 함께 대형화되었습니다. - iPhone을 비롯한 모바일 기기는 데스크톱보다 메모리 여유가 적습니다. - 운영체제가 애플리케이션의 메모리 사용량 증가를 허용하지 않고 프로세스를 종료하면서 모바일 프로토타입 충돌이 잦아졌습니다. - 전체 데이터를 다운로드하고 메모리에 보관하는 방식은 로딩 시간과 메모리 사용량을 동시에 악화시켰습니다. ## 증분 프레임 로딩 전략 - 증분 로딩은 파일 전체가 아니라 새로 필요하거나 변경된 데이터 일부만 로드하는 방식입니다. - Figma는 이 개념을 프로토타입 화면 단위에 적용해 ‘증분 프레임 로딩’이라고 명명했습니다. - 현재 화면을 표시하는 데 필요한 데이터만 내려받고 메모리에 유지합니다. - 사용자가 처음 보는 화면과 해당 화면에서 프로토타입 인터랙션으로 바로 이동할 수 있는 인접 화면을 우선 로드합니다. - 사용자가 다음 화면으로 이동하면 새 화면과 그 화면에서 접근 가능한 인접 화면을 추가로 로드합니다. - 이미 본 화면은 사용자가 뒤로 이동할 수 있으므로 메모리에 계속 유지합니다. ## Time to Interactive 최적화 - 목표는 페이지 전체가 완전히 로드되는 시간이 아니라, 사용자가 유용한 화면을 보고 빠르게 클릭·탐색할 수 있게 되는 시간인 TTI(Time to Interactive)를 줄이는 것입니다. - 첫 화면과 즉시 이동 가능한 화면만 준비하면 사용자는 전체 프로토타입이 로드되기 전에 상호작용을 시작할 수 있습니다. - 이후 화면은 사용자의 탐색 흐름에 맞춰 지연 로딩되므로 초기 표시를 위해 기다려야 하는 데이터 양이 줄어듭니다. ## 실시간 공동 편집과 부분 동기화 - Figma 파일은 다른 사용자가 편집할 때 변경 사항이 활성 사용자에게 실시간으로 동기화되어야 합니다. - 기존 시스템은 파일 전체를 서버의 실시간 데이터 저장소와 연결해 업데이트를 전달했습니다. - 부분 로딩을 지원하기 위해 Figma는 파일의 특정 부분만 조회할 수 있는 기능을 추가했습니다. - 클라이언트는 문서 트리에서 구독할 하위 트리를 `query` 메시지로 요청합니다. - 서버는 해당 하위 트리의 현재 상태를 `reply` 메시지로 전달하고 요청이 처리되었음을 확인합니다. - 이후 구독 범위 안에서 변경이 발생하면 서버는 `changes` 메시지를 통해 업데이트를 전송합니다. ## 문서 트리 구독 프로토콜 - 특정 노드 `a`를 구독하면 해당 노드뿐 아니라 조상 노드와 모든 자손 노드도 구독 대상으로 취급됩니다. - 클라이언트가 `a`를 요청하면 서버는 `a`의 현재 콘텐츠와 함께 요청 완료 응답을 보냅니다. - 구독한 노드의 속성이 변경되면 변경 사항이 클라이언트로 전달됩니다. - 다른 노드 `y`가 `a` 아래로 이동하면 서버는 `a`의 구독 범위에 새로 들어온 `y`의 존재를 전달합니다. - 반대로 `c`가 `x` 아래로 이동하고 클라이언트가 `x`의 자손을 구독하지 않는다면, 클라이언트에는 `c`가 구독 범위에서 제거되었다는 변경 사항이 전달됩니다. - 따라서 클라이언트는 파일 전체가 아니라 현재 관심 영역에 해당하는 문서 트리만 유지하면서도 실시간 상태를 일관되게 관리할 수 있습니다. ## 실용적인 결론 대규모 문서나 프로토타입을 다루는 애플리케이션에서는 전체 데이터 선로드보다 사용자 행동을 기준으로 한 부분 로딩이 효과적입니다. 특히 초기 상호작용에 필요한 최소 데이터만 먼저 제공하고, 탐색 경로에 따라 인접 데이터를 미리 가져오며, 실시간 동기화 역시 구독 단위로 제한하는 설계가 로딩 성능과 메모리 안정성을 함께 개선할 수 있습니다.

원문 읽기(새 탭에서 열림)