Post

Multimodal Quantization 연구 모델 디벨롭

압축된 추천 모델에 Lookup 재구성, Quantization, Compiler, FX Graph rewrite, Database, AMP를 적용하며 추론 효율을 비교한 기록

Intro

최근 Elsevier의 Knowledge-Based Systems 저널 리비전을 진행하면서, 모델 경량화를 저장 공간뿐 아니라 실제 실행 비용의 관점에서도 살펴보고 싶어졌다.

제안한 PReQ 모델에서는 Compact codebook을 활용해 멀티모달 특징을 압축하고, 이를 GNN 기반 추천에 활용했다. codebook은 여러 특징을 표현할 수 있는 대표 벡터를 모아 둔 표다. 각 특징을 긴 벡터 그대로 저장하는 대신, 이 표의 어떤 항목을 사용할지 나타내는 code와 함께 표현하는 방식이다.

여기에 연산을 묶어서 실행하거나, 계산 정밀도를 낮추거나, 컴파일러를 적용하면 어떤 변화가 있을지 궁금했다.

압축된 모델의 추천 품질을 유지하면서, 계산도 더 빠르게 할 수 있을까?

이를 확인하기 위해 기존 배포 모델을 재학습하지 않고, 실행 방법을 바꾸는 여덟 가지 실험을 진행했다.

경량화와 추론 최적화의 차이

아래 실험이 모두 모델의 크기를 줄이는 기법은 아니다. 경량화는 필요한 저장 공간이나 계산량을 줄이는 데 초점을 두고, 추론 최적화는 학습된 모델을 실제로 실행할 때의 시간·메모리 사용 등을 개선하는 더 넓은 과정이다.

예를 들어 INT8은 일부 가중치를 더 작은 형식으로 저장할 수 있지만, Lookup 재구성과 컴파일러 적용은 주로 같은 계산을 실행하는 방법을 바꾼다. Profiling은 비용을 측정하는 도구이고, Database는 좋은 실행 설정을 찾는 데 드는 반복 작업을 줄이기 위한 장치다.

이번 실험은 논문의 압축 모델을 출발점으로, 이런 서로 다른 방법을 실제 추론 과정에 적용해 본 기록이다.

Experiment Process

순서실험역할
01Profiling연산별 시간·메모리 비용 측정
02Lookup 재구성반복 조회를 묶어 실행 방식 변경
03Settings Auto-Tuning처리 묶음 크기·CPU thread 조합 탐색
04INT8 Quantization일부 가중치·계산에 낮은 정밀도 적용
05torch.compile컴파일러로 연산 실행 방식 최적화
06FX Graph rewrite연산 그래프의 특정 패턴 직접 변환
07SQLite Database과거 측정 기록 관리와 중복 측정 방지
08BF16 AMP일부 계산의 정밀도를 바꾸고 적용 정책 선택

주로 측정한 것은 전체 사용자·아이템의 embedding을 다시 계산하는 시간이다. embedding은 사용자나 아이템의 특징을 숫자 벡터로 표현한 것이다. 이를 미리 계산해 보관해 두면, 추천 요청에서는 그 결과를 가져와 점수만 계산할 수 있다.

PReQ의 embedding 갱신과 캐시 기반 추천 흐름 위쪽은 embedding을 갱신하는 과정, 아래쪽은 그 결과를 재사용해 추천하는 과정이다.

Experiments

01. Profiling — 먼저 실행 비용 확인하기

Profiling은 프로그램의 각 작업에 시간과 메모리가 얼마나 드는지 살펴보는 과정이다. 전체 실행이 느리다는 사실에서 더 나아가, 어느 부분부터 바꿔야 할지 찾는 것이다. 그중 성능을 제한하는 구간을 병목이라고 부른다.

PyTorch Profiler로 Lookup, Fusion, 그래프 계산의 비용을 나누어 살폈다. 첫 실행에서 전체 embedding 갱신은 평균 약 48.98 ms, 캐시를 사용한 사용자 32명의 추천 요청은 약 1.31 ms였다.

반복적인 작은 Lookup 호출이 눈에 들어왔다. 다음 실험에서는 이 호출을 묶어서 처리해 보기로 했다. Profiler 자체도 실행에 영향을 줄 수 있어, 연산 분석과 정상 실행 시간 측정은 분리했다.

02. Lookup — 여러 번의 조회를 한 번에 묶기

Lookup은 code가 가리키는 벡터를 codebook에서 꺼내는 작업이다. PReQ에서는 여러 압축 단계에서 꺼낸 벡터를 더해 특징을 만든다. 이때 작은 조회를 여러 번 요청하는 대신 여러 단계의 조회를 한꺼번에 처리하는 방식을 비교했다.

모델이 보유한 정보를 줄이는 작업보다는, 같은 정보를 가져오는 실행 방식을 바꾸는 실험이다.

기존에는 압축 단계마다 텍스트·이미지 특징을 따로 꺼내 더했다. 이를 묶어서 조회하고 합산하도록 바꿨다.

embedding 연산 호출은 240회 → 30회로 줄었고, 사용자 5,753명의 추천 Top-K도 유지됐다. Top-K는 추천 점수가 높은 상위 K개 아이템을 뜻한다.

다만 전체 실행 시간의 개선은 작고 변동이 컸다. 호출 수가 줄어도 전체 모델이 반드시 빨라지는 것은 아니었다.

03. Auto-Tuning — 실행 설정도 함께 탐색하기

Auto-Tuning은 여러 실행 설정을 실제로 측정해 유리한 조합을 찾는 과정이다. 이번에는 chunk 크기와 CPU thread 수를 바꿨다.

  • chunk: 한 번에 처리하는 데이터 묶음의 크기. 작으면 반복 호출이 늘고, 크면 한 번에 다루는 데이터가 많아진다.
  • CPU thread: CPU에서 작업을 수행하는 실행 흐름의 수. 늘려도 작업 분배·동기화 비용 때문에 항상 빨라지는 것은 아니다.

Lookup 방식·chunk·thread를 조합해 12개 설정을 비교했다. 탐색에서 고른 후보는 새로운 측정으로 다시 확인했다.

해당 실행에서는 batched_reduce / chunk 1024 / thread 1이 선택됐다. batched_reduce는 조회를 묶은 뒤 압축 단계 방향으로 한꺼번에 합산하는 방식이다. 기준 설정 대비 평균 갱신 시간은 75.94 → 69.11 ms, 약 9.0% 감소했다.

Lookup 방식과 thread 수를 함께 바꾼 결과이므로, 개선 전체를 한 가지 변경의 효과로 보지는 않았다.

04. INT8 — 낮은 정밀도의 효과 확인하기

Quantization은 숫자를 더 적은 비트로 표현하도록 바꾸는 방법이다. 이번 INT8 Dynamic Quantization에서는 선택한 Linear의 가중치를 8비트 정수 형식으로 저장하고, 입력도 실행 시 양자화해 계산했다.

Linear는 입력 특징에 학습된 가중치를 곱해 새로운 특징을 만드는 연산이다. 표현을 작게 만들 수 있지만, 숫자 변환 비용과 오차가 생길 수 있다. 여기서 적용한 INT8은 논문의 codebook 기반 압축과는 대상이 다르다.

특징을 변환하는 Projection, 텍스트·이미지의 혼합 비율을 정하는 Gate에 INT8을 각각 적용했다.

실제 INT8 연산 실행과 저장·재로딩을 검증했고, Validation 품질 기준도 통과했다. 하지만 평균 갱신 시간은 FP32 35.02 ms, 전체 Fusion Linear INT8 43.31 ms로 늘었다. FP32는 이번 비교의 기준인 32비트 부동소수점 형식이다.

전체 체크포인트 크기도 최대 약 1.48%만 줄었다. 체크포인트는 모델 상태를 저장한 파일이며, 이번에 바꾼 Linear weight가 모델 전체에서 차지하는 비율이 작았기 때문이다.

05. Compiler — GatedFusion에 torch.compile 적용하기

딥러닝 모델의 계산을 실행하기 좋은 형태로 준비하는 데 컴파일러를 사용할 수 있다. TorchDynamo는 연산을 그래프로 포착하고, TorchInductor는 그 그래프를 실행할 코드를 생성한다. torch.compile은 이 경로를 적용하는 PyTorch의 기능이다.

모델을 새로 학습하거나 가중치 수를 줄이는 대신, 연산을 실행하는 방법을 바꾸는 접근이다. 분석 중 그래프가 끊겨 나뉘는 상황은 Graph break라고 부른다.

텍스트·이미지 특징을 혼합하는 GatedFusion에 적용했다. 평균 갱신 시간은 기존 실행 방식인 eager 62.93 → 60.20 ms, 약 4.3% 감소했다.

사용자·아이템 Fusion 내부의 Graph break는 0이었고, 추천 Top-K도 유지됐다. 다만 최초 실행에는 컴파일을 포함해 약 27.9초가 걸렸다. 반복 실행의 이득과 첫 실행의 준비 비용을 따로 봐야 했다.

06. FX Graph rewrite — 작은 그래프 변환 직접 구현하기

연산 그래프는 어떤 계산의 결과가 다음 계산으로 전달되는지 나타낸 것이다. torch.fx로 이 구조를 다루면서, 특정 연산 패턴을 찾아 다른 구조로 바꾸는 작은 pass를 직접 구현했다.

대상은 텍스트와 이미지에 각각 적용하던 Projection이었다. 두 가중치를 미리 묶고, 입력도 묶어서 batched GEMM으로 처리하도록 바꿨다. GEMM은 일반 행렬 곱셈이고, batched GEMM은 여러 행렬 곱셈을 묶어 수행하는 방식이다.

병렬 Projection을 batched GEMM으로 재구성한 구조 실제 FX 그래프에서 바꾼 부분을 단순화했다. stack은 두 입력을 새로운 축으로 쌓는 연산이며, 이 추가 비용은 여전히 남는다.

가중치 크기와 입력 의존성을 검사하고, 변환 후 출력·추천 결과를 검증했다. Linear 호출은 60회 → 30회로 줄었지만 평균 갱신은 87.04 → 98.08 ms로 느려졌다.

하나의 박스로 묶었다고 하나의 하드웨어 커널이 되는 것은 아니다. 그래프 변환이 의도대로 동작하는 것과, 성능상 채택할 수 있는 것은 다른 문제였다.

07. Database — 측정 기록을 재사용할 조건 만들기

같은 설정을 매번 측정하는 대신, 과거 결과를 저장하고 재사용하도록 했다. SQLite는 별도 Database 서버 없이 파일에 데이터를 저장하고 SQL로 조회할 수 있는 도구다.

이런 재사용을 캐시라고 부른다. 기록을 유효하다고 보는 시간인 TTL, 현재 속도를 짧게 확인하는 기준값인 anchor를 두어, 오래됐거나 환경이 달라진 기록은 다시 측정하도록 했다. 모델 자체의 크기나 추론 시간을 줄이기보다 Auto-Tuning의 반복 측정 비용을 줄이려는 실험이다.

SQLite에 설정별 지연을 저장하고 모델·환경·코드가 같은지 확인해 재사용하도록 했다. Colab에서는 baseline anchor가 153.12 → 68.83 ms로 크게 달라져 반복 탐색의 12개 설정 모두 재측정했다. baseline은 비교 기준이 되는 모델·설정이다.

이 실행에서는 캐시로 생략한 측정이 없었다. 대신 환경 변화에 따라 과거 기록을 무효화하는 동작을 확인했다.

08. AMP — 품질과 속도로 정밀도 정책 선택하기

Mixed Precision은 모든 계산을 같은 정밀도로 수행하지 않고, 일부는 낮은 정밀도로, 다른 부분은 FP32로 계산하는 방식이다. AMP는 PyTorch의 autocast 등을 이용해 이를 적용하는 기능이다.

이번에는 16비트 부동소수점 형식인 BF16을 사용했다. INT8 실험과 달리 저장 가중치는 FP32로 유지하고, 선택한 연산의 계산 정밀도를 바꿨다. 그래서 이번 AMP 실험은 체크포인트 압축과는 구분된다.

Projection·Gate에 BF16 autocast를 선택적으로 적용하고, normalization과 그래프 계산 등은 FP32로 유지했다. normalization은 벡터의 크기를 일정한 기준에 맞추는 연산이다.

실제 dtype, 즉 숫자 자료형을 확인한 뒤, Validation 품질과 실행 시간을 기준으로 네 정책 중 하나를 선택하도록 했다. BF16 후보는 품질 기준을 통과했지만 모두 더 느려, 최종 결정은 FP32 유지였다.

Results & Takeaways

정밀도 및 컴파일러 적용에 따른 평균 갱신 시간 비교 각 패널은 별도 Colab 실행이며 눈금도 다르다. 서로 다른 실험의 절대 시간이나 개선율을 합산하지 않았다.

실행 설정 탐색과 컴파일러 적용에서는 소폭의 개선을 관찰했다. 반면 INT8·BF16과 직접 작성한 FX rewrite는 해당 CPU 환경에서 더 느렸다.

정밀도 후보의 품질 기준은 Validation NDCG 상대 하락 1% 이내로 먼저 정했다. Validation은 후보를 선택하기 위한 검증 데이터이고, NDCG는 정답 아이템이 추천 목록의 얼마나 높은 위치에 있는지 반영하는 지표다. 기준을 통과했다고 사용자별 추천 목록까지 완전히 같다는 뜻은 아니다.

작은 행렬, 숫자 형식을 바꾸는 비용, 입력을 묶는 비용 등이 속도 저하의 원인 후보지만 아직 하나로 확정할 수는 없다. 이번 실험에서는 다음 세 가지를 남겼다.

  • 저장 크기와 실행 속도는 별도로 측정해야 한다. 작게 저장된 모델도 계산 과정에서는 다른 비용이 발생한다.
  • 연산 변경과 추천 품질을 함께 검증해야 한다. 수치 오차와 실제 추천 결과의 변화를 모두 확인해야 한다.
  • 실행 환경이 달라지면 다시 확인해야 한다. thread 수, CPU 지원과 부하에 따라 결과가 달라졌다.

이번 작업은 논문에서 다룬 모델·특징 압축을 실제 추론 효율의 관점으로 확장해 본 과정이었다. 특정 기법을 적용했다는 사실에 그치지 않고, 같은 모델에서 무엇이 바뀌고 어떤 비용이 생기는지 확인하는 데 의미가 있었다.

측정 범위
수치는 각 실험 안에서 같은 체크포인트와 데이터 분할로 비교했다. 품질 비교에는 공통 evaluator를 사용했으며, 논문 정확도의 재검증이나 특징 생성 단계의 데이터 분리 검증을 새로 수행한 것은 아니다. 메모리도 tensor 크기와 프로세스 최대 사용량을 구분했다.

References

This post is licensed under CC BY 4.0 by the author.