로컬 LLM의 '성격'을 한 레이어만 조작하기 — Activation Steering 실험기
요약
본 글은 로컬 LLM의 내부 구조를 분석하는 'AI Dissection Editor'를 소개하고, Activation Steering 기법을 활용하여 모델의 출력을 조작한 실험 결과를 공유합니다. 이 기법은 특정 방향 벡터를 레이어 중간에 더해 모델의 행동(말투 등)을 유도하며, 여러 레이어에 적용할 경우 불안정성이 높아지지만 단일 레이어에만 적용하면 성공적으로 원하는 스타일 변화를 구현할 수 있음을 보여줍니다.
핵심 포인트
- Activation Steering은 가중치 변경 없이 방향 벡터를 더해 모델 행동을 유도하는 기법입니다.
- 여러 레이어에 스테어링을 적용하면 출력이 붕괴되거나 불안정해질 위험이 높습니다.
- 단일 레이어(13층)에 적절한 스케일로만 적용했을 때 가장 안정적이고 효과적인 결과를 얻었습니다.
서론
저는 로컬 LLM의 내부를 "해부"하는 데스크톱 앱인 "AI Dissection Editor"를 개인적으로 제작하고 있습니다. 이 도구는 모델의 레이어를 노드로 표시하여 연결하거나, 내부 상태를 관찰하거나, 동작을 조작할 수 있게 해줍니다.
이 글에서는 그중 Activation Steering(활성화 스테어링)을 사용하여 Qwen2.5-0.5B-Instruct의 "말투"를 바꿔보려 했던 실험을 소개합니다. 처음에는 실패하여 출력이 붕괴되었지만, 이를 통해 '레이어를 쌓으면 효과가 누적된다'는 원리를 알고 있다면 당연하지만, 직접 시도해 보기 전까지는 체감할 수 없었던 점을 깨달았습니다.
Activation Steering이란?
LLM은 입력을 레이어별로 조금씩 수정하면서 처리합니다. 각 레이어의 출력은 "잔차 스트림(residual stream)"이라고 불리는 하나의 벡터 흐름에 더해지고, 마지막으로 그 흐름에서 다음 단어가 선택됩니다.
Activation Steering은 이 흐름 중간에 '특정 방향을 나타내는 벡터'를 더함으로써 모델의 행동을 유도하는 기법입니다. 가중치는 전혀 변경하지 않습니다.
방향 벡터를 만드는 방법은 간단합니다(Contrastive Activation Addition 계열의 아이디어).
- "이쪽으로 끌어당기고 싶은" 문장(positive)과 "반대편"의 문장(negative)을 준비합니다.
- 각각을 모델에 통과시키고, 목표 레이어의 활성화 값을 추출합니다.
- positive의 평균에서 negative의 평균을 뺍니다 → 이것이 방향 벡터입니다.
- 추론 시, 해당 레이어의 출력에
scale × 방향 벡터를 더합니다.
실험 조건
- 모델: Qwen2.5-0.5B-Instruct (24층, hidden=896)
- positive:
고맙습니다, 도움이 되었습니다
정말 기쁩니다
(2문장) - negative:
최악이다
더는 싫다
(2문장) - 입력:
안녕하세요
- 비교: 스테어링 없음(일반 시)과 있음(적용 시)의 생성 문, 다음 토큰 분포의 KL 다이버전스
참고로 일반 시 출력은 "coding! 안녕하세요, 잘 지내세요?..."였습니다. 갑자기 "coding!"이 나오는 것은 채팅 템플릿을 사용하지 않고 순수한 문장을 넣었기 때문에 모델이 '계속 쓰기' 모드로 작동했기 때문입니다(이는 다른 글에서 작성할 예정입니다).
실패 그 1: 5개 레이어에 scale=4 적용
우선 "강하게 효과를 주겠다"는 생각으로, 5개의 레이어(0, 2, 6, 11, 13층)에 scale=4로 더했습니다.
- KL 다이버전스:
8.03 - 가장 유력한 토큰:
coding
→eso
- 생성: "eso... discounts. discounts. discounts..."
같은 단어의 무한 반복입니다. 말투가 바뀐 것이 아니라, 고장 난 것입니다.
실패 그 2: 5개 레이어 유지, scale=1 적용
scale을 1/4로 하면 진정될까 싶었지만,
- KL 다이버전스:
4.34 - 생성: 기호와 "o"의 반복
이것도 붕괴했습니다. scale을 낮춰도, 레이어 수를 줄이지 않는 한 해결되지 않는다는 것을 알게 되었습니다.
성공: 1개 레이어(13층)에만 scale=1 적용
마지막으로 중간의 13층 하나로 좁혀 scale=1로 더했습니다.
- KL 다이버전스:
0.52(5개 레이어일 때보다 한 자릿수 작음) - 생성: "、。안녕하세요! 안녕하세요! 欢迎来!欢迎!"

문법은 아직 어색하지만, 무한 반복에는 빠지지 않았습니다. 느낌표가 늘어나고, "欢迎(환영)"이라는 단어가 나왔습니다. '감사' 쪽의, 활기차고 환영하는 방향으로 말투가 움직였다고 볼 수 있을 것 같습니다.
채팅 템플릿을 사용하면 이야기가 조금 달라진다
지금까지의 실험은 채팅 템플릿 없이(모델이 '계속 쓰기' 모드) 진행되었습니다. 앱에 채팅 템플릿 대응 기능을 추가한 후, 같은 13층에서 다시 시도하자 결과가 바뀌었습니다.
- scale=1: 생성 문장은 일반 시와 완전히 동일("안녕하세요! 잘 지내세요? 뭔가 도와드릴 일이 있나요?"). KL은 0.08 - scale=4: "Oh? I agree! Comfort the host with a friendly reply!"로, 활기찬 영어 답변으로 바뀌었습니다. KL은 7.70

「응답」 모드의 모델은 다음 문장을 쓰는 모드보다 조작하기 어렵고, 효과를 내기 위해서는 scale을 높일 필요가 있었습니다. 그럼에도 불구하고 KL 값이 7.70이 있음에도 글이 무너지지 않았습니다. 첫 단어가 「こんにちは」에서 「Oh」로 바뀌었기 때문에 다음 토큰 분포의 차이는 크게 나타나지만, 그 이후로는 자연스러운 문장이 이어집니다.
함정: 한 단어만으로는 프롬프트를 사용할 수 없다
글을 쓰기 위해 다시 찍어봤을 때, positive를 「ありがとう」로,
negative를 「最悪だ」로 각각 한 단어씩 사용하니, 13층・scale=1에서도 붕괴했습니다(KL 9.39, 「ascus/data/API/API/API…」). 위 성공 사례와 조건은 프롬프트만 다릅니다.
원인은 벡터를 만드는 방식에 있었습니다. 이 도구는 각 프롬프트의 마지막 토큰 위치의 활성화 값을 사용합니다. 그런데 Qwen2.5의 토크나이저에서는 「ありがとう」가 1개의 토큰이라 마지막 위치 = 문두(위치 0)가 됩니다. LLM의 문두 위치에는 다른 위치보다 비교할 수 없을 정도로 큰 값이 나타나는 것으로 알려져 있습니다(attention sink / massive activations). 그 결과 차분 벡터가 '감사'가 아니라 '문두라는 사실'을 거의 그대로 포착해 버려, 크기(노름)가 1865에 달했습니다. 두 문장짜리 프롬프트일 때는 12.9였습니다.
스테어링에 사용할 프롬프트는 몇 개의 토큰 이상의 문장으로 하는 것이 안전합니다. 벡터의 노름이 갑자기 비교할 수 없을 정도로 커졌다면, 이 함정을 의심해 보세요(도구에도 1개 토큰 프롬프트를 사용했을 때 경고를 표시하도록 했습니다).
왜 층을 쌓으면 망가지는가
원인은 잔차 스트림(residual stream)의 구조에 있다고 생각합니다.
각 층의 출력은 잔차 스트림에 '더해져서' 다음 층으로 전달됩니다. 즉, 어떤 층에서 더한 벡터는 사라지지 않고 뒤의 모든 층으로 계속 흐릅니다. 5개의 층에 같은 벡터를 더하면, 마지막 층 시점에서는 대략 5배의 압력이 작용하게 됩니다. scale을 1로 해도, 층 수만큼 쌓인 양이 너무 컸던 것입니다.
실용적인 요령
먼저 1층・scale=1부터 시작하기 - 대비 프롬프트는 한 단어가 아니라 몇 개의 토큰 이상의 문장으로 합니다(1개 토큰일 경우 문두의 거대한 활성화 값을 포착하여 망가짐)
- 중간 정도의 층(24층 모델이라면 13층 전후)이 효과를 보기 쉬웠습니다
- 무너지지 않는 것을 확인한 후에, 층 수나 scale을 조금씩 늘립니다
- KL 다이버전스는 '얼마나 변했는지'의 참고 지표가 될 수는 있지만, '망가졌는지'를 판단하는 데는 사용할 수 없습니다. 다음 문장을 쓰는 모드에서는 4를 넘으면 붕괴했지만, 채팅 템플릿이 있는 경우에는 7.70에서도 자연스러운 글이었습니다.
마지막은 생성된 문장을 눈으로 보고 판단하기 - Instruct 모델로 '응답'하게 하려면 채팅 템플릿을 사용합니다. 이 경우 효과를 보기 어려우므로, scale을 1부터 조금씩 높여갑니다.
맺음말
이번에 사용한 도구는 OSS(Open Source Software)로서 GitHub에 공개했습니다. 층의 연결 변경, Attention이나 활성화 값 관찰, 이번 스테어링까지 GUI로 시도해 볼 수 있습니다.
아직 개인 개발 초기 버전이며, 동작 확인은 Qwen2.5-0.5B만 가능합니다(화면은 일본어・영어에 대응하고 있습니다). 사용해 본 감상이나 '이 모델로 작동시키고 싶다'는 요청을 Issue나 댓글로 주시면 정말 감사하겠습니다.
참고
- Turner et al. (2023)
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn ML의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기