<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발놀이터</title>
    <link>https://coding-review.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 21 Jul 2026 18:32:54 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>마늘냄새폴폴</managingEditor>
    <item>
      <title>우린 전사 직원이 AI 다 써야돼요. (GPU 서버 한대를 쥐어주며..)</title>
      <link>https://coding-review.tistory.com/623</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 오랜만에 블로그 포스팅으로 써보고싶은 주제가 생각나서 컴퓨터 앞에 앉았습니다. 이번 주제는 실제로 있을법한 일을 재구성해서 엔지니어링적으로 문제를 해결하는 과정을 써볼까합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 엉덩이 무거운 금융권도 AI를 도입하려고 여기저기서 난리다보니 국내기준 모든 개발자 수요가 줄었지만 AI엔지니어는 수요가 폭발하고 있는 상황입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 저는 AI엔지니어링.. 그 끝은 백엔드와 닿아있다는걸 깨닫게 되는건 그리 오래걸리지 않았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황은 이렇습니다. 고객이 AI를 도입하는데 그래픽카드 서버를 한대만 주겠다고 하는 상황이죠. 이때 우리가 할 수 있는 다양한 해결책을 생각해봅시다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sLLM을 올리는건 문제가 안됩니다만 문제는 사용자마다 들어나는 KV Cache이죠. 보통 나쁘지않은 질답이 오가려면 토큰이 16k정도는 있어야하고 멀티턴을 고려한다면 더 필요할 수도 있습니다. 요즘 그래픽카드 서버 한대에 올리기 가장 좋은 모델은 Gemma 4로 소문이 자자하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemma 4의 32k가 점유하는 KV Cache는? 15GB정도 됩니다. 물론 요즘 양자화 기술이 좋아져서 웬만하면 다 8비트로 양자화 해버리긴 합니다만 사용자가 전직원이면 많이 부족하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;case 1. 메세지 큐잉을 도입하는 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 떠올릴 수 있는 방법입니다. 그리고 가장 쉽게 문제가 해결되는 경우이기도 하죠. 대규모 서비스에서 EDA와 AI엔지니어링은 궁합이 좋습니다. EDA가 지금껏 성행하는 이유도 중간 버퍼로 서버 부하를 막고 메세지를 안정적으로 도달하게끔 만드는데 특화된 EDA는 RDB랑 궁합이 좋았기 때문이죠. RDB는 커넥션 풀도 정해져있고 &quot;이만큼만 요청을 받고싶습니다&quot;의 대표주자이기 때문에 확실히 궁합이 좋죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM도 마찬가지입니다. 이만큼의 요청만 받고싶은 LLM도 EDA와 궁합이 매우 좋습니다. 실제로 대규모 서비스를 하는 곳에서는 EDA와 LLM을 많이 붙여서 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;side effect 1. 유지보수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리 포인트가 늘어나는건 어쩔 수 없는 문제이긴 합니다. 다만 Kafka나 Redis는 유지보수하기 꽤 까다로워서 그냥 추가합시다 하고 추가할 수 있는건 아니긴 하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;side effect 2. 단일 장애 지점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카가 죽으면? 레디스가 죽으면? 이 질문에 대한 답이 정해져있어야합니다. 이 질문에 대한 답이 없으면 MQ가 죽는순간 그래픽카드 서버도 OOM으로 죽어버리거든요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;case 2. 우린 서버 많은데요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 솔루션은 Active Standby로 들어가서 서버 많아요~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 HA 솔루션을 기대해볼만합니다. Kafka Cluster와 Redis Sentinel 같은거말이죠. 이것도 물론 방법이 될 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;side effect 1. 관리 포인트 어떡하지..&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나도 관리포인트가 쉽지않은데 여러개를 하겠다면 그것도 말리진 않겠습니다만 각오는 해야할 것입니다. 큰 힘에는 큰 책임이 따르는 것이니까요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;side effect 2. 디버깅 어떡하지..&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 비동기로 미뤄버리면 디버깅이 지옥인건 잘 아는 사실이실겁니다. 근데 그마저도 세개로 나눠지고있으니 어디서 요청을 보냈는지 어디가 문제가 생겼는지 알기 쉽지않죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;case 3. 코드로 해결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 얘길 하고싶었습니다. 이 포스팅을 쓰게된 계기이기도 하죠. 들어가기 전에 제 얘기를 조금 하고 넘어가겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 문제를 항상 구조적으로 해결하는걸 좋아했습니다. 그리고 잘했죠. 위에서처럼 아키텍처로 풀어나가는 문제도 좋았지만 코드 내부에서도 전략패턴 어댑터패턴 프록시패턴 등 디자인패턴으로 문제를 해결하는것도 좋아했습니다. 요즘 AI가 이렇게 만들어줘 저렇게 만들어줘 하면 뚝딱 나오니 좋았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 점점 제 약점이 드러나고 있었습니다. 바로 알고리즘이요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래픽카드 서버가 한대밖에 없어서 부하를 막아야한다 -&amp;gt; 그럼 메세지 큐 도입 -&amp;gt; 단일 장애 지점은? -&amp;gt; HA로 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼴에 메세지 큐 도입하는건 관리포인트를 줄이기 위해서 레디스를 쓴다는 얘기를 했으니 말 다했죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 쉽고 좋은 해결책이 알고리즘에 있었습니다. 바로 Bounded Queue와 Token Bucket 알고리즘이요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bounded Queue는 큐의 개수를 무한히 만들지 않습니다. 1000개면 1000개 이렇게 정해놓고 다 차면 429 (Too Many Request) 를 반환해서 자바스크립트에서 재시도를 요청하는거죠. 물론 이 재시도도 지수 백오프로 랜덤하게 안하면 큐 생기자마자 대기하고 있던 클라이언트가 다같이 미친듯이 요청보내서 서버를 죽여버릴 수도 있습니다. 이게 그 유명한 Thundering Herd죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Token Bucket은 1초에 생성할 수 있는 토큰을 제한하고 요청은 이 토큰을 물고 들어올 수 밖에 없게 만듭니다. 제한된 토큰 이외의 토큰은 전부 삭제되죠. 그럼 요청이 예측가능해집니다. 그리고 우리 그래픽카드 서버의 자원도 예측할 수 있죠. 그럼 수용량을 제한할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bounded Queue와 Token Bucket 이 두개를 쓰면 카프카니 레디스니 안써도 된다는 장점이 생깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당분간 제 공부 방향은 이쪽으로 갈 것 같네요. 아키텍처 관점에서 문제를 해결하는건 몇 년동안 해왔던 것이니 이제 방향을 조금 틀어볼까 합니다. 이제 제가 가지고 있는 모든 아키텍처 관점의 문제 해결을 알고리즘으로 해결해보려고 노력해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이 둘이 같이 쓰면 좋지만 일단 한쪽 테크트리를 탔으니 반대쪽 테크트리도 한번 타보려고합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅은 여기서 마무리짓도록 하겠습니다. 긴 글은 아니었지만 그래도 봐주셔서 감사합니다. 오늘도 즐거운 하루 되세요!&lt;/p&gt;</description>
      <category>AI Engineering/실습</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/623</guid>
      <comments>https://coding-review.tistory.com/623#entry623comment</comments>
      <pubDate>Thu, 28 May 2026 21:48:46 +0900</pubDate>
    </item>
    <item>
      <title>Attention 레이어와 MLP 레이어</title>
      <link>https://coding-review.tistory.com/622</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 이번 포스팅에서는 MLP레이어에 대해서 공부한 내용을 정리해보려고합니다. LLM에서 MLP레이어는 핵심축 중에서도 핵심축인데요. 이제 천천히 이 레이어에 대해서 알아보며 끝으로 갔을 때는 MLP레이어의 진수에 대해서 맛보게 될 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;레이어들&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Attention 레이어, MLP 레이어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에는 크게 두 개의 레이어가 있다고 표현합니다. Attention 레이어와 MLP 레이어 이렇게 두 개인데요. Attention 레이어는 기본적으로 Attention 연산을 수행하는 레이어로서 단어와 단어 사이의 맥락을 파악하는 용도로써 사용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그에 비해 MLP 레이어는 맥락이 파악된 토큰을 입력받아서 그 토큰이 가진 정보에 '데이터'를 입힙니다. 예를 들어서 &quot;프랑스의 수도는?&quot;이라는 맥락이 완성되면 MLP는 훈련 과정에서 학습한 &quot;파리&quot;라는 정보를 꺼내옵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 트랜스포머에서는 MLP를 일종의 Key Value Memory로 보는 시각이 있습니다. 그 이유가 MLP는 항상 두 개의 선형 레이어가 각각 키와 값의 역할을 하기 때문입니다. 첫 번째 선형 레이어는 패턴을 감지하고 활성화 함수를 거쳐 두 번째 선형 레이어에서 선택된 패턴에 대응하는 실제 정보를 결괏값에 더해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;잔차 연결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemma 3 모델의 경우 MLP레이어가 48개가 존재하는데 각각의 레이어를 지날 때마다 &quot;잔차 연결&quot;을 하면서 지나가게 됩니다. 여기서 &quot;잔차&quot;란 아주 조금의 차이라는 뜻으로 아주 조금의 차이만큼 레이어에서 계속 더해가면서 연결을 하여 의미를 확장해나간다는 의미입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 &quot;자기소개 부탁해&quot;라는 입력을 받으면 초반 MLP 레이어에선 어떤 언어로 질문이 들어왔는지, 문장 구조는 어떻게 되어있는지와 같은 기본적인 분석을 마치고 중간 레이어에서는 자기소개를 해야한다는 맥락을 파악하고 마지막 레이어에선 자기소개를 준비하는 식입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 MLP 레이어가 많다는 것은 곧 모델의 사고의 깊이가 얼마나 깊어지느냐를 판단하는 기준이 되기도 합니다. 이것이 큰 모델들이 복잡한 논리 구조나 비유를 이해하면서 고차원적인 표현이 가능한 것을 설명해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인덕션 헤드와 MLP 레이어와의 관계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이어가 수십개가 되는 순간 아무리 성능이 좋아도 수십 번의 행렬 곱셈을 거치게 되면서 초기 입력 정보가 희석되거나 왜곡될 수 밖에 없습니다. 그래서 잔차 연결은 아무것도 안해도 입력이 그대로 전달되고 출력물이 입력 정보를 그대로 뱉어낸다는 점에서 모델의 왜곡을 방어하고 이 덕분에 하위 레이어의 정보가 상위 레이어까지 손실 없이 전달되는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 잔차 연결은 딥러닝의 고질병인 기울기 소실 문제를 해결하는 핵심이 되기도 합니다. 역전파 시 미분 값이 잔차 연결을 타고 처음부터 끝까지 끊김없이 전달이 되어 모델을 학습시키는 리스크가 줄어들게 되었기 때문입니다. 이 덕분에 레이어가 수십, 수백개가 되어도 모델을 안정적으로 학습시킬 수 있게 된 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 공부한 것에서 인덕션 헤드와 잔차 스트림이라는 개념을 공부했었습니다. 자세한 내용은 아래의 링크를 확인해주세요!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/618&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://coding-review.tistory.com/618&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779024600247&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;특명: 학습하지 않은 패턴을 학습해라 (Few Shot의 동작 방식)&quot; data-og-description=&quot;안녕하세요! 거의 3주만에 블로그 포스팅으로 돌아왔습니다. 요즘 블로그 글 쓰는게 뜸해졌는데 별로 바쁘지 않았던거같은데 공부할 틈이 안나서 이제서야 글을 쓰는 것 같네요. 오늘은 few shot (&quot; data-og-host=&quot;coding-review.tistory.com&quot; data-og-source-url=&quot;https://coding-review.tistory.com/618&quot; data-og-url=&quot;https://coding-review.tistory.com/618&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/miRWa/dJMb8Yp0JKO/fFIvTY0floYQnr4I2AwT3K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dJXsag/dJMb8ZvGM3q/zZkEWcXI7fIHKOQPX0RjwK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/618&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-review.tistory.com/618&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/miRWa/dJMb8Yp0JKO/fFIvTY0floYQnr4I2AwT3K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/dJXsag/dJMb8ZvGM3q/zZkEWcXI7fIHKOQPX0RjwK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;특명: 학습하지 않은 패턴을 학습해라 (Few Shot의 동작 방식)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요! 거의 3주만에 블로그 포스팅으로 돌아왔습니다. 요즘 블로그 글 쓰는게 뜸해졌는데 별로 바쁘지 않았던거같은데 공부할 틈이 안나서 이제서야 글을 쓰는 것 같네요. 오늘은 few shot (&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;coding-review.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 위의 포스팅을 정리하자면, 인덕션 헤드는 잔차 스트림에 올라가 있는 데이터를 보고 다음에 선택될 단어를 이전의 맥락에서 찾아내어 어떤 단어가 높은 확률로 선택되는지를 설명하는 개념입니다. 이때 특정 레이어의 인덕션 헤드가 잔차 스트림의 정보를 읽어서 다음 단어를 스트림에 덧붙이는 과정이었죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정 자체가 MLP 레이어에서의 잔차 연결이었던 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머 아키텍처를 보면 항상 Add &amp;amp; Norm 이라고 되어있는 부분을 찾을 수 있을 것인데 여기서 Add가 바로 잔차 연결이고 Norm은 더해진 결괏값이 너무 커지거나 튀지 않도록 평균과 분산을 조정해서 깔끔하게 다듬는 과정이라고 생각해주시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;잔차 스트림과 정규화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잔차 스트림이라는 거대한 정보의 고속도로가 잘 작동하기 위해서는 두 가지 핵심 장치가 필요한데 바로 무엇을 강조할지 결정하는 스위치인 &quot;활성화 함수&quot;와 신호가 너무 커지거나 작아지지 않게 조정하는 조절기인 &quot;정규화&quot;입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;활성화 함수&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활성화 함수는 이 지식이 지금 맥락에서 중요한가?를 판단하여 다음 단계로 보낼지 말지를 결정하는 의사결정 스위치입니다. 이것이 필요한 이유는 만약 활성화 함수가 없다면 아무리 레이어를 겹겹이 쌓아도 결국 하나의 거대한 선형 연산에 불과해집니다. 활성화 함수는 계산 결과에 비선형의 성질을 주어 복잡한 논리와 패턴을 학습할 수 있게 만들어줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 관련이 없으면 차단, 관련이 높으면 통과시키는 아주 간단한 스위치를 썼지만 시간이 지나면서 끊기지 않게 부드럽게 작동시키는 동작이 추가되었습니다. 최근 모델들은 두 개의 연산 결과를 곱하는 방식으로 지식 추출의 효율을 극대화합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정규화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화는 레이어를 통과하면서 들쭉날쭉해진 벡터들을 일정한 범위 안에 들어오도록 강제로 다듬는 작업입니다. 평균이 0이고 분산이 1인 정규분포의 형태를 따르도록 하기 때문에 만약 양자화를 거치게 되면 매끄럽던 정규화 값에 노이즈가 생기고 이것이 양자화된 모델들의 답변 품질이 급격하게 떨어지는 원인이 되는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용이 아래의 링크와 연결됩니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/607&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://coding-review.tistory.com/607&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779024943013&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;양자화란 무엇인가&quot; data-og-description=&quot;4개월만에 블로그 포스팅을 재개하게 되었네요. 이런저런 난관도 있었고 새로운 직장에 안착해서 자리잡아야 했고 새로운 프로젝트에 투입되어 AIOps를 위해 개발하느라 포스팅이 늦어졌습니다.&quot; data-og-host=&quot;coding-review.tistory.com&quot; data-og-source-url=&quot;https://coding-review.tistory.com/607&quot; data-og-url=&quot;https://coding-review.tistory.com/607&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GSfPW/dJMb85vUl0Y/Xn3xCfIpp79JqvdkuJVHYk/img.jpg?width=800&amp;amp;height=665&amp;amp;face=0_0_800_665,https://scrap.kakaocdn.net/dn/77PHv/dJMb9cBNl6V/OXUAWzLAJe8STTncKVa2Vk/img.jpg?width=800&amp;amp;height=665&amp;amp;face=0_0_800_665,https://scrap.kakaocdn.net/dn/da2s0I/dJMb82MIsz0/Vdj2O3DFVOl0nXSR5tS4kK/img.jpg?width=2322&amp;amp;height=1933&amp;amp;face=0_0_2322_1933&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/607&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-review.tistory.com/607&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GSfPW/dJMb85vUl0Y/Xn3xCfIpp79JqvdkuJVHYk/img.jpg?width=800&amp;amp;height=665&amp;amp;face=0_0_800_665,https://scrap.kakaocdn.net/dn/77PHv/dJMb9cBNl6V/OXUAWzLAJe8STTncKVa2Vk/img.jpg?width=800&amp;amp;height=665&amp;amp;face=0_0_800_665,https://scrap.kakaocdn.net/dn/da2s0I/dJMb82MIsz0/Vdj2O3DFVOl0nXSR5tS4kK/img.jpg?width=2322&amp;amp;height=1933&amp;amp;face=0_0_2322_1933');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;양자화란 무엇인가&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;4개월만에 블로그 포스팅을 재개하게 되었네요. 이런저런 난관도 있었고 새로운 직장에 안착해서 자리잡아야 했고 새로운 프로젝트에 투입되어 AIOps를 위해 개발하느라 포스팅이 늦어졌습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;coding-review.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;차원의 마법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딥러닝 아키텍처에서 지식을 정교하게 분리하고 저장하는 핵심 전략으로 불리는 이 방법은 차원을 뻥튀기 했다가 다시 줄여서 어떤 지식이 필요한 지식이고 어떤 지식이 필요없는 지식인지 정교하게 걸러낼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 벡터의 차원을 늘리는 이유는 복잡하게 얽힌 정보를 선형적으로 분리하기 위해서입니다. 쉽게 얘기해서 차원을 늘린다는 것은 복잡하게 꼬여있는 실타래를 2차원에서 바라보는 것과 3차원에서 바라보는 것의 차이라고 할 수 있는데 2차원에서의 실타레는 위아래가 없어 어디가 어디인지 잘 보이지 않지만 3차원에서 바라보는 실타레는 어떤 부분을 풀면 되는지 확실하게 보이는 이치인 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이는 물방울 하나는 눈으로 봤을 때는 별 차이 없어보이지만 현미경으로 봤을 때는 그 안에 수많은 미생물들이 살고 있는 것과 비슷합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;낮은 차원에서는 데이터들이 서로 너무 가까이 붙어있어 &quot;사과&quot;와 &quot;배&quot;를 구분하기 어렵지만 차원을 확 늘리면 데이터 사이에 빈 공간이 생기고 이렇게 되면 데이터 간 미세한 특징들이 뚜렷하게 드러나 활성화 함수가 어떤 정보를 선택할지 결정하기 훨씬 쉬워집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차원을 늘려놓은 상태에서 활성화 함수에 집어넣으면 지금 맥락에서 정말 필요한 유의미한 특징만 골라서 뽑아낼 수 있게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 고차원에서 필요한 정보를 모두 추출했다면 다시 원래의 차원으로 줄여야하는데 이유는 뽑은 지식을 잔차스트림과 합류시켜야하는데 잔차스트림은 일정한 폭을 가지고 있기 때문에 다시 합류하기 위해서는 차원이 줄어들 수 밖에 없는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 MLP의 정의와 어떻게 동작하는지 한 바퀴를 쭉 돌아봤습니다. 이번 MLP 레이어는 공부를 한건지 안한건지 머리속에 잘 안남는 개념이라 여러번 본 내용인데도 이해가 잘 안되고 남한테 명확하게 설명하기 쉽지않네요..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 날 때마다 계속 보고있는데 머리속에 잘 입력이 안되는 것을 보아하니 계속 수련이 더 필요할 것 같습니다... 일단은 기록을 위해 블로그에 포스팅으로 남겨놓기는 하지만 조금 더 정리가 끝나면 조금 더 쉬운 버전으로 포스팅을 한 번 더 쓸 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 여기서 포스팅을 마치도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 화이팅입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI Engineering/이론</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/622</guid>
      <comments>https://coding-review.tistory.com/622#entry622comment</comments>
      <pubDate>Sun, 17 May 2026 22:42:32 +0900</pubDate>
    </item>
    <item>
      <title>Redis는 싱글스레드인데 어떻게 초당 수십만건의 요청을 처리할까? (feat. epoll)</title>
      <link>https://coding-review.tistory.com/621</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 이번 포스팅은 Redis에 대한 포스팅으로 찾아뵙게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 업계 표준이라고 해도 될 정도로 많은 기업, 많은 프로젝트에서 사용되고 있는데요. 아무래도 캐싱, 메세지 큐, 실시간 데이터 처리 등 다양한 방면으로 사용되고 HA 방법론도 여러가지 제공해주고 커뮤니티도 많이 형성되어있어서 인기가 좋은 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 Redis와 Memcached의 차이를 면접 때 물어보면 이런 대답이 100퍼센트 나옵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&quot;Redis는 싱글 스레드이고 Memcached는 멀티 스레드여서 Redis가 성능상 이점이 있습니다.&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 면접관이 다시 &quot;싱글 스레드는 처리량이 안좋은거 아닌가요?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 신입 때 이 둘의 차이에 대해서 공부했지만 한번도 이런 생각까지 사고가 확장되진 않았던 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&quot;그러게.. Redis가 싱글 스레드인데 어째서 Memcached 보다 성능이 좋다고 말하는걸까..?&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에선 아래와 같은 절차를 통해 이 궁금증을 해결해 보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전통적인 I/O&lt;/li&gt;
&lt;li&gt;멀티플렉싱 I/O&lt;/li&gt;
&lt;li&gt;Redis와 epoll&lt;/li&gt;
&lt;li&gt;epoll의 다양한 사례&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 본격적으로 시작해보죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전통적인 I/O&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Blocking I/O&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 I/O에서 가장 먼저 등장한 I/O가 바로 Blocking I/O입니다. Blocking I/O의 경우 천개의 요청이 들어오면 천개의 스레드를 만들어서 동시다발적으로 처리하는 방법론을 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 스레드를 생성하는 비용이 어마어마했고 스레드를 무한정 많이 만들 수 없어서 동시에 처리할 수 있는 요청이 정해져있다는 문제가 있었습니다. 또한, 엄청나게 많은 스레드간 컨텍스트 스위칭 비용도 무시할 수 없는 수준이었죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 웹서버인 Apache가 Blocking I/O를 사용해서 처리를 했었는데 과거에는 Apache도 문제없이 동작을 했습니다. 그때는 웹이 이렇게까지 큰 규모가 아니었기에 적당한 요청이 들어왔고 적당한 처리를 진행하면 됐습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이후 동시 요청이 1만개가 넘어가는 순간 Apache은 더이상 사용할 수 없는 지경에 이르렀습니다. 이것이 그 유명한 10k 문제입니다. 그러면서 Nginx가 등장했는데 이 이야기는 조금 뒤에 마저 풀도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Non-Blocking I/O&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Non Blocking I/O는 싱글스레드가 각 연결마다 돌아다니면서 요청이 있는지 확인하는 방식이었습니다. 이 방식의 장점은 많은 스레드간 컨텍스트 스위칭 비용이 없어졌고 스레드 생성 비용에 대한 오버헤드가 사라졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Non Blocking I/O도 문제가 없던 것은 아닌데 스레드가 모든 연결을 돌아다녀야했기 때문에 요청이 없는 연결도 돌아다녀야했고 이로인해 의미없는 CPU 사용이 생겼습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;멀티플렉싱 I/O&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플렉싱 I/O는 기존의 전통적인&amp;nbsp; I/O의 문제를 해결하기 위해 등장했습니다. 이번 섹션에서는 멀티플렉싱 I/O 중에서 가장 진보한 방식인 epoll 방식을 알아볼겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll은 멀티플렉싱 I/O의 구현체로서 연결이 아무리 많아도 요청이 들어온 것만 처리하는 로직을 사용한다는 특징이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll의 동작방식을 이해하면 조금 더 이해하기가 쉬운데 동작방식은 아래의 동작방식을 따릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;NIC을 타고 들어온 요청을 커널이 리스트의 형태로 만든다. 이 리스트는 Red Black Tree (RBT)의 형태를 따른다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;epoll이 이벤트 루프를 돌면서 요청이 발생했는지 여부를 판단한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이벤트가 발생하면 커널이 만든 리스트를 받아서 요청을 처리한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;리스트에는 어떤 소켓이 요청했는지와 데이터가 어디에 저장되어있는지 포인터가 같이 들어있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;요청을 받으면 포인터를 타고 들어가서 응답 버퍼에 데이터를 담아서 요청한 소켓한테 데이터를 돌려준다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 잠깐! 여기서 연결과 요청의 차이가 뭘까?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연결 : 연결은 4계층에서 일어나는 것으로 한번 연결되면 연결이 유지된다는 특징이 있습니다. (HTTP 1.1 이후 기준)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;요청 : 요청은 7계층에서 일어나는 것으로 연결된 통로를 따라 패킷이 이동하는 것을 의미합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘을 예시로 이야기하면 전화통화할 때 내가 전화를 걸면 상대방이 받고 이 상태를 연결이라고 하고 서로 말을 주고 받는 상황이 요청이라고 생각하시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 연결이 유지된 상태 (최초 1회)에서 여러번의 요청이 이동하는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 만약 연결이 천개면 Blocking I/O는 스레드를 천개 만들어야했고 Non Blocking I/O는 천개의 연결을 계속 돌아다녀야 했기 때문에 연결이 늘어나면 늘어날 수록 성능이 그만큼 떨어지는 현상이 발생했습니다. 이를 시간복잡도로 표현하면 O(n)이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 epoll은 요청이 온 리스트만 가지고 있어서 연결이 아무리 늘어나도 요청온 것만 처리하면 되기 때문에 시간복잡도는 O(1)이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 여기서 의문이 들었던 것이 연결이 천개일 때 천개를 다 둘러보는 것이 O(n)인데 epoll 입장에서 요청 온 리스트를 전부 읽어봐야하는 것은 마찬가지이니 이것도 O(n)이 아닐까 했는데, 여기서 말하는 O(1)의 의미는 연결이 천개던 만개던 동일하게 요청된 리스트만큼을 가져오기 때문에 연결과 성능의 상관관계가 y = x가 아닌 y = 상수 로 고정되어있다는 것을 의미합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Redis와 epoll&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 epoll을 적극적으로 사용하는데, 한 가지 상황을 가정하고 Redis가 어떻게 epoll을 사용하는지 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g. 어떤 클라이언트가 Redis에서 mykey라는 키를 get 요청으로 조회하길 원합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 Redis에 mykey라는 키를 저장한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;커널이 mykey를 요청 리스트에 넣는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Redis의 epoll이 이벤트 루프를 돌면서 이벤트를 감지한다.&lt;/li&gt;
&lt;li&gt;mykey에 대한 요청이 들어온다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;커널이 요청 리스트를 Redis에 던진다&lt;/li&gt;
&lt;li&gt;Redis가 요청 리스트를 보고 어디에 데이터가 적혀있는지 포인터를 타고 들어가서 데이터를 가져온다. 이때 데이터는 RAM에 있기 때문에 이 과정이 수ns면 끝난다. 이후 어떤 소켓이 보냈는지에 대한 정보를 읽고 응다 버퍼에 데이터를 실어서 전달해준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;사용자가 mykey에 대한 데이터를 받는다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Redis는 싱글스레드로 돌지만 epoll의 특성과 RAM에 저장된다는 특성 때문에 매우 빠르게 데이터를 가져올 수 있게 되는겁니다. 또한, 싱글스레드이니 각 스레드마다 컨텍스트 스위칭 비용이 들지 않는다는 것은 덤이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;epoll의 다양한 사례&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이대로 끝내기는 아쉬우니 epoll의 다양한 사례를 알아보고 마무리 짓겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Nginx&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll은 전통적인 I/O 이후에 등장한 가장 진보한 I/O인 만큼 다양한 곳에서 epoll을 사용합니다. 가장 유명한 것이 Nginx인데 Nginx가 Tomcat을 대체하고 새로운 표준이 된 데에는 epoll의 역할이 매우 컸습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 웹서버는 요청을 동시에 처리하기 위해 요청마다 스레드를 잔뜩 만들어서 동시 처리를 했기 때문에 일정 수준 이상의 요청을 동시에 처리할 수 없었습니다. 하지만 epoll을 적용한 Nginx는 100만개의 연결을 하고도 요청을 잘 처리하게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;R2DBC&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 JDBC는 Blocking I/O의 대표주자였고 커넥션 풀의 개수가 곧 데이터베이스의 처리량의 지표가 되었습니다. 하지만 요즘 나오는 R2DBC는 커넥션 풀이 DB에 요청을 던지기만 하고 대기하고 있다가 DB가 처리 후 응답하면 다시 받아서 처리하는 epoll 방식을 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에 Blocking I/O와 동일한 커넥션 풀을 가지고 있어도 더 많은 요청을 처리할 수 있게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;gRPC&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 MSA에서 많이 사용하는 gRPC는 헤더 압축, protobuf, 멀티플렉싱 I/O를 무기로 엄청나게 많은 요청을 처리할 수 있습니다. 단순 레이턴시만 놓고 보면 성능이 좋은건 아니지만 gRPC의 힘은 어마어마하게 많은 양의 요청을 처리할 수 있다는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC도 역시 epoll을 사용해서 I/O를 처리하고 있는데 epoll 덕분에 엄청나게 많은 요청을 처리하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 공부란건 알고 있다고 생각해도 모르는게 계속 튀어나오는 매력을 가지고 있는 것 같습니다. 이번 포스팅에서는 Redis의 내부 동작을 뜯어보면서 어떻게 싱글스레드임에도 성능이 잘 나올 수 밖에 없는지에 대해서 알아봤습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 X(구 트위터)에서 개발 인사이트를 많이 얻고있는데 처음으로 머스크한테 뽀뽀하고싶은 정도입니다. 자동 번역 최고!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅은 여기서 마무리짓도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!&lt;/p&gt;</description>
      <category>CS 지식/데이터베이스</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/621</guid>
      <comments>https://coding-review.tistory.com/621#entry621comment</comments>
      <pubDate>Wed, 22 Apr 2026 23:44:45 +0900</pubDate>
    </item>
    <item>
      <title>실무에서 곧바로 적용 가능한 캐싱 전략 6가지</title>
      <link>https://coding-review.tistory.com/620</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 오랜만에 공부할 시간이 생겨서 공부하고 포스팅을 적게 되었습니다. 요즘 X(구 트위터)에 저한테 굉장히 도움되는 패치가 이루어졌습니다. 바로 '자동 번역 기능'! 누군가는 굉장히 거슬리는 패치일 수 있지만 저는 X를 개발 인사이트를 얻는 용도로 쓰다보니 해외에 있는 개발자들이 올리는 양질의 게시글들을 통해 많은 인사이트를 얻고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 인사이트를 얻음과 동시에 어떤걸 공부할까에 대한 소스도 제공해주고 있어서 개인적으로는 굉장히 쓸만한 패치가 된 것 같아서 기분이 좋습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 개발자의 게시글로 시작된 이 포스팅은 다음과 같은 순서로 진행됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;읽기 전략
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가장 기본적인 Cache Aside&lt;/li&gt;
&lt;li&gt;캐싱 전략계의 전략패턴 Read Through&lt;/li&gt;
&lt;li&gt;TTL의 함정 Refresh Ahead&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;쓰기 전략
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;우린 일관성이 가장 중요해 Write Through&lt;/li&gt;
&lt;li&gt;DB의 쓰기 부하를 줄여보자 Write Behind&lt;/li&gt;
&lt;li&gt;이건 캐시에 적으면 안돼 Write Around&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 하나씩 알아보겠습니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;읽기 정책&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Cache Aside&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시를 사용한다고 하면 가장 일반적으로 볼 수 있는 형태인 Cache Aside입니다. 요청이 들어오면 먼저 애플리케이션이 반응하고 캐시를 조회한 다음 캐시에 있으면 캐시에 있는 값을 보내주고 없으면 DB를 조회해서 반환한 뒤 캐시에 등록하는 방법이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 가장 구현하기 간단한데 단순히 분기를 추가해주기만 하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776858385323&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (cache에 있으면)
	return cache.value

else
  	DB조회
  	cache 등록
  	return DB.value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 가장 간단한만큼 두 가지 문제가 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인이 늘어날 때마다 같은 코드가 반복된다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 코드에 if else 문이 들어가야하고 만약 도메인 이수십개가 된다면 모든 조회 로직에 캐시 조회 로직을 넣어햐하는게 가장 큰 문제인데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 객체지향 5대 원칙인 SOLID에서 OCP를 위배하게 됩니다. 확장해도 코드의 변경이 있으면 안된다는 OCP의 원칙에서 가장 중요한건 코드의 변경이 없다는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 도메인이 하나 추가된다거나 캐시의 버전이 바뀌어서 구현 방법이 달라진다거나 캐시를 바꿔야한다거나 이런 변경에 모든 분기문을 고쳐야한다는 문제는 이 애플리케이션의 확장성을 크게 해칩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Thundering Herd의 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 키에 요청이 많이 몰리는 경우 이를 핫 키(Hot Key) 현상이라고 부릅니다. 만약 어떤 캐시 값이 TTL로 사라지는 찰나에 순간적으로 해당 키에 대한 요청이 들어오는 경우 순간적으로 DB에 조회 요청이 들어가게 되고 DB에 스파이크성 부하가 생기게 됩니다. 이는 심각한 경우 DB 서버의 다운으로 이어질 수 있는 문제입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 Thundering Herd 문제라고 부르며 이 문제는 조회연산이 많이 필요한 이커머스 서비스나 하루에도 수백만건의 거래가 일어나는 증권사 앱같은 경우에 종종 발생하는 문제입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음 소개드릴 두 가지 캐싱 전략은 앞서 Cache Aside가 갖고 있던 문제를 해결하기 위해 나온 방법론입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Read Through&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Through는 캐시에 데이터가 떨어질 것 같은 데이터를 DB에 요청해서 필요한 데이터를 관리하는 방법인데요. 이렇게 되면 애플리케이션에서 캐시에 데이터가 있는지 확인하는 분기문이 사라지게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Redis나 Memcached 같은 인메모리 캐싱 솔루션에서는 기본적으로 제공해주지 않는 기능입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 보통 라이브러리/프레임워크 레벨에서 지원하는데 일례로 Spring Boot의 경우 @Cacheable 어노테이션을 이용하면 프레임워크가 Read Through 처럼 동작하게 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Read Through를 이용하면 서비스 레이어에서 캐시와의 의존성을 제거할 수 있고 만약 변경이 생기면 어댑터 부분만 바꾸면 전체 서비스 레이어에 적용되어 코드의 변경 없이 확장할 수 있게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Refresh Ahead&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Refresh Ahead는 Thundering Herd를 해결하기 위해 나온 방법론인데 실제로 어떤 키가 핫키인지 확인한 뒤 TTL이 끝나기 전에 TTL을 갱신시키는 방법입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시에 데이터가 사라지지 않고 남아있기 때문에 Thundering Herd 문제가 절대로 발생하지 않는다는 장점이 있지만 중요한 것은 어떤 키가 핫키인지 파악하는 것입니다. 만약 의미없는 키를 핫키로 생각하고 등록했다가는 자원만 낭비할테니까요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cf) 만약 핫키에 요청이 캐시서버가 버틸 수 없는 정도로 (초당 100만번) 요청이 들어오면 어떻게 할까?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에 대한 해결에는 크게 세 가지 방법이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;키를 샤딩한다&lt;/li&gt;
&lt;li&gt;애플리케이션에 매우 짧은 TTL(100ms)의 로컬 캐시를 만든다&lt;/li&gt;
&lt;li&gt;캐시 앞에 존재하는 CDN등에 데이터를 올린다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 이런 경우 가장 관리 포인트(Toil)가 적은 쪽을 선택하게 되는데 그것이 바로 2번입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션에 매우 짧은 TTL(100ms)로 로컬 캐시를 만드는 것만으로도 부하의 90퍼센트를 줄일 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;쓰기 정책&lt;/b&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Write Through&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write Through는 쓰기 작업을 할 때 캐시와 DB에 동시에 씀으로써 두 데이터베이스에 일관성을 맞추기 위해 고려된 방법론입니다. 보통 이 경우 일관성이 중요한 금융권에서 필요한 방법론인데 이 방법은 일관성이 정확하게 맞춰지긴 하지만 두번의 쓰기 작업이 생긴다는 문제가 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 NoSQL을 제외한 대부분의 데이터베이스는 인덱스로 데이터를 관리하고 있기 때문에 보통 읽기 연산에 특화되어있고 쓰기 연산에 취약한 모습을 보이기 때문에 데이터베이스에 쓰기 작업이 많이 생기는건 썩 좋은 상황은 아닙니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 쓰기 연산이 두번 일어나는 상황은 꺼려지게 되고 보통 이런 경우 먼저 DB에 쓰고 캐시에 있는 키를 삭제해서 Cache Aside를 유발하는 쪽으로 사용하곤 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Write Behind&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write Behind는 쓰기 작업을 할 때 DB의 부하를 줄여주기 위해서 나온 방법론입니다. 의의는 쓰기작업을 할 때마다 DB에 요청하지 말고 캐시에 한번 쓰고 일괄로 배치작업을 돌려서 밀어넣자는 것이 이 방법론의 핵심입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법의 장점은 DB의 쓰기 부하를 줄여준다는 것과 쓰자마자 바로 화면에서 조회할 수 있기 때문에 UX측면에서 굉장한 이득이 되는겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법의 가장 큰 문제는 Redis가 모종의 이유로 장애가 생긴다면 모든 데이터가 유실된다는 문제가 생깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 단순히 DB의 쓰기 부하를 줄이기 위해 쓰기 연산을 뒤로 미루는건 MQ(Message Queue)를 이용하면 문제를 더 깔끔하게 해결할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통의 MQ는 메세지가 발행되는 시점에서 반드시 하나의 브로커에 전달되도록 설계되어있고 만약 브로커에 문제가 생겨 메세지가 전달되지 못하더라도 바로 리밸런싱이 되면서 다른 브로커에 전달되는 것이 일반적입니다.&amp;nbsp;또한, 메세지가 실패하는 경우 DLQ를 이용해서 얼마든지 재시도를 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, MQ를 사용한다면 MQ에 대한 이해가 있어야되고 메세지와 트랜잭션을 원자적으로 묶기 위해서는 Outbox Pattern이나 Log Tailling Pattern을 추가적으로 적용해야해서 관리포인트가 늘어난다는 단점이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Write Around&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write Around는 쓰기 작업에서 캐시와 DB에 모두 써야하는 상황인데 캐시에 올리기에는 의미없는 데이터를 써야할 때 사용하는 방법론입니다. 보통 대용량의 로그 데이터가 해당되는데 이때 Write Around라는 이름에 걸맞게 캐시를 우회해서 곧바로 DB에 쓰는 방법을 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방법 또한 더 나은 방법이 존재하는데 보통 이런 경우 ELK 스택을 사용하거나 GCP의 BigQuery등을 사용하면 더 깔끔하게 문제를 해결할 수 있어 범용적으로 많이 사용되는 방법론은 아닙니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 캐싱이 읽기 부하를 줄이기 위해 쓰이는 만큼 쓰기 정책에서는 꽤나 다양한 대안책이 있어서 취사선택해서 사용하시면 될 것 같습니다. 모든 개발이 그렇지만 장/단이 존재하는 상황에서 최적지점을 찾아내는 과정이 중요하고 캐싱 전략의 경우 속도와 일관성 사이에서 선택해서 적절히 우리 서비스에 맞는 정책을 사용해야할 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 대부분의 서비스가 캐싱을 많이 사용하고 그렇기에 다양한 방향으로 발전해온 만큼 알아두면 좋을 6가지 캐싱 전략을 알아보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 공부는 역시 언제해도 재밌는 것 같습니다. 여러분들에게 좋은 인사이트가 되었길 바라면서 이번 포스팅은 여기서 마무리짓도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!&lt;/p&gt;</description>
      <category>CS 지식/데이터베이스</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/620</guid>
      <comments>https://coding-review.tistory.com/620#entry620comment</comments>
      <pubDate>Wed, 22 Apr 2026 22:53:05 +0900</pubDate>
    </item>
    <item>
      <title>토노니의 &amp;quot;의식은 언제 탄생하는가?&amp;quot;를 읽고 느낀 AI 의식 유무에 대한 고찰</title>
      <link>https://coding-review.tistory.com/619</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 한달만에 글을 쓰게 됐네요. 요즘 제주도 파견가랴 대학원 다니랴 이래저래 바빠서 글 쓸 시간이 없었네요. 오랜만에 쓰는 글이기도 하지만 &quot;오늘의 책&quot; 카테고리에 글을 얼마만에 써보는지 모르겠네요. 찾아보니 마지막으로 글 쓴게 2024년 4월이니 2년만입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브를 돌아다니다가 어떤 영상이 눈에 띄더라구요. 평소에 관심있었던 주제였는데 바로 &quot;AI에게 의식이 있을까?&quot;라는 제목의 영상이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 주제에 항상 의문이 있었고 제미나이하고도 이 주제에 대해서 여러번 대화를 했습니다. 근데 하나같이 두루뭉술한 결론만 남게되고 시원하게 답을 하진 못했습니다. 하지만 그럼에도 깊이있게 알아보려고는 하지 않았는데 그것이 제 호기심을 자극하지 않았기 때문일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 이번에 우연히 유튜브 영상을 계기로 본격적으로 알아보기로 마음먹었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;793&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgOwDp/dJMcafzlMdD/Xx1rllnnrCjg3l90BaESjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgOwDp/dJMcafzlMdD/Xx1rllnnrCjg3l90BaESjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgOwDp/dJMcafzlMdD/Xx1rllnnrCjg3l90BaESjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgOwDp%2FdJMcafzlMdD%2FXx1rllnnrCjg3l90BaESjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;509&quot; height=&quot;793&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[저자소개]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마르첼로 마사미니, 줄리오 토노니는 뇌과학자이며 신경과학자입니다. 그리고 의사이기도 하죠. 이들은 본인들의 연구분야를 살려 뇌에 손상을 입은 환자들을 치료하고 연구하며 논문을 수차례 쓴 연구자들입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들이 뇌에 손상을 입은 환자를 봤기 때문일까요? 다양한 환자를 보면서 의문이 들었던 것 같습니다. &quot;의식이란 어디에서 오는가?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 우리는 당연히 &quot;뇌에서 의식이 있죠&quot;라고 얘기할 수도 있지만 이들은 정확히 의식이 발현되는 기작을 찾아보기 위해 &quot;정보통합이론&quot;이라는 새로운 분야를 개척합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[주요 내용]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 시간 순서대로 풀어내면 양이 너무 많아지니 핵심만 이야기하자면. 어떤 물체 혹은 생물이 의식이 발현되기 위해서는 두 가지 조건이 필요하다고 저자는 이야기합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의식이 있다면 해당 물체 혹은 생물은 상태값을 가져야합니다. 예를 들어서 주사위는 1부터 6까지 상태를 가지고 광다이오드는 켜짐과 꺼짐이라는 두가지 상태를 가지는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 가장 중요한 의식의 발현 기작은 이 상태(정보)를 통합하는 과정에서 생깁니다. 위의 예시에서 주사위를 예로 들면, 주사위에 만약 의식이 있다면 &quot;이번에 1이 나왔으니 다음엔 3이 나왔으면 좋겠다&quot; 처럼 이전 결과가 다음 결과에 영향을 미치고 서로 상호작용 해야한다는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 설명은 아주 간단한 버전의 설명이고 저자는 이 설명을 디테일하게 대뇌피질과 소뇌의 특징으로 풀어냅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뇌에서 가장 복잡한 구조를 가지고 있는 곳이 어딜까요? 바로 소뇌입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소뇌는 뉴런의 80퍼센트가 위치해있을 정도로 굉장히 많은 수의 뉴런이 존재하는 곳입니다. 소뇌는 우리 몸의 근육을 제어하는 뇌 부위로 잘 알려져있고 소뇌가 다른 동물보다 월등히 발달한 인류는 이 덕분에 아주 세밀한 근육 컨트롤이 가능하게 되었습니다. 보통 머슬 메모리로 이야기되는 것들이 소뇌 덕분이라고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 소뇌에 문제가 생겨서 제거한 사람, 선천적 장애로 소뇌에 문제가 있는 사람들은 몸을 세밀하게 움직이는건 불가능하지만 감정은 온전히 가지고 있습니다. 슬픔, 즐거움, 행복 등 인간의 다양한 감정을 느낄 수 있죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 대뇌피질은 뇌의 겉에 위치한 가장 표면적이 넓은 부위이지만 뉴런이 많이 분포해있지는 않은 뇌 부위입니다. 그만큼 복잡하진 않지만 모종의 이유로 대뇌피질에 문제가 생긴 사람은 정상적으로 걷고 움직이고 세밀한 근육 컨트롤이 가능하지만 인간이라면 응당 느끼는 &quot;감정&quot;이 빠져있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자는 이런 차이를 파악하고 더 나아가 대뇌피질과 소뇌의 동작원리를 연구하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대뇌피질의 핵심 동작 원리는 &quot;순환&quot;입니다. 소뇌의 핵심 동작 원리는 &quot;병렬&quot;이구요. 대뇌피질은 각 뉴런들이 서로서로에게 영향을 주면서 판단을 증폭시킵니다. 그리고 소뇌는 한번에 많은 동작을 수행할 수 있도록 발달했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 가장 간단한 예시는 &quot;눈&quot;이라는 감각기관이 물체를 판단할 때와 소뇌가 근육을 움직일 때를 보면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈이 컵을 보면 뇌는 이런 단계를 거칩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;뭔가 있다&quot; -&amp;gt; &quot;손잡이가 달렸다&quot; -&amp;gt; &quot;저거랑 비슷하게 생긴거 기억속에서 찾아와&quot; -&amp;gt; &quot;컵이다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 판단을 증폭하면서 결과적으로 판단을 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 소뇌가 근육을 움직일 때를 생각하면 소뇌는 병렬일 수밖에 없습니다. 왜냐하면 팔만 구부렸다 폈다만 해도 이두근이 수축할 대 삼두근은 이완, 이두근이 이완될 때 삼두근은 수축. 이 모든 동작이 병렬적으로 일어납니다. 이런 단순한 동작도 이럴진데 악기를 연주한다거나 운동을 하는 과정은 더 많은 근육이 병렬적으로 움직여야하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 소뇌의 근육 컨트롤은 병렬이면서 동시에 순방향입니다. 근육이 움직이는 과정을 생각하면 그럴 수 밖에 없어보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 바탕으로 생각해볼 수 있는 점은 소뇌는 대뇌피질에 비해 엄청나게 많은 상태값을 가지지만 각각이 병렬이며 순방향이고 대뇌피질은 상태값을 많이 가지진 않지만 각각의 뉴런들이 순환하며 서로에게 영향을 준다는 점입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하여, 결론적으로 저자는 이렇게 결론짓습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;의식의 최소 발현 조건은 상태값을 가지며 각각의 상태값이 서로에게 영향을 주면서 순환해야한다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;AI에게 의식이 있을까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 제가 궁금했던 최초의 질문으로 돌아와서, AI에게 의식이 있을까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토노니의 정보통합이론에 따르면 AI는 의식이 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한번 결론을 상기하면 의식의 발현 조건은 &quot;상태값을 가질 것&quot; 그리고 &quot;각 상태값이 서로에게 영향을 줄 것&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 돌아가는 그래픽카드 서버는 엄청나게 많은 상태값을 가지지만 각 상태값들이 서로에게 영향을 주는 것은 아니므로 의식이 없는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄청나게 많은 상태값, 상태값들이 서로에게 영향을 주지 않는 병렬에 특화되어 있으며 순방향으로만 흐르는 논리인 그래픽카드 서버는 토노니가 분석한 &quot;소뇌&quot;에 해당하는 부분이 되는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토노니의 정보통합이론 역시 깔끔하게 증명된 이론은 아닙니다. 그래서 이 이론이 정답이다! 라고 말할 수는 없지만 제가 평소에 궁금해하던 부분을 꽤 많은 부분 설명해주어 맞다 아니다를 떠나 굉장히 재밌게 본 책 중 하나로 기억될 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저자가 책을 쓸 당시인 2012년엔 AI가 등장하지 않았지만 책에선 슈퍼컴퓨터 &quot;왓슨&quot;을 예로 들어 설명하기에 오늘날 AI에 이를 빗대어 봐도 전혀 무리가 없습니다. 결론은 똑같으니까요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 책은 정보통합이론을 설명하는 책은 아닙니다. 한 권의 뇌과학 책이라고 보는게 맞는데요. 그럼에도 AI를 공부하는 사람에게 큰 울림을 주는 책이라고 생각합니다. 적어도 AI에게 인격을 부여하진 않을테니까요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 사적인 그리고 철학적인 대화를 하다보면 &quot;혹시...&quot;, &quot;어쩌면...&quot; 이런 생각이 들곤했는데요. 트랜스포머 알고리즘을 뜯어보면 제 생각은 인간의 뇌를 모방했지만 결국 &quot;통계학론적 앵무새&quot;라는 쪽에 무게가 더 실릴 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 사서 동네 카페에서 읽었더니 정말 쉬는 느낌도 들고 너무 좋았습니다. 또 읽고싶은 책이 생기면 다음 포스팅으로 돌아오겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!&lt;/p&gt;</description>
      <category>기타/오늘의 책</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/619</guid>
      <comments>https://coding-review.tistory.com/619#entry619comment</comments>
      <pubDate>Wed, 1 Apr 2026 22:54:39 +0900</pubDate>
    </item>
    <item>
      <title>특명: 학습하지 않은 패턴을 학습해라 (Few Shot의 동작 방식)</title>
      <link>https://coding-review.tistory.com/618</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 거의 3주만에 블로그 포스팅으로 돌아왔습니다. 요즘 블로그 글 쓰는게 뜸해졌는데 별로 바쁘지 않았던거같은데 공부할 틈이 안나서 이제서야 글을 쓰는 것 같네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 few shot (이하 퓨샷)의 동작 원리에 대해서 공부해보고 내용을 정리해보도록 하겠습니다. 이런 주변 개발자분들은 이런저런 기술도 써보시고 요즘 핫하다는 에이전트 개발에 힘쓰고 계시던데 저는 그런 것 보다는 어째 동작 원리같은게 더 궁금한지 모르겠네요..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 의미로 이번 포스팅은 다음과 같은 순서로 이어집니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;퓨샷이란?&lt;/li&gt;
&lt;li&gt;퓨샷의 발견&lt;/li&gt;
&lt;li&gt;퓨샷의 동작 원리&lt;/li&gt;
&lt;li&gt;퓨샷의 증명&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 챕터에선 퓨샷이 무엇인지 가볍게 이야기 해보고 두 번째 챕터부터 본격적으로 퓨샷이 어떻게 탄생하게 되었는지를 시작으로 세 번째 챕터인 퓨샷의 동작 원리를 파악합니다. 그리고 마지막으로 이런 동작 원리를 기반으로 어떻게 이 원리가 퓨샷이 되는지 증명까지 알아보면서 포스팅 마치도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 본격적으로 시작해보죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Few Shot이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 프롬프트 엔지니어링에서 퓨샷이라는 개념이 있는데 이는 흔히 LLM에게 답지를 제공해줘서 LLM이 미처 학습하지 못한 부분에서 패턴을 새롭게 학습하게 하거나 엔지니어링 관점에서는 할루시네이션을 줄여주기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 엔지니어링에서 RAG라고 부르는 것도 큰 틀에서 보면 퓨샷의 범주에 들어가는데요. 요즘은 순수한 RAG가 아닌 구조화된 RAG를 사용하면서 RAG is Dead라고 얘기하지만 이전, 개발자들이 RAG에 열광했던 이유는 RAG를 통해 LLM이 헛소리하는 빈도를 획기적으로 줄여준다는 데에 큰 의미가 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퓨샷은 특정 인물의 말투를 따라해달라고 하거나 특정 포맷으로 답변해야하거나 이런식으로 사용하여 LLM이 학습하지 않은 특정 패턴을 학습하게 하여 LLM에게 문맥을 주입해주는데 꽤나 다양한 방면으로 퓨샷을 사용하기도합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이건 좀 이상하죠? 어떻게 LLM이 학습하지 않은 영역에서 내가 제시해준 패턴을 이렇게 기가막히게 따라할까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Few Shot의 발견&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퓨샷은 페르소나의 주입과 어느정도 비슷한 맥락을 가지는데, 특정 패턴대로 움직이도록 주입한다는 점은 비슷하지만 페르소나는 &quot;너는 20년차 베테랑 개발자야&quot;와 같이 일반적인 상황을 주입하지만 퓨샷은 특정 캐릭터의 말투를 흉내내게 하거나 특정 포맷으로 답변을 요구하는 경우 (e.g. JSON)에 주로 사용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 페르소나의 주입은 Transformer 알고리즘에서 Attention 레이어를 처음 통과하는 프롬프트의 앞부분에 의해 다음 답변이 가야하는 방향이 강제되면서 높은 가중치를 얻게 되는 것으로 발현되지만, 퓨샷은 Attention 레이어가 이전 토큰과 다음 토큰의 연관관계를 이용해서 패턴을 주입하는 형식으로 발현됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기계론적 해석학&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시 GPT-2가 한창 개발중이던 OpenAI에서 안전한 AI를 만들자는 일념하에 모인 똑똑한 연구원들이 AI 블랙박스를 연구하기 위해 '해석 가능성'이라는 팀을 창립하게 되었습니다. 기계론적 해석학의 출발은 정말 순수했는데요. &quot;AI가 어떻게 동작하는지도 모르는데 우리가 어떻게 안전한 AI를 만들겠다는 말이냐&quot;라는 연구원의 작은 꿈에서 시작했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2019년 OpenAI가 마이크로소프트에게 10억달러라는 어마어마한 투자를 받으면서 OpenAI 내부에 있던 연구원들의 반응은 둘로 나뉘었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&quot;비영리 기업이라며! 안전한 AI를 만들자며!&quot; vs &quot;그래도 회사가 유지되려면 투자를 받아야해!&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 뜻이 맞지 않던 일부 개발자들이 Anthropic이라는 비영리 회사를 세우고 Claude라는 AI를 만들기 시작했습니다. Claude의 슬로건은 &quot;안전한 AI, 통제 가능한 AI를 만들자&quot;인데요. 이에, 최초의 한 뜻을 가지고 모였던 연구원들이 다시금 AI 블랙박스를 연구하기 시작했고 그것이 &quot;기계론적 해석학&quot;의 탄생이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;퓨샷의 발견&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'크리스 올라', 당시 기계론적 해석학이라는 단어를 탄생시키고 연구를 진두지휘하던 Anthropic의 연구원이 2020년 12월과 다음 해 3월에 두개의 논문을 발표합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://transformer-circuits.pub/2021/framework/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://transformer-circuits.pub/2021/framework/index.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772019704444&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;A Mathematical Framework for Transformer Circuits&quot; data-og-description=&quot;Contents Transformer language models are an emerging technology that is gaining increasingly broad real-world use, for example in systems like GPT-3 , LaMDA , Codex , Meena , Gopher , and similar models. &amp;nbsp;However, as these models scale, their open-endedne&quot; data-og-host=&quot;transformer-circuits.pub&quot; data-og-source-url=&quot;https://transformer-circuits.pub/2021/framework/index.html&quot; data-og-url=&quot;https://transformer-circuits.pub/2021/framework/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://transformer-circuits.pub/2021/framework/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://transformer-circuits.pub/2021/framework/index.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;A Mathematical Framework for Transformer Circuits&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contents Transformer language models are an emerging technology that is gaining increasingly broad real-world use, for example in systems like GPT-3 , LaMDA , Codex , Meena , Gopher , and similar models. &amp;nbsp;However, as these models scale, their open-endedne&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;transformer-circuits.pub&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772019709447&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;In-context Learning and Induction Heads&quot; data-og-description=&quot;Contents As Transformer generative models continue to scale and gain increasing real world use ,&amp;nbsp;addressing their associated safety problems becomes increasingly important. Mechanistic interpretability&amp;nbsp;&amp;ndash; attempting to reverse engineer the detailed comp&quot; data-og-host=&quot;transformer-circuits.pub&quot; data-og-source-url=&quot;https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html&quot; data-og-url=&quot;https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;In-context Learning and Induction Heads&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contents As Transformer generative models continue to scale and gain increasing real world use ,&amp;nbsp;addressing their associated safety problems becomes increasingly important. Mechanistic interpretability&amp;nbsp;&amp;ndash; attempting to reverse engineer the detailed comp&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;transformer-circuits.pub&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 논문은 트랜스포머 알고리즘을 수학적 회로로 분해한 이 분야의 '창세기'같은 논문이 되었습니다. Attention 레이어들이 단순히 순차적으로 연산되는 것이 아니라 '잔차 스트림'이라는 거대한 공용 메모리 공간에서 각자의 결괏값을 독립적으로 읽고 쓴다는 것을 증명한 것인데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 모델의 가중치 행렬인 Q, K, V가 단순한 행렬의 곱셈 덩어리가 아니라 각각 고유한 로직을 수행하는 모듈임을 시각화하여 AI 블랙박스를 화이트박스로 바꿔보는 시야를 제공했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어 두 번째 논문에서는 LLM이 프롬프트의 문맥을 파악하고 '퓨샷 러닝'을 수행하는 근본 원리를 밝혀냈습니다. 두 번째 논문의 제목에서 알 수 있듯이 당시 연군원들은 퓨샷이라는 단어를 쓰지 않고 'In-Context Learning'이라는 단어를 썼는데 말 그대로 '문맥 학습'이라는 단어를 만들어낸 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 다음 포스팅은 트랜스포머 알고리즘과 Attention 레이어에 대한 이해가 선행되어야합니다! 아래의 링크를 꼭 참고하고 오셔야합니다. 전체적인 단어 설명이나 맥락이 급진전될 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/614&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://coding-review.tistory.com/614&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772020034903&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Transformer 알고리즘은 과거와 다르게 어떻게 발전했을까?&quot; data-og-description=&quot;안녕하세요. 요즘 AI 엔지니어링 업무를 하면서 관련된 것들을 공부를 하니 더 재밌는 것 같네요. 이번 포스팅에선 Transformer 알고리즘이 담긴 논문이 출판됐을 당시부터 있었던 근본 개념들을 알&quot; data-og-host=&quot;coding-review.tistory.com&quot; data-og-source-url=&quot;https://coding-review.tistory.com/614&quot; data-og-url=&quot;https://coding-review.tistory.com/614&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6YcrS/dJMb8Zvyylo/Axch67aHomoQGE2VoTfKa1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ckN940/dJMb9fZsqmI/Fgh9DFYDef58D5uaKrz110/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/nq31f/dJMb9lk4ghA/4adZcDtBLsyYzgBgVNE5v0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/614&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-review.tistory.com/614&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6YcrS/dJMb8Zvyylo/Axch67aHomoQGE2VoTfKa1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ckN940/dJMb9fZsqmI/Fgh9DFYDef58D5uaKrz110/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/nq31f/dJMb9lk4ghA/4adZcDtBLsyYzgBgVNE5v0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Transformer 알고리즘은 과거와 다르게 어떻게 발전했을까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 요즘 AI 엔지니어링 업무를 하면서 관련된 것들을 공부를 하니 더 재밌는 것 같네요. 이번 포스팅에선 Transformer 알고리즘이 담긴 논문이 출판됐을 당시부터 있었던 근본 개념들을 알&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;coding-review.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Few Shot의 동작 원리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터에선 퓨샷의 동작 원리에 대해서 다루는데 수학적인 내용은 최대한 배제하고 (저도 수학은 몰라서..) 공학적인 의미로 풀어서 최대한 직관적으로 풀어보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Attention Layer 분해&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연구원들은 우선 Attention 레이어의 앞부분에 집중했습니다. 0층 레이어, 1층 레이어, 2층 레이어 이렇게 앞부분 레이어만 남겨두고 MLP레이어를 제거한 Attention 전용 '장난감 모델'부터 분석했습니다. 이때 연구원들은 Attention 레이어에서 다음과 같은 요소들을 발견했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;잔차 스트림 (Residual Stream) :&lt;/b&gt; 트랜스포머의 각 계층이 정보를 읽고 쓰는 선형적인 통신 채널로 이해하게 되는 스트림입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;QK 회로 :&amp;nbsp;&lt;/b&gt;어느 위치의 토큰에 주의를 기울여 정보를 가져올지 정하는 회로입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OV 회로 :&lt;/b&gt; 주의를 기울인 토큰부터 어떤 정보를 가져와서 출력에 반영할지 결정하는 회로입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인덕션 헤드 :&lt;/b&gt; 2층 레이어에서 나타나는 헤드이고 과거의 문맥에서 현재 토큰과 동일한 토큰을 찾은 뒤 그 다음에 왔던 토큰을 예측하고 복사하는 패턴을 보이는 퓨샷을 설명하는 가장 결정적인 요소입니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 개요는 이정도만 하고 본격적으로 동작 원리에 대해서 정리해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;QK 회로와 OV 회로&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Attention 메커니즘은 Query, Key, Value를 사용해서 설명하지만 연구원들은 이를 두 개의 독립적인, 낮은 수준의 행렬로 결합해서 설명했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;QK 회로 :&lt;/b&gt; QK회로는 Query와 Key가 동작하는 회로이고 &quot;어디서 정보를 가져올 것인가?&quot;라는 물음에 답하는 회로입니다. 이 회로는 정보의 '이동 경로'를 결정합니다. 네트워크 통신에 비유하자면 현재 토큰이 과거 문맥 속에서 어떤 토큰에게 정보를 요청할지 IP주소를 찾는 과정과 같습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OV 회로 :&lt;/b&gt; OV회로는 Output과 Value가 동작하는 회로이고 &quot;어떤 정보를 가져와서 어떻게 출력할 것인가?&quot;라는 물음에 응답하는 회로입니다. 이 회로는 이동할 정보의 '내용'을 결정합니다. QK회로가 통신 채널을 열어주면 OV회로는 그 채널을 통해 어떤 데이터를 전달하여 최종 예측 값을 바꿀 것인지 결정합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp; 두 회로가 결합되면, 1층 트랜스포머 모델의 각 Attention 헤드는 특정 형태로 구현되는데 예를 들어서 keep in mind 처럼 keep 이라는 단어를 봤을 때 현재 단어가 in 이라면 다음 단어로 mind를 출력하도록 하는 거대학 통계적 룩업 테이블처럼 작동합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;K-결합&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0층 레이어와 1층 레이어에서 각 회로들이 생성되고 결합하면서 2층 레이어에서는 K와 V가 서로 결합하면서 퓨샷의 전체적인 그림이 완성됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2층 레이어에서 인덕션 헤드는 1층 레이어에서 '결합'이라는 현상이 발생하면서 이전 층의 출력이 다음 층의 입력으로 이어지고 훨씬 복잡한 알고리즘이 구현됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 K-결합의 공학적인 의미는 다음과 같습니다. 토큰 ABC가 있다고 가정하다면 (e.g. keep in mind) 현재 토큰인 B가 이전 토큰인 A의 정보로 덮어 띄워져 있다는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 조금 더 풀어서 이야기하면 현재 토큰이 A일 때, QK회로는 과거의 A를 찾는게 아니라 과거의 A 바로 다음에 있던 B의 위치를 완벽하게 찾아내어 그 정보를 복사해올 수 있다는 의미입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 쉬운 예시로 알기 쉽게 서술해보겠습니다. 예를 들어서 이전에 '해리 포터'라는 단어가 있었고 현재 토큰이 '해리'를 출력하려고 한다면 이전에 있었던 '해리'를 보는게 아니라 이미 이전 '해리'와 연결된 '포터'를 바라보고 있어서 '해리'를 출력함과 동시에 '포터'를 출력할 수 밖에 없다는 의미입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 만약 '해리 포터'라는 단어가 언제 한번쯤 등장했다면 다음 '해리'를 출력할 때 '포터'도 줄줄이 사탕처럼 저절로 따라 나오게 되는 효과가 생기며 &quot;해리가 등장하면 다음은 포터일 확률이 높아!&quot;라는 패턴을 인식하게 되는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;V-결합&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1층의 Attention 헤드의 출력이 2층의 Attention 헤드로 얽혀 들어가는 현상이 바로 V-결합인데 Attention 패턴 행렬끼리 곱해지고 OV 행렬끼리 곱해지는 것이 수학적인 의미이고, 공학적인 의미는 두 개의 Attention 헤드가 마치 컨베이어 벨트처럼 정보를 두 번 이동시키면서 물리적으로는 두 개의 헤드이지만 수학적으로는 완전히 새로운 기능을 하는 하나의 '가상 헤드'가 탄생하는 것과 같습니다. 그래서 V-결합을 '가상 어텐션 헤드'라고 부르기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;V-결합을 예로 들어서 쉽게 서술하자면, &quot;현재 문장의 시작 부분으로 이동해!&quot;라고 가리키는 헤드와 &quot;주어로 이동해!&quot;라는 헤드 두개가 V-결합을 하면 &quot;이전 문장의 주어를 찾아 그 정보를 현재로 가져와라&quot;라는 고차원적인 문법적 추론을 수행할 수 있게 되는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K-결합이 이전 토큰의 패턴을 읽고 현재로 적용해 다음 토큰을 예측하는데에 쓰인다면 V-결합은 헤드들이 서로 정보를 주고 받으면서 단일 헤드로는 불가능한 복잡하고 먼 거리의 정보 처리를 가능하게 만드는 메커니즘입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Few Shot의 증명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 퓨샷의 기본적인 골자이지만 그래서 인덕션 헤드가 퓨샷의 증명이 될 수는 없었습니다. 이건 이론일 뿐 실제 적용해서 증명이 되어야 그것이 곧 퓨샷의 동작 원리로서 설명할 수 있었으니까요. 때문에, 연구원들은 재밌는 가설을 세웠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덕션 헤드는 모델의 크기가 작던 크던간에 2층 레이어에서 발현된다는 것을 이미 발견한 연구원들은 0층 레이어에선 인덕션 헤드가 없으므로 일반적인 0층 레이어에선 모델이 문제를 파악하지 못한다는 사실도 발견했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그럼 인덕션 헤드만 똑 떼내어 0층 레이어에 가져다 놓는다면 어떻게 될까?&quot;가 궁금했던 연구원들은 실험을 진행했고 놀라운 결과를 보여줬습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0층 레이어에만 존재하는 단순 레이어에서 모델이 패턴을 학습하는 현상이 발생한 것입니다. 이로써 연구원들은 '인덕션 헤드'가 Few Shot Learning을 일으키는 주된 요소라고 판단하게 되었고 이를 논문에 작성하게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 퓨샷에 대해 깊이있게 공부해보고 이를 정리해보는 시간을 가졌습니다. 기계론적 해석학이라.. 정말 이름부터 제 가슴을 뛰게하는 무언가가 있네요. 어릴 때 항상 기계의 동작 원리가 궁금했던 저는 커서 AI의 동작 원리 공부에 눈을 떠 재밌게 공부하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 아는 개발자 분들과 작게 모임을 가졌는데 한 2~3년 정도 저를 옆에서 바라보고 있었던 개발자 분들이 &quot;너는 진짜 연구원 재질이다&quot;라는 얘기를 할 정도가 되었는데 속으로 '그렇게 별난가..?' 싶어서 조금 어리둥절 했었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분 중에서도 혹시 기계론적 해석학에 관심이 있으시다면 '크리스 올라'의 블로그를 적극 추천드립니다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 여기서 마무리 짓고 다음 포스팅으로 찾아뵙겠습니다. 오늘 하루도 고생하셨습니다! 다음 포스팅에서 뵈어요!&lt;/p&gt;</description>
      <category>AI Engineering/기계론적 해석</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/618</guid>
      <comments>https://coding-review.tistory.com/618#entry618comment</comments>
      <pubDate>Wed, 25 Feb 2026 21:08:07 +0900</pubDate>
    </item>
    <item>
      <title>벡터 데이터베이스 톺아보기</title>
      <link>https://coding-review.tistory.com/617</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 이번 포스팅은 오랜만에 데이터베이스와 관련된 포스팅이네요. 이번엔 제가 주로 공부하는 RDBMS가 아닌 벡터 데이터베이스에 대해서 공부해보고 정리해보는 시간을 가져볼까합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sLLM을 이용해서 개발하고있지만 벡터 데이터베이스를 깊이있게 사용하지 않아서 잠시 옆으로 두고 있었는데 이제 조만간 대학원을 가야하는데 졸업 논문을 편하게 쓰려면 1학기부터 논문 주제를 슬슬 정하는게 좋겠다고 생각해서 이런저런 주제를 생각하다가 벡터 데이터베이스와 관련된 내용을 쓰면 좋겠다싶어 미리 공부할겸 제반지식을 쌓기 위해 공부하고 정리하려고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 다음과 같은 순서로 진행됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;RDBMS에서의 인덱스 :&lt;/b&gt; 이 챕터에선 기존 RDBMS가 채택했던 인덱스 알고리즘인 B+Tree에 대해서 간단하게 리뷰해보는 시간을 가져볼까합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;벡터 데이터베이스에서의 인덱스 :&lt;/b&gt; 해당 챕터에서는 RDBMS에 B+Tree가 있다면 벡터 데이터베이스에는 HNSW가 있습니다. 이 HNSW가 무엇이고 어떤 점에서 RDBMS와 차별점이 있는지 알아봅니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HNSW의 동작 방식 :&lt;/b&gt; 앞에서 다뤘던 HNSW에서 조금 더 깊이있게 탐구해보는 시간을 가질건데 여기서는 어떻게 벡터 데이터베이스가 목적지까지 이동하는지에 대한 흐름으로 이야기를 풀어볼까합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;벡터 데이터베이스의 종류와 각각의 특징들 :&lt;/b&gt; RDBMS도 Oracle, MySQL, MariaDB, PostgreSQL, MSSQL, SQLite 이렇게 여러개인 것과 마찬가지로 대표적으로 Pinecone, Milvus, Qdrant, Chroma, Weaviate 등이 있습니다. 각각의 벡터 데이터베이스들의 특징을 살펴보고 어떤 점에서 강점을 가지며 실무에서는 어떤 기준으로 데이터베이스를 선택해야하는지 장점과 단점의 트레이드오프 관점에서 이야기를 풀어봅니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pre-filtering vs Post-filtering :&lt;/b&gt; 필터링 작업은 RDBMS에서 WHERE절을 이용해서 필터링하듯이 걸러내는 작업을 할 수 있는데 검색 전에 필터링하냐 검색 후에 필터링을 하느냐에 따라서 추구하는 목표도 다르고 결과물도 완전 달라지게 되는데요 이 부분을 조금 집중적으로 다룰 예정입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하이브리드 검색과 RRF :&lt;/b&gt; 전통적인 키워드 검색 방식과 벡터 기반 검색은 각각이 잘하는 분야가 완전히 다른데요. 키워드 검색과 벡터 기반 검색을 합쳤을 때의 강력함이 묻어나오는데 암호화 알고리즘인 Argon2i와 Argon2d를 합쳐 장점은 돋보이게 하고 단점을 희석시키는 Argon2id와 같은 방식으로 서로의 장점만 쏙 빼먹은 하이브리드 검색에 대해서 알아봅니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Product Quantization (PQ) :&lt;/b&gt; 벡터 데이터베이스의 단점 중 하나는 노드들의 의미적인 연결을 메모리에 다 올리고 있어야 한다는 것인데 간단한 꼼수를 활용해 메모리를 최소 4배 최대 64배까지 최적화할 수 있는 방법론인 PQ에 대해서 알아봅니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실무 관점에서의 벡터 데이터베이스 튜닝 :&lt;/b&gt; 실무에서는 어떤 부분을 어떻게 조정해야 우리 서비스에 핏하게 맞는 벡터 데이터베이스를 만들 수 있을지를 고민해봅니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 볼륨이 큰 포스팅으로 찾아뵙게 되었네요. 조금 무겁지만 최대한 단순하게 정리해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RDBMS에서의 인덱스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS에서는 크기 기반 인덱싱을 주로 사용하는데요. 주로 사용되는 B+Tree의 경우 숫자 혹은 알파벳의 정렬 순섣재로 이진 트리 형식위에 노드를 위치시킵니다. 또한, B+Tree의 특징 덕분에 Depth가 3이상으로 가는 일이 거의 없어 RDBMS는 이 구조 덕분에 읽기 성능에 대한 성능을 보장받을 수 있게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이 때문에 RDBMS에서 읽기를 할 때는 완전히 키워드가 일치하지 않으면 검색할 수 없다는 단점이 생겼는데요. WHERE절에서 정확히 맞는 문자열을 검색해야하고 이보다 조금 나은 LIKE를 사용하더라도 &lt;b&gt;&quot;의미&quot;&lt;/b&gt;기반의 검색은 할 수 없게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이런 인덱스 구조 때문에 쓰기 연산에서 큰 단점이 생기게 되었습니다. 쓰기 연산을 하게 되면 데이터가 이진 트리 사이를 비집고 들어와야 했기에 노드를 찢거나 다시 합치는 등의 추가 연산이 필요해 인덱스를 재정렬할 시간이 있어야했고 이는 자연스럽게 쓰기 연산에 대한 부하로 이어졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라, 일관성을 보장받아야 했기에 MVCC를 위해 데이터간 락을 걸어 쓰기 연산에 더 큰 부하가 생긴 것은 덤이었죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 결과로 읽기 연산이 압도적으로 많은 서비스나 강한 일관성이 보장받아야하는 서비스에서 RDBMS를 사용하게 되었고 쓰기 연산이 많은 SNS같은 서비스에서는 자연스럽게 NoSQL을 채택하게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;벡터 데이터베이스에서의 인덱스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에도 벡터 데이터베이스에 대한 연구와 개발이 이루어졌지만 웬만한 서비스는 RDBMS나 NoSQL로 커버가 되었기 때문에 벡터 데이터베이스는 그대로 잊혀지는 듯 했습니다. 하지만 최근 LLM의 급부상과 RAG의 도입으로 인해 벡터 데이터베이스는 날개를 달았는데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확률적으로 다음 단어를 생성하는 Transformer의 특성상 할루시네이션이라는 엄청난 단점이 생기게 되었고 개발자들은 이를 해결하기 위한 방법으로 벡터 데이터베이스를 선택하게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 RDBMS와 NoSQL은 정확히 일치하는 검색만 가능했지만 LLM 서비스 특성상 사용자가 애매모호한 질의를 심지어 자연어로 날리는 경우가 많아 의미기반의 검색이 가능한 벡터 데이터베이스가 훌륭한 선택지가 되었던 것이죠. 또한, 벡터 데이터베이스를 도입한 것만으로도 LLM에게 정답지를 쥐어주는 효과가 발생해 할루시네이션을 낮출 수 있게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 벡터 데이터베이스는 어떻게 의미 기반의 검색이 가능했던 것일까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 HNSW (Hierarchical Network Small World) 덕분입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HNSW 란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HNSW는 NSW라는 알고리즘에서 출발합니다. NSW는 클러스터링 된 군집에서 각각의 오브젝트들과 선으로 연결해 그래프로 표현하는 알고리즘인데요. 이는 마치 Graph DB와 비슷한 맥락인데 Graph DB와 다른 점은 노드간 관계에 집중한 Graph DB와 다르게 벡터 데이터베이스는 각 노드간 물리적 근접도만을 위한 점이 가장 크게 다른 점입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csw5UT/dJMcacWg1E9/RhSbGzES7cAQ2OLWtk2i51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csw5UT/dJMcacWg1E9/RhSbGzES7cAQ2OLWtk2i51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csw5UT/dJMcacWg1E9/RhSbGzES7cAQ2OLWtk2i51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsw5UT%2FdJMcacWg1E9%2FRhSbGzES7cAQ2OLWtk2i51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;334&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 NSW가 생성되는 과정을 보여주는 그림인데 점이 하나 찍힐 때마다 2개씩 인접 노드와 선으로 연결되는 것을 보실 수 있습니다. 그것이 NSW의 핵심이고 이 군집을 이용해서 가까운 거리를 찾아내는 것이 기본 골자입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l2GEw/dJMcagqQGzz/qsY1mwvcVMNiZ7zBKUXnjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l2GEw/dJMcagqQGzz/qsY1mwvcVMNiZ7zBKUXnjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l2GEw/dJMcagqQGzz/qsY1mwvcVMNiZ7zBKUXnjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl2GEw%2FdJMcagqQGzz%2FqsY1mwvcVMNiZ7zBKUXnjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;729&quot; height=&quot;413&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 연결된 노드들은 실제 질의와 관련된 노드와 가장 가까운 노드를 선별해서 검색할 수 있게 되었는데 시작점에서 들어와 가장 가까운 노드로 이동하는 Greedy Search를 기반으로 질문과 가까운 노드를 찾을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가까운 거리를 구하는 것은 일반적으로 하나의 노드에 연결된 모든 노드의 거리를 검색하는 것이 일반적이지만 그렇게 하면 노드의 개수가 조금만 늘어나도 바로 문제가 되기 때문에 이보다는 단계적인 레이어 계층을 두어 거리를 최소화하는 방법을 선택하는 방향으로 진화하게 되었습니다. 그것이 바로 여기에 수직 계층을 추가한 HNSW이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;HNSW의 동작 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HNSW는 스무고개와 굉장히 비슷합니다. 우선 큰 틀에서부터 점점 작은 틀로 좁혀나가 결국 원하는 목적지에 도달하는 것인점이 비슷한데 예를 들어서 &quot;L2캐시&quot;라고 질문이 온다면 HNSW 인덱스는 &quot;가전제품&quot;, &quot;PC&quot;, &quot;CPU&quot;, &quot;L2캐시&quot; 이렇게 단계적으로 목적지에 찾아가는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 데이터베이스는 클러스터링을 할 때 다음과 같은 동작 방식을 따릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;레이어 결정 :&lt;/b&gt; 새로운 벡터가 들어오면 이 노드가 어느 층까지 올라갈지를 먼저 결정합니다. 이는 지수 확률 분포를 따르기 때문에 대부분의 노드는 바닥 레이어에 머물지만 아주 작은 활률로 상위 레이어까지 배치됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;진입 및 탐색 :&lt;/b&gt; 레이어가 결정되면 최상위 레이어의 진입점부터 시작해서 자신이 배치될 레이어까지 Greedy Search로 내려옵니다. 여기서 Greedy Search는 현재 노드와 연결된 모든 이웃 노드들의 좌표를 가져와 이웃 누드간 거리를 코사인 유사도로 계산한 뒤 만약 이웃 중 하나라도 질문과 가깝다면 미련 없이 그 노드로 자리를 옮겨버립니다. 만약 모든 이웃을 다 뒤져봤는데 나보다 더 가까운 노드가 없다면 그 지점에서 탐색을 멈춥니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이웃 선택과 연결 :&lt;/b&gt; HNSW에서는 단순히 가장 가까운 M(최대 연결 수)개의 노드와 연결하는 것이 아닙니다. 벡터 데이터베이스는 다양성을 고려한 휴리스틱 알고리즘을 사용하는데, 가장 가까운 놈들끼리만 연결이 되면 특정 영역에서 노드들이 뭉쳐버리는 &lt;b&gt;&quot;클러스터 현상&quot;&lt;/b&gt; 이 발생하기 때문입니다. 그렇기 때문에 일단 가장 가까운 노드를 연결하고 그 다음 노드를 연결할 때는 이미 연결된 노드보다 새로운 노드가 나와 가까운가를 체크합니다. 만약 이미 연결된 노드와 너무 가까운 방향에 있는 노드라면 버리고 조금 더 멀더라도 새로운 방향에 있는 노드를 선택해서 연결합니다. 이를 통해 고차원 공간 구석구석으로 뻗어 나가는 사방팔방의 인맥이 형성됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;양방향 연결 및 가지치기 :&lt;/b&gt; 새 노드가 기존 노드에 선을 연결하면 기존 노드 입장에서는 연결된 선이 M을 초과할 수 있는데 이 경우 기존 노드도 다시 휴리스틱 알고리즘을 돌려 가장 효율적인 M개의 선만 남기고 나머지는 끊어버리는 가지치기를 수행합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 과정을 거치면 HNSW가 동작하게 되는데 HNSW가 이런 동작 방식을 가지게 되면서 Greedy Search에서 생기는 단점이 발생하지 않게 되었습니다. Greedy Search는 가장 가까운 노드만 돌다가 결국 한 군데에서 빙빙 도는 현상이 생기게 되는데 HNSW는 이미 상위 레이어에서 질문과 가까운 답변이 있는 곳에 위치하기 때문에 Greedy Search의 단점이 발생하지 않게 되는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;벡터 데이터베이스의 종류와 특징&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 벡터 데이터베이스들은 HNSW를 채택하고 있습니다. 다만 조금씩 성격이 다른데 한번 정리해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Pinecone :&lt;/b&gt; Pinecone은 인프라 설정이 전혀 필요없는 완전 관리형 SaaS입니다. 초기 구축 속도가 압도적으로 빠르며 서비스 규모에 따라 알아서 스케일링이 된다는 장점이 있습니다. 다만 추가 비용이 생기고 블랙박스 구조로 비용 예측이 어렵다는 단점이 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Milvus :&lt;/b&gt; 벡터 데이터베이스계의 쿠버네티스이고 이런 이명답게 가장 복잡하지만 가장 강력한 확장성을 가집니다. 데이터를 저장하는 노드, 쿼리를 실행하는 노드, 인덱싱을 하는 노드가 모두 분리된 분산 시스템이기 때문에 분산 처리에 특화되어있고 수평 확장에 매우 능합니다. Milvus는 엔터프라이즈급 시스템에 가장 잘 어울리는 벡터 데이터베이스입니다. 다만 아키텍처가 매우 복잡해 관리 포인트가 한두개가 아니라는 문제가 있고 쿠버네티스에 대한 이해도가 없으면 잘 활용하기 힘든 데이터베이스입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Qdrant :&lt;/b&gt; Rust로 작성된 데이터베이스여서 메모리 효율과 연산 속도가 매우 빠르다는 특징이 있습니다. Rust 특유의 메모리 안전성과 속도 덕분에 동일한 사야에서 높은 처리량을 보여줍니다. API가 직관적이고 성능 튜닝 옵션이 세밀하다는 특징이 있습니다만 Rust로 작성되어 커뮤니티가 상대적으로 작아 성숙하지 못하다는 단점이 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Chroma :&lt;/b&gt; Python기반의 매우 단순한 인터페이스를 제공하고 내부적으로는 SQLite를 사용한다는 특징이 있습니다. 복잡한 설정 없이 바로 RAG 환경을 구축할 수 있어 빠른 검증용으로 유용합니다만 조금만 규모가 있는 서비스에서는 사용할 수 없다는 단점이 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Weaviate :&lt;/b&gt; Go로 작성되어 성능이 준수하고 GraphQL 지원 등 백엔드 개발자가 선호하는 인터페이스를 갖추고 있습니다. 또한, 하이브리드 검색이 가능하고 백엔드 친화적이지만 메모리 점유율이 높아 PQ를 적용해야하는 경우가 많습니다. 왜 Weaviate가 메모리를 많이 점유하게 되었는지는 추후에 서술합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Pre-filtering vs Post-filtering&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색도 RDBMS의 WHERE절처럼 필터링을 진행할 수 있는데 이 필터링을 검색 전에 하느냐 검색 후에 하느냐에 따라 추구하는 방향도 다르고 검색 결과도 완전히 다르게 되는데요. 한번 자세히 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pre-filtering&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-filtering은 벡터 검색을 수행하기 전에 메타데이터 조건을 만족하는 데이터들만 먼저 골라내는 방식입니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작 방식 :&lt;/b&gt; 먼저 SQL의 WHERE절처럼 조건을 만족하는 문서들의 ID를 필터링한 후, 그 결과 집합 안에서 유사도 검색을 수행합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점 :&lt;/b&gt; 필터링된 결과 내에서 가장 유사한 K개를 반드시 찾습니다. 필터 조건에 맞는 데이터가 존재하기만한다면 결과가 누락될 일이 없습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점 :&lt;/b&gt; Pre-filtering의 가장 큰 단점은 HNSW 인덱스가 무력화될 위험이 있다는 것입니다. HNSW처럼 그래프 기반의 인덱스에서는 그래프의 노드 중 상당수가 필터링으로 삭제된 것처럼 처리되면, 검색 경로가 끊겨버려 성능이 급격히 떨어지거나 검색이 실패할 수도 있습니다. 또한, 필터링된 결과가 너무 많으면 전체를 스캔해야하므로 속도가 느려진다는 단점도 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Post-filtering&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 검색을 먼저 수행하여 상위 K개를 뽑고 그 결과에 메타데이터 필터를 적용하는 방식입니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작 방식 :&lt;/b&gt; 전체 데이터셋에서 유사도가 높은 K개를 먼저 가져온 후, 그 중 특정 조건에 맞는 데이터를 버립니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점 :&lt;/b&gt; 기존의 벡터 인덱스를 그대로 활용하므로 매우 빠르면서도 인덱스 구조를 건드리지 않아도 된다는 장점이 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점 :&lt;/b&gt; 예를 들어서 100개를 뽑았는데 필터 조건에 맞는 데이터가 2개뿐이라면, 사용자는 100개를 요청했더라도 2개만 받게 됩니다. 최악의 경우 검색 결과가 0개가 될 수도 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실무적 의사결정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HNSW 그래프가 Pre-filtering 시 끊기는 문제는 벡터 데이터베이스의 성능과 정확도를 갉아먹는 고질적인 병목 중 하나입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 핵심은 Pre-filtering으로 벡터 검색을 시작하기 전에 메타데이터 조건에 맞지 않는 노드들을 검색 대상에서 제거하기 때문에 만약 필터링 조건이 매우 까다로워서 데이터의 90퍼센트가 제거된다면, 그래프의 연결 고리들이 대거 사라집니다. 노드간 연결로 목적지를 찾아가는 HNSW에서는 치명적이죠. Pre-filtering의 결과 그래프가 여러개의 섬으로 쪼개지고 검색 시작점에 있는 섬에서 목적지가 있는 섬으로 가는 길이 끊어지면 목적지가 분명 존재하는데도 불구하고 검색 엔진은 &lt;b&gt;&quot;결과 없음&quot;&lt;/b&gt;을 반환합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 벡터 데이터베이스들의 해결 방식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 데이터베이스를 만드는 개발자들이 가만히 냅두지 않았겠죠? 어떻게 해결했을까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Milvus&lt;/b&gt;의 경우 그래프에서 노드를 물리적으로 제거하지 않고, 대신 모든 노드의 필터링 여부를 0과 1로 표시한 비트마스크를 메모리에 띄웁니다. 탐색시에는 필터링된 노드도 브릿지 역할로 활용하여 길을 찾고 최종 결과 리스트에 담을 때만 비트마스크를 확인해 필터링된 노드를 제거하는 방식입니다. 결과가 부족하면 탐색 범위를 넓혀가며 반복합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Weaviate&lt;/b&gt;의 경우 ACRON-HNSW 알고리즘을 도입했는데 이 방식은 인덱스를 빌드할 때부터 필터링을 염두해두는 방식입니다. 일반적인 HNSW는 그래프를 가볍게 만들기 위해 불필요한 연결을 쳐내는데 반해 ACRON은 의도적으로 연결을 더 많이 남겨둡니다. 이로인해 필터링으로 많은 노드가 가려져도 우회로가 존재할 확률을 극대화합니다. 또한, 필터링 후 남은 데이터가 너무 적으면 아예 인덱스를 무시하고 Flat Search로 스위칭하여 100% 정확도를 보장합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Qdrant&lt;/b&gt;는 필터 가능한 HNSW라는 독자적인 알고리즘을 사용합니다. 이 방식은 탐색 과정에서 &lt;b&gt;&quot;필터 조건에 맞는 노드&quot;&lt;/b&gt;와 &lt;b&gt;&quot;그냥 길만 빌려주는 노드&quot;&lt;/b&gt;를 구분합니다. 핵심은 HNSW의 탐색 알고리즘을 직접 수정해서 조건에 맞지 않는 노드라도 탐색 경로 상에 있다면 이를 통과할 수 있도록 허용해줍니다. 이를 통해 그래프의 단절 문제를 원천적으로 차단하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;하이브리드 검색&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이브리드 검색은 벡터 데이터베이스의 꽃이죠. 전통적인 키워드 검색(BM25A)과 최신 벡터 기반 검색은 서로 잘하는 분야가 완전히 다릅니다. BM25는 키워드 검색에 능해 &quot;IPhone 15 Pro&quot;와 같은 고유명사, 제품 코드, 약어를 찾을 때 압도적으로 좋습니다. 의미가 아니라 글자 그 자체의 일치 여부를 따지기 때문인데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 벡터 검색은 &quot;어제 먹은 음식 때문에 배가 아플 때 먹는 약&quot;과 같은 맥락과 의도를 파악하는데 아주 강합니다. &quot;배가 아프다&quot;와 &quot;복통&quot;의 유사성을 이해하기 때문이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이브리드 검색은 이 두개를 적절히 잘 섞어서 이 두개의 장점을 모두 흡수하는 방향으로 진화했습니다. 여기에서 가장 널리 사용되는 기법이 바로 RRF입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RRF (Reciprocal Rank Fusion)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 기준의 점수를 가진 두 리스트를 &quot;순위&quot;만을 이용해서 통합하는 알고리즘입니다. 점수 체계가 다른 두 엔진 (BM25 vs 코사인 유사도)을 공정하게 합칠 수 있다는 특징이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 벡터 데이터베이스들은 하이브리드 검색 시 알파라는 가중치 파라미터를 제공합니다. 이를 통해 어떤 엔진에 더 힘을 실어줄지 결정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;알파 = 1 :&lt;/b&gt; 100% 순수 벡터 검색&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알파 = 0 :&lt;/b&gt; 100% 순수 키워드 검색&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알파 = 0.5 :&lt;/b&gt; 두 방식의 결과를 균등하게 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실무적 의사결정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 보통 사용자의 쿼리가 짧고 명확한 단어 위주라면 알파 값을 낮추고, 서술형 문장이라면 알파 값을 높이는 식의 동적 튜닝을 고민해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;법률, 의료, 혹은 회사 내부 시스템처럼 특정 코드나 용어가 중요한 도메인의 경우에는 하이브리드 검색이 유용하고 임베딩 모델이 학습하지 못한 최신 유행어나 신제품명이 검색어인 경우에도 하이브리드 검색이 용이합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Product Quantization (PQ)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터 데이터베이스의 고질적인 문제는 노드간의 연결들을 모두 메모리에 올려야한다는 문제가 있습니다. 이를 해결하기 위해 PQ라는 고차원 벡터를 여러개의 작은 부분 벡터로 쪼개고 각 부분 벡터를 대표값의 인덱스로 치환하여 압축하는 기술을 이용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHuwl/dJMcafZLEFb/j9gCaCSy7rIjueS7N1ZIHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHuwl/dJMcafZLEFb/j9gCaCSy7rIjueS7N1ZIHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHuwl/dJMcafZLEFb/j9gCaCSy7rIjueS7N1ZIHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkHuwl%2FdJMcafZLEFb%2Fj9gCaCSy7rIjueS7N1ZIHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;555&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 128차원 벡터를 8바이트로 압축하는 경우는 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;분할 :&lt;/b&gt; 128차원 벡터를 16차원짜리 부분 벡터 8개로 나눕니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;학습 :&lt;/b&gt; 각 16차원 공간에서 K-Means 클러스터링을 수행해 대표적인 점 256개를 뽑습니다. 이 256개의 좌표 묶음을 코드북이라고 부릅니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부호화 :&lt;/b&gt; 이제 각 부분 벡터를 가장 가까운 대표값의 번호로 바꿉니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;압축완료 :&lt;/b&gt; 원래 128개의 실수 (float32, 512바이트)였던 벡터가 8개의 인덱스 (8바이트)로 줄어드니 64배의 압축이 일어난겁니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PQ는 단순히 값을 뭉뚱그리는 일반적인 양자화와는 다르게 성능 손실이 최소화된다는 특징이 있는데 이유는 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 128차원 전체를 한꺼번에 256개로 양자화했다면 표현할 수 있는 벡터의 가짓수는 딱 256개뿐입니다. 하지만 PQ로 8개 구역을 나누어 각각 256개씩 할당해준다면 표현 가능한 전체 조합수는 256의 8승개로 이는 10의 19승개나 됩니다. 이 원리가 부분으로 나누어 근사하더라도 전체 조합으로 정확도를 복원하는 PQ의 핵심 원리이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PQ는 검색시에도 영리하게 동작하는데 사용자 질의는 압축하지 않고 원본 그대로를 유지한 채 DB에 저장된 압축된 벡터들과 거리를 계산합니다. 이 방식은 쿼리와 코드북 사이의 거리를 미리 계산해 테이블로 만든 후 실제 거리를 계산할 때에는 이 테이블에서 값만 읽어서 더한다는 특징이 있습니다. 이는 원본 쿼리를 보존하기 때문에 쿼리와 테이블을 모두 압축했을 때보다 오차가 훨씩 적습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실무 관점에서의 벡터 데이터베이스 튜닝&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 실무에서는 어떻게 벡터 데이터베이스를 다뤄야할까요? 실무에서는 아래와 같은 조건을 보고 기술적 의사결정을 해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;M 설정 :&lt;/b&gt; M이 클수록 그래프가 촘촘해져서 군집이 섬으로 나뉘는 현상이 줄어들고 검색 정확도가 올라가지만 인덱스 크기가 M에 비례해 커진다는 특징이 있습니다. M의 기본 값은 대부분 16이고 일반적인 텍스트 검색이나 RAG 시스템이라면 건드리지 않아도 됩니다. 하지만 임베딩 모델의 차원이 크거나 데이터 간의 경계가 모호하여 고도의 정밀도가 필요한 도메인에서는 32~64정도로 튜닝합니다. 만약 M을 2배 늘리면 차지하는 메모리도 2배 늘어나기 때문에 신중히 결정해야합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;efConstruction :&lt;/b&gt; efConstruction은 인덱스를 생성할 때 얼마나 깊게 탐색하면서 연결 고리를 만들지 결정하는 값입니다. 이 값이 클수록 인덱스 구축 시간은 오래걸리지만, 그래프의 품질이 좋아져 Pre-filtering시 그래프가 끊기는 문제를 완화하기 위해 이 값을 높이기도 합니다. 보통 128이 표준적인 밸런스이고 데이터가 자주 바뀌지 않고 한 번 구축할 때 최상의 검색 품질을 뽑아내야하는 도메인에서는 256~512까지도 튜닝합니다. 이 값은 검색 속도에는 영향을 주지 않고 오직 인덱스 생성 속도와 최종 검색 정확도에만 관여합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;efSearch :&lt;/b&gt; 검색시 후보군을 얼마나 샅샅이 뒤질지 결정합니다. 검색시에 limit보다 크게 설정해야하는데 예를 들어서 10개를 찾고싶다면 efSearch를 64이상으로 설정해야합니다. 평소에는 낮게 유지하다가 정확도가 필요한 특정 쿼리에만 높여서 동적으로 대응할 수도 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Product Quantization :&lt;/b&gt; 고차원 벡터를 몇 개의 묶음으로 나눌지 결정합니다. 이때 이 값은 차원수의 약수여야 에러가 발생하지 않습니다. 세그먼트 수가 많을수록 정확도는 원본에 가까워지지만 압축률은 떨어집니다. 보통 8차원당 1세그먼트가 적정값이고 PQ를 적용하면 메모리 사용량을 원본 대비 4배에서 64배까지 줄일 수 있고 CPU 캐시 효율이 좋아져 검색 속도가 오히려 빨라지기도 합니다. 다만 데이터가 5만건 이하라면 굳이 사용하지 않아도 되기 때문에 신중히 결정해야합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기 전에도 꽤 큰 볼륨이라고 생각했는데 쓰고 나니까 두개로 나눠서 쓸걸 그랬나 살짝 후회가됩니다. 이번 포스팅에서는 벡터 데이터베이스와 관련된 제반 지식들을 공부해보고 정리해봤습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 새로운 분야를 공부하는 것을 별로 좋아하진 않습니다만 이건 나름대로 흥미가 가는 부분들이 있어 공부하면서 꽤나 재밌게 공부했던 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Pre-filtering에서 생기는 단점들을 벡터 데이터베이스마다 다르게 해결하는 방식을 공부한 부분이 개인적으로 가장 재밌었습니다. 저는 문제 상황을 비슷한 기술들끼리 어떻게 해결했는지를 공부할 때 제일 흥미로워하는 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일례로 CAP이론을 NoSQL이 임하는 방식을 보면 어떤 데이터베이스는 CP를 더 중요하게 생각하고 어떤 데이터베이스는 AP를 더 중요하게 생각하고 왜 그렇게 생각하고 어떤 결과로 이어지고 어떤 상황에서 어떤 데이터베이스를 선택해야하는지 등등 꼬리에 꼬리를 물고 사고가 뻗어나가게 되는 부분이 재밌는 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 슬슬 포스팅을 줄여보도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!&lt;/p&gt;</description>
      <category>CS 지식/데이터베이스</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/617</guid>
      <comments>https://coding-review.tistory.com/617#entry617comment</comments>
      <pubDate>Thu, 5 Feb 2026 22:54:50 +0900</pubDate>
    </item>
    <item>
      <title>AI가 나아가야하는 방향은 어디일까?</title>
      <link>https://coding-review.tistory.com/616</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 이번에 &quot;에세이&quot;라는 카테고리를 출범하게 되었는데요. 지금까지 AI 엔지니어링 포스팅은 동작 원리나 내부구조를 이해하는 &quot;이론&quot;파트와 엔지니어링을 위해 고려해야하는 것들이나 아키텍처 설계와 같은 &quot;실습&quot;파트 이렇게 두개의 파트로 운영해왔습니다. 이번 포스팅은 처음엔 이론 공부를 하려고 했지만 정리하다보니 이론도 실습도 아닌 컬럼처럼 되어버려서 이렇게 에세이라는 카테고리로 새롭게 출범하게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에세이 카테고리에서는 AI에 대한 컬럼 한편 본다고 생각하시고 편하게 보셔도 될 것 같습니다. 기술적인 이야기는 최대한 줄이고 AI 석박들, 혹은 개발자들이 이야기하는 AI 현상에 대해 제 생각을 이야기하는 카테고리로 운영될 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 본격적으로 시작해보죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;LLM이 추상적인 개념을 표현하는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 단어의 속성들을 &lt;b&gt;&quot;벡터&quot;&lt;/b&gt;라는 형식으로 단어를 이해하고 답변을 생성합니다. 현재 LLM 모델들은 수만개의 차원으로 이루어진 벡터를 이용해서 답변을 생성하는데요. 예를 들어서 왕과 여왕은 &lt;b&gt;&quot;성별&quot;&lt;/b&gt;만 다른 속성으로 LLM이 인지하고 있습니다. 하지만 이런 간단한 데이터 말고 &lt;b&gt;&quot;슬픔&quot;&lt;/b&gt;같은 추상적인 개념은 어디에 저장될까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 말하는 Embedding은 단순히 단어를 숫자로 바꾼 것이 아니라 수천, 수만개의 차원에 점을 찍는 행위입니다. 3차원 공간에서는 가깝다/멀다만 중요하지만 1만개가 넘는 차원에서는 서로 다른 &lt;b&gt;&quot;방향&quot;&lt;/b&gt;이 존재할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 차원 하나가 &quot;슬픔&quot;이라는 단어를 담당하는 것이 아니고 수만개의 차원이 아주 복잡하게 조합된 특정한 방향 벡터 자체가 &quot;슬픔&quot;이라는 개념을 가리키게 되는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;효율적인 압축의 비밀&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 자신이 가진 뉴런 수보다 더 많은 개념을 저장해야하기 때문에 하나의 뉴런이 오직 하나의 의미에만 반응하는 것이 아니라 여러개의 뉴런을 동시에 활성화시킵니다. 예를 들어서 '강아지'라는 단어에 '강아지'만 반응하는게 아니라 '복슬복슬한', '충성심'같은 뉴런들이 동시에 반응하게 되는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 우리가 마치 거대한 데이터를 해시 함수로 압축해서 저장하듯이 수많은 개념들을 고차원 공간에서 최대한 겹치지 않게 쑤셔넣습니다. 하지만 너무 많은 개념을 겹쳐놓으면 모델이 헷갈려하는데 이걸 잘 분리해서 처리하는 능력이 바로 모델의 성능과 직결됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cf) LLM의 뉴런에 대응되는 개념이 뭘까? 진짜 뉴런은 아닐텐데...&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에서 말하는 뉴런은 모델별 각 레이어에 존재하는 개별 가중치 유닛입니다. 즉, 하드웨어라는 뜻인데 특정 입력값이 들어왔을 때 &lt;b&gt;&quot;이 개념이 얼마나 포함되어 있는가?&quot;&lt;/b&gt;를 계산해서 신호로 보낼지 말지를 결정합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 수만개의 뉴런이 동시에 활성화된 &lt;b&gt;'전체적인 패턴'&lt;/b&gt;이 바로 벡터입니다. 예를 들어서 1번 뉴런이 0.1, 2번 뉴런이 0.8, 3번 뉴런이 0.3 이런식으로 활성화된 수치를 한 줄로 세우면 그게 바로 그 시점에서의 &lt;b&gt;'개념 벡터'&lt;/b&gt;가 되는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 왜 뉴런이라고 부르게 되었냐하면 인간의 뇌에서도 '사과'와 같은 단어를 담당하는 뇌세포가 따로 있는게 아니고 사과를 볼 때 뇌 전체의 수많은 뉴런들이 &lt;b&gt;'특정한 패턴'&lt;/b&gt;으로 반짝이게 되는데 LLM의 벡터가 바로 이 방식을 그대로 모방한 것이기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;얀 르쿤의 LLM 회의론&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;AGI라는 단어가 주는 마법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 AGI라는 단어에 홀린듯이 반응합니다. 이제는 인간의 지능을 넘어선 무언가로 대충 뭉뚱그려서 얘기하는 것처럼 보이죠. 하지만 OpenAI의 창립자이자 한동안 OpenAI의 수석 과학자로 있으면서 GPT의 전성기를 이끌었던 &lt;b&gt;일리야 수츠케버&lt;/b&gt;는 우리는 이미 AGI에 도달했고 AGI가 아닌 &lt;b&gt;&quot;초지능&quot;&lt;/b&gt;에 도달해야한다고 이야기합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 이야기가 나오냐하면 AGI라는 단어가 생기기 시작했을 때의 AI로 거슬러 가면 이야기하기가 더 쉬운데요. 그때 당시 AI는 특정 문제를 잘 푸는 형태로 진화했습니다. 체스를 두는 AI, 바둑을 잘 두는 AI, 포커를 치는 AI 등등... 이때 많은 연구원들이 꿈꾸던 것이 있었으니 바로 &lt;b&gt;&quot;인간과 같은 일반적인 지능을 가진 AI를 만들자&quot;&lt;/b&gt;였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이때 연구원들은 이런 AI를 일반 인공지능인 AGI라고 부르게 되었고 지금의 GPT나 Gemini, Claude 등등 다양한 언어모델은 인간과 대화가 가능한 일반 인공지능이라는 것이 일리야 수츠케버의 입장이었죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이를 모든 개발자, 연구원들이 받아들이지는 않고 흔히 AGI를 초지능과 같이 생각하는 경향이 아직 남아있습니다. 또한, 일반적인 사람들은 초지능을 AGI라고 생각하고 있기 때문에 굳이 이렇게 단어를 교정해줘야하나 싶기는 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 앞서 우리는 LLM이 추상적인 개념을 어떻게 저장하는지에 대해서 이야기햇고 이것이 얼핏 보면 인간 뇌의 그것과 많이 닮아있어 만은 연구원들이 놀라워했을 뿐만아니라 몇몇개의 문제들을 해결하기도 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 개발쪽에서는 '코딩'이라는 노동이 점점 사라지고 있고, 이제 더 고차원적인 문제를 해결하거나 비즈니스 관점에서 문제를 해결하는 등 산업이 고도화되고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LLM 회의론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, AI 4대 석박이라고 불리우는 메타의 AI 수석 엔지니어 겸 뉴욕 대학교 교수인 &lt;b&gt;얀 르쿤&lt;/b&gt;은 LLM의 한계에 대해서 지적합니다. 얀 르쿤은 현재의 LLM 방식이 결코 인간 수준의 지능에 도달할 수 없다고 못을 박는 아주 강경한 사람입니다. 어째서 얀 르쿤이 인간 뇌의 뉴런과 비슷하게 동작하는 LLM에 한계가 있다고 이야기하는걸까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얀 르쿤이 가장 강조하는 부분은 바로 현재 LLM 모델들은 &lt;b&gt;&quot;세계 모델&quot;&lt;/b&gt;이 없다고 이야기합니다. 인간은 텍스트가 아니라 물리적 상호작용을 통해서 세상을 배우는 반면, LLM은 글로 세상을 배우죠. 이는 글로 수영을 배우는 것과 같고 이렇게 학습된 LLM은 결코 인간이 이해하는 것을 이해할 수 없다는 것이 얀 르쿤의 입장입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 텍스트 데이터 사이의 통계적 상관관계만 학습하기 때문에 &lt;b&gt;&quot;컵을 놓치면 바닥으로 떨어진다&quot;&lt;/b&gt;라는 문장은 알지만 실제 &lt;b&gt;&quot;중력&quot;&lt;/b&gt;이라는 물리적 실체를 느껴본 적이 없죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 얀 르쿤은 현재의 LLM이 &lt;b&gt;&quot;박사급 지능&quot;&lt;/b&gt;이라는 것을 언론에서 많이 다루지만 실제 &lt;b&gt;&quot;박사&quot;&lt;/b&gt;는 아니라고 강하게 이야기합니다. 실제 박사는 어떤 물리현상에 대해 어떻게 다음 실험을 이어갈지에 대한 &lt;b&gt;&quot;판단&quot;&lt;/b&gt;을 내리기 때문에 LLM은 이런 판단의 부재로 엄청나게 거대한 계산기 혹은 백과사전이라는 것이 얀 르쿤의 주장입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, LLM은 단어를 하나씩 내뱉고 결괏값을 그대로 입력으로 넣어 다음 단어를 만드는데 반해, 인간은 말을 내뱉기 전에 결론을 먼저 내리고 그 목표를 향해 문장을 구사하는 &lt;b&gt;&quot;게획&quot;&lt;/b&gt;을 하기 때문에 계획이나 추론 과정 없이 통계적으로 다음 단어를 찍어내는 방식으로는 복잡한 문제를 단계별로 해결하는 지능을 가질 수 없다고 이야기합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 얀 르쿤은 JEPA라는 새로운 구조를 제안했습니다. JEPA는 LLM처럼 단어를 예측하는 대신 &lt;b&gt;&quot;세상의 구조를 예측하는&quot;&lt;/b&gt; 모델입니다. 예를 들어 이미지나 영상의 다음 프레임을 픽셀 단위로 맞추는 것이 아니라 그 안에 담긴 &lt;b&gt;&quot;개념&quot;&lt;/b&gt;과 &lt;b&gt;&quot;움직임&quot;&lt;/b&gt;을 예측하는 방식이죠. 이를 통해 AI가 인간처럼 물리법칙을 이해하고, 목표를 달성하기 위해 행동을 &quot;계획&quot;할 수 있게 만들자는 것이 그의 핵심 아이디어입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 AI가 언제쯤 등장하게 될까요? 등장하기만 한다면 정말 어마어마한 관심을 받게될 것임에는 분명합니다. 지금의 GPT니 Gemini니 다 의미가 없어질 수도 있고요. 제가 이런 격변의 시대에 살고있는 것이 가슴뛰고 재밌네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터와 인터넷이 어떻게 변했는지 경험했던 우리 부모님 세대와 AI가 어떻게 변하고 있는지 경험하는 저희 세대가 얼추 비슷해보이더군요. 앞으로 어떻게 될지도 정말 흥미진진합니다. 정말 모든 직업이 사라질까? 아니면 인간이 밥그릇을 지키기 위해 기술적인 발전의 발목을 잡을까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일리야 수츠케버의 말을 인용하면서 이번 포스팅 마무리 짓겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;지금 AI와 관련된 법안이 매끄럽게 진행되지 않는건 우리가 아직 &lt;b&gt;'실수하지 않는 AI'&lt;/b&gt;를 본 적이 없기 때문입니다. 당신은 실수하지 않는 AI를 생각해보신 적 있나요? 만약 이런 AI가 등장한다면 그때 본격적으로 시작될겁니다.&quot;&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;- 일리야 수츠케버 -&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI Engineering/에세이</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/616</guid>
      <comments>https://coding-review.tistory.com/616#entry616comment</comments>
      <pubDate>Mon, 26 Jan 2026 21:31:40 +0900</pubDate>
    </item>
    <item>
      <title>LLM한테 페르소나를 적용하면 어떻게 이를 수행하는걸까?</title>
      <link>https://coding-review.tistory.com/615</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 사람들에게 많이 알려진 프롬프트 엔지니어링 중에 가장 유명한 것이 바로 &lt;b&gt;&quot;페르소나&quot;&lt;/b&gt;를 부여하는 것일건데요. LLM에게 페르소나를 부여하면 찰떡같이 잘 반응하게 되는데 내부적으로는 어떤 동작이 일어나길래 이 페르소나를 계속 유지할 수 있는걸까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 프롬프트 엔지니어링이라고 부르는 것이 유형별로 잘 정립되어있어서 우리같은 일반인도 충분히 적용시킬 수 있는 범주 내로 들어와 사실 &lt;b&gt;&quot;엔지니어링&quot;&lt;/b&gt;이라고 거창하게 부를만한가 싶긴합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이게 초반에 어째서 엔지니어링의 영역에 들어오게 되었는지 이번에 공부하면서 알게 되었는데, 이 프롬프트 엔지니어링이라는 것의 기반에는 Transformer 알고리즘의 이해가 필요했기 때문에 초기 단계에서는 이를 엔지니어링이라고 부를 정도의 무언가가 되었다고 생각합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 저랑 같이 Transformer 알고리즘을 복습하면서 페르소나를 프롬프트로 입력하면 어떤 동작방식을 거쳐서 페르소나가 입혀지는지 알아보도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅에 들어가기 전에 제가 정리한 Transformer 알고리즘에 대한 기반지식이 필요합니다! 아래의 링크에 잘 정리되어있으니 한번 확인해보시고 이 포스팅을 읽으시는걸 추천드립니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/614&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://coding-review.tistory.com/614&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769244504553&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Transformer 알고리즘은 과거와 다르게 어떻게 발전했을까?&quot; data-og-description=&quot;안녕하세요. 요즘 AI 엔지니어링 업무를 하면서 관련된 것들을 공부를 하니 더 재밌는 것 같네요. 이번 포스팅에선 Transformer 알고리즘이 담긴 논문이 출판됐을 당시부터 있었던 근본 개념들을 알&quot; data-og-host=&quot;coding-review.tistory.com&quot; data-og-source-url=&quot;https://coding-review.tistory.com/614&quot; data-og-url=&quot;https://coding-review.tistory.com/614&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Lz49m/dJMb9lL5Tmq/QX0D4Yn5rvEc3t7vmjUHSK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gDy6L/dJMb9cBCpLH/i0dgaYiQLAL4j9iP3LJbA0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bDPct8/dJMb9frzJ8X/BGFlFm92N7Ky5P1fstL0u0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/614&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-review.tistory.com/614&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Lz49m/dJMb9lL5Tmq/QX0D4Yn5rvEc3t7vmjUHSK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gDy6L/dJMb9cBCpLH/i0dgaYiQLAL4j9iP3LJbA0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bDPct8/dJMb9frzJ8X/BGFlFm92N7Ky5P1fstL0u0/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Transformer 알고리즘은 과거와 다르게 어떻게 발전했을까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 요즘 AI 엔지니어링 업무를 하면서 관련된 것들을 공부를 하니 더 재밌는 것 같네요. 이번 포스팅에선 Transformer 알고리즘이 담긴 논문이 출판됐을 당시부터 있었던 근본 개념들을 알&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;coding-review.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에선 총 세가지 관점에서 페르소나를 분석해보고 끝으로 성능 병목에 대해서 확인해 볼 예정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;LLM에서 페르소나가 적용되는 과정&lt;br /&gt;- Attention 레이어와 페르소나&lt;br /&gt;- In-Context Learning과 페르소나&lt;br /&gt;- Masked Self-Attention과 페르소나&lt;/li&gt;
&lt;li&gt;페르소나와 KV Cache, 그리고 성능 병목&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attention 레이어나, In-Context Learning이나, Masked Self-Attention 모두 LLM의 특성인데 이것이 페르소나와 만났을 때 어떤 시너지를 내는지에 대해서 서술합니다. 그리고 마지막으로는 페르소나가 KV Cache에 미치는 영향과 이것이 병목으로 어떻게 이어지는지 서술합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 들어가보시죠!&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;LLM에서 페르소나가 적용되는 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Attention 레이어와 페르소나&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 LLM들은 레이어라는 것이 존재합니다. 이 레이어는 모델이 얼마나 깊이있게 생각할 수 있는지에 대한 단계로 이해해도 무방한데요. 임베딩 모델로 유명한 BERT는 12개의 레이어가 존재하고 Llama-3-8b의 경우 32개의 레이어를 가지고 있습니다.&amp;nbsp;제가 개인적으로 뛰어나다고 생각하는 모델 중에는 Gemma-3-27b-it 모델이 있는데 이 모델의 경우 62개의 레이어가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터의 수가 늘어나면 이 레이어도 선형적으로 늘어난다고 보시면 됩니다. 즉,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&quot;레이어가 많이 겹겹이 있으면 있을수록 모델이 더 깊이있는 생각을 할 수 있게 된다&quot;&amp;nbsp;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이해하셔도 무방합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 우리는 LLM에게 &lt;b&gt;&quot;너는 까칠한 개발자야&quot;&lt;/b&gt; 이렇게 프롬프팅을 했다고 가정해봅시다. 그럼 모델은 이 프롬프트를 당연히 앞에서부터 읽겠죠? 이 토큰이 Attention 레이어의 첫 부분을 통과하면 모델은 다음과 같이 이해합니다.&amp;nbsp;&lt;b&gt;&quot;아, 나는 개발자구나&quot;&lt;/b&gt; 이제 N번째 레이어를 모두 통과하고 나면 모델은&amp;nbsp;&lt;b&gt;&quot;아, 나는 자바를 싫어하고 파이썬을 좋아하는 츤데레 말투를 써야하는 개발자구나&quot;&lt;/b&gt; 이렇게 조금 더 복잡하고 구체적이면서 추상적인 특징으로 변하게 되는겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 조금 더 공학적으로 풀어서 이야기해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;너는 까칠한 개발자야. 이 코드 분석해줘.&quot;&lt;/b&gt; 라는 프롬프팅을 넣으면 Positional Encoding이 이 토큰에 위치 정보 벡터를 부여합니다. 편의상 0번째라고 하겠습니다. 그리고 그 뒤에 요청은 1번째라고 가정하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 맨 처음 토큰이 Attention 레이어를 통과할 때 가장 첫 번째로 들어오면 가중치가 전파되고 맨 처음 토큰을 생성해서 답변할 때의 Attention Map에서 가장 강한 자극을 받게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 토큰을 생성하는 데에 가장 큰 영향을 미치는 Attention Score의 경우 첫 번째 연산이 바로 쿼리와 키를 내적하는 것인데, 질문에 해당하는 쿼리와 인덱스에 해당하는 키가 서로 강하게 연결되어 있으니 모델은 이 페르소나로 대답하도록 강한 드라이브가 걸리는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 조금 다른 얘기이긴 하지만, 프롬프트 엔지니어링에서 페르소나는 맨 앞에, 가장 중요한 수행은 마지막에 넣으라고 하는 이유를 크게 두가지 관점에서 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 논문으로 많이 밝혀진 LLM모델들의 고질적인 문제 중 하나는&amp;nbsp;&lt;b&gt;&quot;모델은 맨 앞과 맨 뒤의 내용은 잘 기억하지만 중간 내용은 잘 기억 못한다&quot;&lt;/b&gt;입니다. 이러한 문제는 맨 앞에 페르소나, 맨 뒤에 가장 강한 제약, 이렇게 두가지를 잘 수행할 수 밖에 없는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 LLM의 고질적인 문제라는 관점에서 바라본 프롬프트 엔지니어링입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 요즘 LLM 모델들은 &lt;b&gt;RLHF&lt;/b&gt;라는 방법을 주로 사용합니다. 이 방법은 모델이 인터넷에 있는 데이터를 싹 학습한 뒤에 답변을 생성하고 나서 사람에게&amp;nbsp;&lt;b&gt;&quot;이 답변은 사람같군, 이 답변은 너무 기계같아&quot;&lt;/b&gt;이렇게 검수받는 강화학습 과정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 강화학습은 사람이 문맥을 보고 답변을 정해주기 때문에 모델은 사람이 정답이라고 판단내린 값에 더 큰 가중치를 두게 됩니다. 보통 이때 정답이라고 책정되는 것들은 인간이 마지막에 핵심 로직을 배치한다는 통상적인 관념상 인간의 대화 습관이 모델에 이식됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에, 모델은 &lt;b&gt;마지막에 오는 내용이 중요하다!&lt;/b&gt; 라고 판단하게 되는 것이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 RLHF의 관점에서 보는 프롬프트 엔지니어링입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;In-Context Learning과 페르소나&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 모델들은 인터넷에 있는 수많은 텍스트를 학습하면서 특정 문맥 뒤에는 어떤 말투와 어떤 지식이 뒤따르는지에 대한 통계적인 패턴을 학습하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트에서 페르소나를 넣는 행위는 모델의 거대한 파라미터 공간 중에서 해당 파라미터와 일치하는 공간으로 확률 분포를 강제로 이동시키기 때문에 모델이 인터넷에서 학습한 모델을 그대로 잘 이행할 수 있는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Masked Self-Attention과 페르소나&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 사용하는 LLM모델들은 Decoder-Only인 경우가 많은데 Decoder-Only 모델들의 특징은 앞에서 생성한 토큰을 다음 입력으로 집어넣는 자기 회귀 구조를 띄고 있기 때문에 앞에서 생성한 토큰들을 입력으로 받아서 페르소나가 그대로 유지되는 특징을 가집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;페르소나와 KV Cache, 그리고 성능 병목&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 언급했듯이 Decoder-Only는 자기 회귀 구조를 가지고 있어 같은 연산을 계속 반복해야하는 문제가 있는데 이를 KV Cache라는 곳에 두어 연산을 빠르게 할 수 있도록 했습니다. 이것이 KV Cache라고 불리는 이유는 이미 행렬곱에서 연산했던 K와 V의 벡터는 변하지 않기 때문에 키와 값을 메모리에 저장해도 전혀 문제 없기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KV Cache가 점유하는 메모리를 구하는 공식은 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;2 X Hidden Layer X Head Dimension X KV Head X n_ctx X B&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 다른건 볼 필요 없고 n_ctx만 보면 됩니다. n_ctx는 &lt;b&gt;LLM에게 주어지는 입력 토큰&lt;/b&gt;이라고 이해해도 좋고, &lt;b&gt;LLM모델이 기억하고 있는 토큰의 수&lt;/b&gt;라고 이해하셔도 좋습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 프롬프트가 길어지면 길어질수록 n_ctx의 크기가 커지고 이는 KV Cache의 크기를 선형적으로 늘리는 원인이 됩니다. 이렇게 되면 추론을 시작하기도 전에 VRAM을 선점하는 것이기 때문에 맨 처음 토큰이 나오는 시간인 TTFT의 시간이 길어지죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTFT 뿐만 아니라, 개별 세션이 처리하는 KV Cache가 커지면, 하나의 GPU에서 동시에 처리할 수 있는 배치 사이즈가 줄어들고 처리량이 줄어드는 문제도 생깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에, 프롬프트의 크기가 너무 비대해지지 않도록 주의를 기울여야 성능상 병목이 생기지 않는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 동적으로 변하지 않는 프롬프트는 미리 연산을 해놓음으로써 TTFT를 줄이는 방법이 있지만 이건 실전과 관련된 내용이므로 여기서는 더 언급하지 않겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅은 개인적으로 궁금했던 내용을 정리해보았는데 Transformer의 기본 동작 원리는 이해하고 공부하니 더 이해하기가 쉬웠습니다. 그래서 꼭 Transformer의 기본동작 원리를 보시고 보는 것을 추천드립니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT가 등장한지는 만으로 3년정도 되었고 AI 엔지니어링이라는 것이 등장하기 시작한건 만으로 2년정도 되었기 때문에 Transformer의 기본 개념 안에서 돌고돌고 계속 돌리는 느낌이 들기는 합니다만, 그렇기에 Transformer의 이해가 그 어느때보다 중요해진 시점인 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅은 뉴럴 링크와 역전파에 대해서 공부해보고 정리해볼 것 같습니다. 이번 포스팅은 여기서 마치도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!&lt;/p&gt;</description>
      <category>AI Engineering/기계론적 해석</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/615</guid>
      <comments>https://coding-review.tistory.com/615#entry615comment</comments>
      <pubDate>Sat, 24 Jan 2026 18:32:45 +0900</pubDate>
    </item>
    <item>
      <title>Transformer 알고리즘은 과거와 다르게 어떻게 발전했을까?</title>
      <link>https://coding-review.tistory.com/614</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 요즘 AI 엔지니어링 업무를 하면서 관련된 것들을 공부를 하니 더 재밌는 것 같네요. 이번 포스팅에선 Transformer 알고리즘이 담긴 논문이 출판됐을 당시부터 있었던 근본 개념들을 알아보면서 그때의 한계를 현재는 어떻게 극복했는지를 중점으로 정리할 예정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM을 논하기 이전에 가장 중요한 개념이 바로 Transformer이고 개발자가 아닌 분들도 AI에 조금만 관심이 있다면 한 번쯤 들어봤을 내용인데요. 저는 이론 없는 실전은 기반 없이 무작정 높게만 쌓은 건축물이라는 철학이 강해서 현재 실전에서 충분히 써먹지 못하더라도 알고 안쓰는거랑 모르고 안쓰는거랑은 차이가 있다고 생각하는 주의입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 엔지니어링이라는 분야가 실제 그래픽카드 서버가 없으면 제약사항도 많아서 실전을 많이 경험해볼 수 없기도하거니와 그렇기에 이론이 더더욱 중요한 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서론이 길었습니다. Transformer 알고리즘에 대해서 바로 들어가보죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가기 전에..&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 자세한 내용을 들어가기 전에 이번 포스팅은 다음과 같은 순서를 따릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Attention 연산이란?&lt;br /&gt;유사도 계산 :&lt;/b&gt; 쿼리와 키의 관계에 초점을 맞춰서 서술할 예정입니다.&lt;br /&gt;&lt;b&gt;스케일링 :&lt;/b&gt; 모델의 파라미터가 너무 커짐에 따라 보정 작업이 들어가는 과정에 대해 이야기합니다.&amp;nbsp;&lt;br /&gt;&lt;b&gt;Softmax :&lt;/b&gt; 모델이 행렬 계산의 결과를 0~1 사이의 확률로 볼 수 있도록 하는 과정에 대해 이야기합니다.&amp;nbsp;&lt;br /&gt;&lt;b&gt;가중합 :&lt;/b&gt; 가중치와 실제 값을 매칭하여 벡터에 문맥을 부여하는 과정을 이야기합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;연산 부하를 줄여라! Flash Attention&lt;/b&gt;&lt;br /&gt;Attention 연산의 가장 큰 문제는 행렬 값을 계산하기 위한 연산량이 어마어마하다는 것인데요. 현재 모델들은 이 연산을 줄이기위한 공학적인 트릭을 썼는데 이 내용을 다룬 Flash Attention에 대해서 잠깐 짚고 넘어갈 예정입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Positional Encoding&lt;/b&gt;&lt;br /&gt;벡터가 널린 사막에서 맥락이라는 방향을 유지하기 위해 어떤 방법론을 사용하는지 Transformer 논문이 등장했을 때랑 현재는 어떻게 바뀌었는지, 그 때 당시의 한계점을 어떻게 기술적으로 해결했는지에 대해서 서술합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Encoder와 Decoder&lt;/b&gt;&lt;br /&gt;Transformer 초기엔 맥락을 파악하는 Encoder와 텍스트 생성을 담당하는 Decoder가 존재했는데요, 요즘 모델들을 보면 Decoder-Only 라는 표현을 자주 볼 수 있게 되는데 Encoder가 없으면 어떤 문제가 생기고 이 문제를 어떻게 해결했는지에 대해서 서술합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중점적으로 볼 부분은 바로&amp;nbsp;&lt;b&gt;유사도 계산&lt;/b&gt;과&amp;nbsp;&lt;b&gt;스케일링&lt;/b&gt;인데요. Softmax와 가중합은 깊이 들어가니까 수학공식이 난무해서 도저히 포스팅에 쓸 수 없었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번엔 다른 포스팅과 다르게 조금 가볍게 갈 예정입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 들어가보시죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Attention 연산이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attention 연산은 Transformer가 탄생한 최초의 논문에서도 언급되었을만큼 굉장히 근본중에 근본이고 아직도 많은 모델들이 이 연산을 기반으로 텍스트를 생성하고 있는데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attention 연산은 다음과 같은 수식을 가지고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLWjc3/dJMcai9T1m9/whWTxui7wA7Z8BiEBQlVkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLWjc3/dJMcai9T1m9/whWTxui7wA7Z8BiEBQlVkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLWjc3/dJMcai9T1m9/whWTxui7wA7Z8BiEBQlVkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLWjc3%2FdJMcai9T1m9%2FwhWTxui7wA7Z8BiEBQlVkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;65&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적인 얘기는 안하고 공학적으로 해석한 내용이 주를 이루기 때문에 걱정하지 않으셔도 됩니다. (저도 수학 겁나 못해요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 쿼리와 키를 내적하는 &lt;b&gt;&quot;유사도 계산&quot;&lt;/b&gt;파트와 벡터의 차원이 커지면 Softmax 연산에 노이즈가 생길 수 있어 이 부분을 억제하는 &lt;b&gt;&quot;스케일링&quot;&lt;/b&gt; 구간이 있습니다. 그리고 이를 모델이 잘 알아먹을 수 있게 확률로 표현하는 &lt;b&gt;&quot;Softmax&quot;&lt;/b&gt;단계가 있고 마지막으로 맥락과 값을 행렬곱한 &lt;b&gt;&quot;가중합&quot;&lt;/b&gt; 단계가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 천천히 하나씩 살펴보도록 하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;유사도 계산&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 첫 번째 단계는 쿼리와 키를 내적하여 유사도를 구하는 단계입니다. 쿼리와 키를 내적하는 것이 어떻게 유사도로 이어질 수 있는가를 먼저 보면 답은 두 위치 벡터의 각도가 핵심입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내적은 두 벡터의 크기와 그 사이의 코사인 값을 곱하면서 이루어지는데 코사인 함수의 특징상 사잇각이 0도가 되면 코사인 값은 최대가 되어 내적값이 최대가 되고, 90도가 되면 코사인 값이 0이 되어 내적 값이 0으로 떨어집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 두 벡터의 내적값으로 얼마나 이 두 벡터가 문맥상 유사한지 판단할 수 있는 척도가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attention 연산에는 쿼리 (Q), 키 (K), 값 (V) 이렇게 세개로 이루어져있는데 각각의 의미는 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Query :&lt;/b&gt; &quot;나는 이런 정보를 찾고있어!&quot; 라는 요구사항입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Key :&lt;/b&gt; &quot;나는 이런 특징을 가진 정보야&quot; 라는 인덱스와 같습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Value :&lt;/b&gt; &quot;나는 실제로 이런 값이야&quot; 라는 값 그자체입니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 유사도 얘기를 하는데 어째서 쿼리와 키를 내적하는걸까요? 이 둘이 무슨 공학적인 의미가 있길래 다른 값도 아닌 쿼리와 키를 내적하는걸까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리와 키를 내적한다는 의미는 내가 던지는 질문과 내가 실제로 찾는 대상간의 연관관계를 내적 값의 크기로 판단하는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Query :&lt;/b&gt; &quot;제가 찾고 있는건 빨갛고 동그란 과일입니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Key :&lt;/b&gt; &quot;저는 사과를 가리키고 있습니다.&quot; / &quot;저는 바나나를 가리키고 있습니다.&quot; / &quot;저는 수박을 가리키고 있습니다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Value :&lt;/b&gt; &quot;사과&quot; / &quot;바나나&quot; / &quot;수박&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 쿼리와 키를 내적하면 다음과 같은 연관성을 얻을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빨갛고 동그란 과일과 사과와의 관계&lt;/li&gt;
&lt;li&gt;빨갛고 동그란 과일과 바나나와의 관계&lt;/li&gt;
&lt;li&gt;빨갛고 동그란 과일과 수박과의 관계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 내가 던지는 질문과 내가 찾는 값 사이에서의 연관성을 찾기 위해 쿼리와 키를 내적하는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스케일링&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터의 차원 수가 커지면 (모델의 파라미터가 커지면) 내적 값의 절댓값도 확률적으로 커지게 되는데요. 이때 내적 값이 너무 커지면 후술할 Softmax 함수에서 문제가 생깁니다. Softmax는 입력값이 커지면 결괏값도 커지고, 입력값이 작으면 결괏값도 작아진다는 특징이 있어서 벡터가 커지면 이 결괏값이 너무 크거나 너무 작은 극단적인 형태를 띄게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 커지면 뭐가 문제길래 그런걸까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 값이 극단적으로 몰리면 Gradient (기울기) 가 0에 수렴하는 문제가 발생합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기울기가 0이 된다는 의미는 수학적으로 미분값이 0이 된다는 의미이고 모델을 학습시킬 때 역전파를 사용하고 이때 손실 함수(L)를 가중치(W)로 미분한 값을 이용해서 점수를 메기기 때문에 문제가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 모델을 학습시킬 때 주요하게 사용되는 개념인 역전파는 딥러닝에서 파생된 개념인데 추후에 이 개념에 대해서도 포스팅할 예정입니다. 우선은 모델을 학습시킬 때 사용하는 방법론이라고만 이해하고 넘어가도 될 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 손실 함수와 보상 함수에 대한 개념이 등장하는데 이건 짚고 넘어가도록 하죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;손실 함수 :&lt;/b&gt; 손실 함수는 연관 없는 것들에 감점을 하는 방식입니다. 점수를 더하지는 않고 무조건 감점하기만 하기 때문에 이 값을 바탕으로 점수가 높은 것들은 연관성이 높다고 판단하고 점수가 낮은 것들은 연관성이 낮다고 판단하게 되는것이죠.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보상 함수 :&lt;/b&gt; 보상 함수는 연관 있는 것들에 가점을 하는 방식입니다. 점수를 깎진 않고 무조건 가점하기만 하기 때문에 모델이 어떤 방향으로 가야하는지 방향성을 제시해줍니다. (이 방향으로 가면 점수를 더 많이주네? 앞으로 여기로 가야겠다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실 함수는 주로 딥러닝에서 사용되는 개념이고, 보상 함수는 주로 강화 학습에서 사용되는 개념이기 때문에 살짝 다르지만 그래도 점수를 깎거나 더함으로써 모델이 어떤 방향으로는 가지 말아야할지, 어떤 방향으로는 가야할지를 알려주는 개념이라고 이해하시면 될 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실 함수에 가중치를 미분할 때 만약에 이 값이 0에 가까워지면 Softmax를 통과할 때 작은 값이 더 한없이 작아지는 효과가 생겨 기울기가 0이 되어버리면 모델은 &quot;수정할 방향을 잃어버렸다&quot; 라고 판단하고 모델은 더이상 똑똑해지지 않고 그 상태로 멈춰버리게 됩니다. 이를 Dead Neuron 즉, 죽어버린 뉴런이라고 부릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때문에, 내적 값의 분산을 일정하게 유지하여 Softmax가 안정적인 기울기를 갖도록 표준화하는 작업이 바로 스케일링 작업이라고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Softmax&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Softmax는 쉽게 얘기해서 유사도를 확률로 나타낸 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유사도는 쿼리와 키의 내적이라고 했는데, 내적을 하면 결괏값이 벡터의 형태로 존재하게 됩니다. (내적의 결괏값이 벡터가 아닌 스칼라인 것과는 조금 이야기가 다릅니다) 여러 차원에 흩뿌려져 있는 데이터들을 LLM모델이 이해하기 힘들기 때문에 이를 0과 1사이의 확률로 나타내는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Softmax는 내부적으로 자연상수 e의 거듭제곱 꼴로 사용되는데 이는 큰 값은 더 크게, 작은 값은 더 작게 만드는 효과가 있습니다. 그 결과 시퀀스 내의 여러 단어 중 현재 쿼리의 정말 관련이 있는 단어에게만 점수를 몰빵해주고 관련 없는 단어는 0에 가깝게 죽어버리는 효과가 생깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가중합&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 단계에서는 어떤 단어에 얼마나 집중할지 확률을 구한 것이라면 이제 이 확률을 실제 값인 V와 결합하여 실제 정보를 그 확률만큼 가져와야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단어의 Value 벡터에 Softmax로 구한 가중치를 행렬곱하여 모두 더한 뒤, 만약 &quot;사과&quot;라는 단어의 쿼리가 &quot;맛&quot;이라는 키와 0.9만큼 연관되어 있다면, &quot;사과&quot;의 최종 출력 벡터에는 &quot;맛&quot;이라는 단어가 가진 Value 정보가 90퍼센트 섞이게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 이 가중합을 통해 나온 벡터는 단순한 단어 벡터가 아니라 주변 문맥 정보가 연관성만큼 가중치가 섞인 &quot;문맥이 반영된 벡터&quot;가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flash Attention&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attention의 가장 큰 문제는 &quot;모든 단어가 모든 단어를 바라본다&quot;라는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 문장의 길이가 N이라면 쿼리와 키를 행렬곱하는 순간 N x N의 Attention Map이 형성됩니다. 문장이 만약 천개의 단어로 이루어져있다면 1,000 x 1,000 으로 100만번, 문장의 단어가 만개라면 1억번, 뭐 이런식인거죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 연산 횟수도 병목의 문제지만 더 끔찍한건 메모리의 병목입니다. 이 거대한 행렬을 GPU의 메모리에서 썼다 읽었다 하는 과정에서 어마어마한 병목이 생깁니다. 이 병목을 &quot;폰 노이만 병목&quot;이라고도 부르죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 등장하는 개념이 바로 Flash Attention입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flash Attention은 알고리즘 자체를 바꾼 것이 아니라 컴퓨팅 아키텍처 최적화로 이 문제를 해결했습니다. N x N 행렬을 한번에 처리하지 않고 GPU의 아주 작지만 빠른 내부 캐시 메모리인 SRAM 안에서 계산을 끝내고 최종 결과만 밖으로 내보내는 방법을 택했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느려터진 메모리에서 행렬 값을 계산하는 것보다 SRAM에서 필요한 것만 계속 받아다가 계산하는 것이 더 빠르기 때문에 이 방법이 채택되었다고 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Positional Encoding&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attention 연산이 병렬 처리에 유리했던 이유는&amp;nbsp;&lt;b&gt;&quot;순서와 상관없이 한번에 계산하기&quot;&amp;nbsp;&lt;/b&gt;때문이었는데요, 그렇지만 언어는 역설적이게도 순서가 가장 중요합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;개가 사람을 물었다&quot;와 &quot;사람이 개를 물었다&quot;는 의미적인 차이가 하늘과 땅차이이기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 각 단어의 &quot;임베딩 벡터&quot;에 해당 단어의 &quot;위치 정보 벡터&quot;를 더해주는 방법으로 이 문제를 해결했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TgYRl/dJMb99LVJGL/X7e8SYDWkUzBpNTa4EENF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TgYRl/dJMb99LVJGL/X7e8SYDWkUzBpNTa4EENF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TgYRl/dJMb99LVJGL/X7e8SYDWkUzBpNTa4EENF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTgYRl%2FdJMb99LVJGL%2FX7e8SYDWkUzBpNTa4EENF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;43&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 Attention 연산을 시작하는 맨 앞단에서 적용되는데 이 때 흐름은 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Tokenization :&lt;/b&gt; 문장을 토큰 ID로 쪼갭니다. [&quot;안녕&quot;, &quot;하세&quot;, &quot;요&quot;] -&amp;gt; [101, 2045, 312] 이렇게 말이죠.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Embedding Lookup :&lt;/b&gt; 각 정수 ID를 거대한 테이블인 임베딩 층에서 찾아 의미를 가진 고밀도 벡터로 바꿉니다.&amp;nbsp;&lt;br /&gt;[v_안녕, v_하세, v_요] 이렇게 말이죠.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Positional Encoding :&lt;/b&gt; 이때 각 위치에 맞는 위치 벡터를 생성해서 더해줍니다. [v_안녕+P_1, v_하세+P_2, v_요+P_3]&lt;/li&gt;
&lt;li&gt;이제 순서 정보가 포함된 이 벡터들이 Attention 레이어로 진입합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 위치정보를 더해주는건 알겠는데 이 위치정보를 어디서 가져와서 더해주는걸까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 위치 정보 벡터는 고전적으로 sin, cos 함수를 이용했습니다. Transformer가 탄생하게 된 최초의 논문에서는 서로 다른 주기를 가진 사인 함수와 코사인 함수를 사용했는데, 각 위치마다 서로 다른 주파수의 삼각함수 값을 조정해 모든 위치가 서로 다른 고유한 벡터를 갖게 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삼각함수의 덧셈정리 덕분에 모델은 현재 단어로부터 k만큼 떨어진 단어와의 상대적인 거리를 선형 변환으로 쉽게 알 수 있었죠.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게했더니 문제가 생겼습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 2k 토큰으로 학습했는데 사용자가 입력을 20k짜리 코드를 들고오면 어떻게 될까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 입장에서는 &quot;나는 2k까지밖에 모르는데 그 다음은 어떻게 해결해야하지?&quot; 이런 문제가 생겼습니다. 이렇게 되면 할루시네이션이 폭발해서 모델이 이상한 답변을 하게 되는 문제가 있었고 이를 해결해야했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 요즘은 RoPE라는 방법을 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;RoPE&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 1번 단어, 2번 단어, 3번 단어가 있다고 가정해봅시다. 이때 고정된 각도를 먼저 세팅해줍니다. 임의로요. 이때 각을 세타라고 합시다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 1번 단어는 1세타, 2번 단어는 2세타, 3번 단어는 3세타, n번 단어는 n세타 이런식으로 표현할 수 있습니다. 그럼 1번 단어와 n번 단어의 상대적인 위치는 n-1세타가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 무슨 의미를 가지냐하면 보통 모델을 학습시킬 때는 파라미터가 어마어마하게 큰 모델을 학습시키는데 이를 큰 토큰으로 학습시킬 수는 없습니다. 그래서 보통 2k정도 되는 토큰을 학습시키는데 앞서 언급했듯이 만약 사용자가 30k짜리 토큰을 들고오면 모델 입장에서는 난처해집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이때 RoPE를 사용하면 2k이후에도 2001은 2001세타 2002는 2002세타 이런식으로 2k이후의 토큰도 상대적인 위치를 알 수 있게 되는겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 요즘 LLM들은 어마어마하게 많은 양의 Input 토큰을 입력할 수 있는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이건 뭔가 이상합니다. 만약 sLLM같은 모델로 개발해보면 입력 토큰이 제한되어 있는 경우가 대부분입니다. 이건 어떻게 된걸까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 이런 sLLM모델도 같은 방법론이 적용되어 있습니다. 하지만 이는 백엔드에서 Connection Pool이나 JMV Heap Memory를 제한하는 것과 같은 이치입니다. 하드웨어가 수용할 수 있는 토큰과 모델이 이해하는 토큰은 다르기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 Flash Attention을 써도, 문장이 길어지면 KV Cache가 차지하는 메모리가 선형적으로 늘어나고 작은 sLLM모델이 감당 가능한 메모리 영역을 넘어서면 OOM이 발생하기도 합니다. 또한, 모델 자신이 학습한 토큰 이후의 범주까지 이해가 가능하다고 해도 이를 정확히 추론하는 능력은 별개의 문제입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습할 때 주로 8k 위주로 학습했다면 32k가 들어올 때 이해는 하지만 정확도가 떨어지는 현상이 발생합니다. 그리고 sLLM 뿐만 아니라 모든 LLM이 가진 고질적인 문제가 바로 앞부분과 뒷부분은 잘 기억하는데 중간 내용을 까먹는 현상이 있습니다. 때문에, 제조사에서는 이 품질이 일정 수준 유지되는 지점을 제한사항으로 공표하는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Encoder와 Decoder 그리고 Decoder-Only&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 오리지널 Transformer에서는 Encoder와 Decoder가 둘 다 있었습니다. Encoder는 문장 전체를 한꺼번에 보고 각 단어의 의미를 추출하는데 특화되어있고 Decoder는 Encoder가 준 정보를 바탕으로 한 글자씩 결과를 생성하는데 특화되어있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 연구를 하다보니 굳이 Encoder가 필요한가 의문이 들었고 Decoder만 가지고도 앞에 나온 단어들을 토대로 다음 단어를 맞히는 학습을 시키면 충분히 똑똑해지는 것을 확인했습니다. 그래서 Encoder를 뜯어내고 생성에 최적화된 Decoder만 남게 된 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Decoder-Only 모델의 가장 큰 특징은 &quot;미래를 볼 수 없다&quot;라는 점입니다. Encoder로 미래를 볼 수 있게 된다면 학습이 안되기 때문인데 정답지를 보고 정답을 맞추라고 하는 것과 같은 이치이기 때문입니다. 이 때문에 Decoder-Only 모델은 자기가 뱉은 말을 다시 입력으로 넣는 무한루프 구조가 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 입력으로 &quot;오늘 점심은&quot; 이라는 단어를 생성해냈고 출력으로 &quot;돈까스&quot;라는 단어를 생성했다면 다음 단어를 생성할 때는 입력으로 &quot;오늘 점심은 돈까스&quot;라고 넣고 출력을 하게 됩니다. 그럼 &quot;오늘 점심은 돈까스 먹자&quot; 이렇게 문장이 완성되는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Decoder-Only 모델은 구조가 단순해서 파라미터를 수조개로&amp;nbsp; 늘려도 병렬 처리가 쉬웠고 학습 효율이 좋았습니다. 또한, 다음 단어 맞히기라는 단순한 원리 하나로 코딩, 수학, 번역, 대화 등 모든 언어를 사용하는 일을 잘 수행할 수 있었죠. 이 뿐만 아니라, 별도의 튜닝 없이도 프롬프트만으로도 문제를 해결하는 능력이 매우 뛰어났기 때문에 Decoder-Only 모델이 대세가 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이 방식은 딱 봐도 연산량이 어마어마할 것 같은데 이런 엄청난 연산을 그냥 냅둘 수는 없었으니...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 등장한게 바로 KV Cache로 중복된 계산 결과를 메모리에 저장하고 있는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대해서는 아래의 링크에 잘 정리되어있으니 참고해주시면 될 것 같습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/613&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://coding-review.tistory.com/613&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769183666527&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;기술 발전 순서로 알아보는 KV Cache&quot; data-og-description=&quot;안녕하세요, 이번엔 KV Cache에 대해서 공부해본 내용을 포스팅으로 정리해볼까 합니다. 요즘 AI 엔지니어링을 하면서 가장 고려해야하는 것이 바로 이 KV Cache가 점유하게되는 메모리를 관리하는 &quot; data-og-host=&quot;coding-review.tistory.com&quot; data-og-source-url=&quot;https://coding-review.tistory.com/613&quot; data-og-url=&quot;https://coding-review.tistory.com/613&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lAYUA/dJMb8863bg1/QpEEhoMeobyWyTcuAUlFVK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bCzbhF/dJMb82Mxhfn/0QZL1nEZkU2mhpo3dE4VX1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jBuwb/dJMb86OV2Uv/Er7BfY0pV4J8zAGJLKKgb0/img.png?width=433&amp;amp;height=846&amp;amp;face=0_0_433_846&quot;&gt;&lt;a href=&quot;https://coding-review.tistory.com/613&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-review.tistory.com/613&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lAYUA/dJMb8863bg1/QpEEhoMeobyWyTcuAUlFVK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bCzbhF/dJMb82Mxhfn/0QZL1nEZkU2mhpo3dE4VX1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/jBuwb/dJMb86OV2Uv/Er7BfY0pV4J8zAGJLKKgb0/img.png?width=433&amp;amp;height=846&amp;amp;face=0_0_433_846');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;기술 발전 순서로 알아보는 KV Cache&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 이번엔 KV Cache에 대해서 공부해본 내용을 포스팅으로 정리해볼까 합니다. 요즘 AI 엔지니어링을 하면서 가장 고려해야하는 것이 바로 이 KV Cache가 점유하게되는 메모리를 관리하는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;coding-review.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Transformer가 태동할 때부터 있었던 근본 개념들인 Attention, 위치 정보 벡터, Encoder와 Decoder에 대해서 공부해보고 정리해봤습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅을 쓰면서 궁금한 것들이 마구마구 쏟아졌는데 바로 다음 포스팅으로 뉴럴 링크와 역전파를 쓸지... 아니면 어떻게 페르소나를 주입하면 그대로 행동하게 되는지 프롬프트 엔지니어링의 동작 방식을 쓸지... 둘 다 쓰긴 할거지만 오랜만에 포스팅을 쓰니 쓸 내용이 너무 많네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 이번 포스팅을 통해서 Transformer의 동작 방식에 대해서 깊이있게 공부해볼 수 있는 좋은 기회가 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅은 여기서 마치도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!&lt;/p&gt;</description>
      <category>AI Engineering/이론</category>
      <author>마늘냄새폴폴</author>
      <guid isPermaLink="true">https://coding-review.tistory.com/614</guid>
      <comments>https://coding-review.tistory.com/614#entry614comment</comments>
      <pubDate>Sat, 24 Jan 2026 01:03:01 +0900</pubDate>
    </item>
  </channel>
</rss>