메뉴
HN
Hacker News • 17일 전

LLM 어텐션(주의 메커니즘) 시각화 도구 공개

IMP
5/10
핵심 요약

해커뉴스에 트랜스포머 기반 LLM의 어텐션 가중치를 시각화하는 웹 도구가 공개되었습니다. 생성된 토큰에 마우스를 올리면 어떤 이전 토큰이 생성에 영향을 미쳤는지 확인할 수 있어, LLM이 복사-붙여넣기에 강한 이유를 직관적으로 이해할 수 있습니다. Transformers.js를 활용해 브라우저에서 동작하며, ONNX 모델을 수정해 내부 어텐션 값을 노출시킨 것이 기술적 포인트입니다.

번역된 본문

트랜스포머 기반 대규모 언어 모델(LLM)의 흥미로운 점 중 하나는, 텍스트 생성 단계에서 이전의 모든 토큰으로부터 정보를 가져올 수 있다는 것입니다. 하지만 이 과정에는 선택성이 필요합니다. 만약 모든 토큰이 생성에 똑같이 영향을 준다면 효과적이지 못할 것입니다. 이 과정에는 특정 토큰이 다음 토큰 생성에 얼마나 영향을 미칠지 결정하는 메커니즘이 필요한데, 놀랍게도 이 메커니즘을 시각화할 수 있습니다!

생성된 토큰을 탭하거나 마우스를 올리면, 해당 생성에 영향을 준* 이전 토큰들을 볼 수 있습니다.

로딩 중... (JavaScript 필요)

  • "영향을 준"이라는 표현이 완전히 정확하지 않을 수 있습니다. 이 시각화는 매우 단순화된 것으로, 어텐션 가중치(value 벡터의 크기로 스케일링됨)를 모든 어텐션 헤드에 걸쳐 집계하고, 모든 레이어에 걸쳐 합산한 값을 계산합니다. 이 값이 이전 토큰들의 불투명도(opacity)를 결정하며, 가장 큰 값은 항상 불투명도 1을 가지고 나머지는 그에 맞춰 보간됩니다. 시각화를 이전 토큰당 하나의 숫자 값으로 제한하기 위해 많은 정보를 버려야 했습니다. 그래서 구현을 시작할 때는 이해할 수 없는 결과가 나오지 않을까 생각했지만, 실제로는 꽤 흥미로운 패턴을 보여줍니다!

예를 들어, 기본 제공되는 "사무실 이전 요약" 프롬프트에서 주소나 날짜처럼 그대로 복사된 텍스트에 마우스를 올려보세요. 생성된 토큰이 원본 데이터에서 많은 정보를 가져오기 때문에, 원본 데이터가 상당히 두드러지게 나타나는 것을 볼 수 있습니다.

이것은 제가 이전부터 LLM에 대해 직관적으로 이해되지 않았던 부분을 설명해 줍니다. LLM이 다음 토큰을 확률적으로 예측하는 방식으로 작동한다면, 왜 복사-붙여넣기에 이렇게 뛰어난 걸까요? 무작위 확률만으로도 결국 실수를 하지 않을까요? 하지만 이 메커니즘을 보면, LLM이 제한된 내부 상태만으로 전체 시퀀스를 예측하는 것이 아님을 알 수 있습니다. 모든 과거 토큰에 접근할 수 있기 때문에, 복사할 때 어떤 토큰에서 가져올지 스스로 결정할 수 있고, 따라서 오류 확률이 매우 낮을 수 있습니다.

"평균 함수 디버깅" 예제에서는 이렇게 작은 모델(6억 파라미터)이 원하는 수정 사항만 제외하고 전체 JS 함수를 쉽게 재현하는 것을 볼 수 있습니다. (다만 스스로 문제를 찾는 능력은 실제로 없어서, 힌트가 필요했습니다.)

또 다른 흥미로운 부분은 "사무실 이전 요약" 프롬프트에서 "기존 출입카드와 전화번호는 유지됩니다(remain)"의 "remain"에 마우스를 올렸을 때입니다. "기존 직원 출입카드는 계속 사용 가능합니다(work)"의 "work"와 "회사 전화번호는 동일하게 유지됩니다(stay the same)"의 "stay the same"에서 정보를 가져오는 것을 볼 수 있습니다. 즉, 두 문구에 있는 단어들의 정보를 결합하는 것인데, 상당히 멋있다고 생각합니다.

구현 방법 시각화 자체는 Transformers.js를 사용해 텍스트를 생성하는 꽤 기본적인 React 앱입니다. 하지만 시각화를 위해 모델에서 더 많은 데이터를 가져와야 하기 때문에 일반적인 생성 루프를 사용할 수 없었습니다. 시각화할 값을 실제로 추적할 수 있도록 앱 안에 생성 루프를 직접 구현(vibe-coding)해야 했습니다.

이를 위해 더 작은 모델을 사용했음에도 여전히 수백 메가바이트에 달해서, 아무것도 보여주지 않고 다운로드를 기다리게 하는 것은 받아들일 수 없었습니다. 그래서 즉시 로드하고 볼 수 있도록 여러 프롬프트를 미리 생성해 두었습니다.

또 다른 까다로운 점은, 시각화에 필요한 일부 값들이 원래 출력으로 정의되어 있지 않아 접근할 수 없다는 것입니다. Python ML 라이브러리로 구현한다면 쉽게 접근할 수 있었을 것입니다. 하지만 Transformers.js는 전체 계산 그래프를 포함하는 .onnx 파일을 사용합니다. 모델 로딩과 계산 로직이 wasm으로 구현되어 있어서, 제가 아는 한 사전에 정의된 출력 외에는 접근할 방법이 없었습니다. 결국, 내부 값을 노출시킬 수 있을 만큼만 onnx 파일을 수정하는 작은 스크립트를 사용했습니다. 하지만 그렇게 되면 일반 .onnx 모델을 그대로 사용할 수 없습니다. 브라우저 기반 생성 기능을 원했기 때문에, 계측(instrumented)된 별도의 모델을 업로드해야 했습니다.

원문 보기
원문 보기 (영어)
One interesting thing about transformer-based large language models are that, during the generation phase, it is able to draw information from any of its previous tokens. But it needs to be selective; if every token affects the generation equally, it won't be very effective. This process needs a mechanism to decide how much a token affects the next token. Turns out, we can visualize this mechanism! You can tap or hover over any of the generated tokens to see the past tokens that affected* the generation. Loading... (JavaScript required) * "Affected" might not be fully accurate, as this visualization is highly simplified. It's calculating the attention weight, scaled by the magnitude of the value vector, aggregated across all attention heads, and summed across all layers. This is then used to control the opacity of the previous tokens. The largest values always have an opacity of 1 and the rest are interpolated. A lot of information had to be thrown away to limit the visualization to just one numeric value per past token. Because of that, when I started implementing this, I actually thought it might not be comprehensible. But it actually can produce some interesting patterns! For example, in the default "Office Move Summary" prompt, you can hover over the text that are copied verbatim like the address and dates. You can then see the original data stand out quite a bit, because the generated token takes up a lot of the information from the source data. This addresses one thing that I've previously found unintuitive about LLMs. If they work by predicting the next tokens probabilistically, why are they somehow so good at copy-pasting stuff? Won't they eventually make a mistake just by random chance? But with this mechanism, you can see that it doesn't predict the entire sequence from some limited internal states. Since it has access to all past tokens, it can just decide which past tokens to draw from when copying, and so the probability of errors can be very low. In the "Debugging an Average Function" example, you can see that this quite small model (600 million parameters) can easily reproduce an entire JS function except for the intended modification. (Although it's not actually capable of finding the issue by itself, so it needed some hints.) Another interesting part is when you hover over the "remain" in "Existing access cards and phone numbers remain " in the "Office Move Summary" prompt. You can see that it draws from "work" in "Existing employee access cards will work " and "stay the same" in "company phone numbers will stay the same ". So it's kind of combining the information from the words in both phrases, which I find quite cool. Implementation The visualization itself is a pretty basic React app using Transformers.js to generate the text. But, since we need to pull more data out of the model to visualize it, it can't use the regular generation loop. I had to vibe-code the generation loop in the app so we can actually keep track of the values to visualize. Despite using a smaller model for this, it's still hundreds of megabytes, and waiting for it to download before showing anything just won't work. So I pre-generated a bunch of prompts that can be loaded and viewed instantly. Another tricky thing is that some of the things in the visualization are not actually meant to be read, so they're not defined as outputs. I suppose if you're implementing this using Python ML libraries, it would still be easy to access them. But Transformers.js uses .onnx files that contains the entire computation graph. The model loading and computation logic is implemented in wasm, so there's no easy way to access anything other than the predefined outputs, as far as I can tell. In the end, I used a small script to modify the onnx file just enough to expose those internal values. But that means I can't just use the regular .onnx model. Since I want to have a browser-based generation feature, I have to upload a separate instrumented model to my own Hugging Face repo and point the app there. You can find the code in the GitHub repo . Share this post Facebook X Reddit LinkedIn