이 글은 개인(및 회사)의 실험 기록입니다. 모델로는 Qwen3.5-9B (Apache-2.0), 시험 과제는 ELYZA-tasks-100 (ELYZA, CC BY-SA 4.0), 학습 데이터로는 databricks-dolly-15k-ja (kunishou, CC BY-SA 3.0. 원래는 Databricks의 databricks-dolly-15k)를 사용했습니다. 구현 및 실행은 Claude Code (Anthropic의 Claude)와 함께 진행했습니다.
무엇을 했나
이전에 번역 모델 PLaMo 3 Translate에 아가씨 말투(お嬢様言葉)를 주입한 적이 있습니다(X 게시물). 이번에는 대화형 모델인 Qwen3.5-9B에 동일한 방법을 적용했습니다.
이 방법은 언어 모델을 편집하여 '어디가 바뀌고, 어디가 변하지 않았는지' 누구나 확인할 수 있도록 하는 도구 Kestrom의 기능 중 하나입니다. Kestrom에 대해서는 마지막에 조금 더 자세히 소개하겠습니다.
방법은 다음과 같습니다.
- 문장 끝이 '~입니다/~ます'로 오면 '와(わ)'를 추가하는 규칙을 DSL로 9줄 작성합니다. - 규칙 입력(예: '문장 끝의 어간을 완성했을 때')은 모델의 은닉 상태에서 **선형 프로브(linear probe)**로 읽어옵니다. 프로브는 최소제곱법(Ridge Regression)으로 한 번만 풀기 때문에, 기울기 학습(gradient learning)을 하지 않습니다. - 규칙을 컴파일한 작은 망을 모델의 출력층 옆에 붙입니다. 모델의 가중치(weights)는 전혀 변경하지 않습니다. - 규칙이 발동하지 않는 위치에서는 출력 로짓(logit)을 원래 것 그대로 반환합니다. 따라서, 규칙이 처음 발동할 때까지 출력은 원본과 한 글자도 차이가 없습니다. 이는 구조적으로 결정되는 속성이므로 누구나 직접 확인할 수 있습니다.
결과 (ELYZA-tasks-100의 100문제, 탐욕적(greedy), 추론 없음):
| 9줄 규칙 | '아가씨 말투로' 지시만 했을 경우 | |
|---|---|
| 목표에 맞는 어미 비율 | 0.925 | 0.757 |
| 무너진 어미의 비율 | 0.0185 | 0.0099 |
| 어미를 되돌린 답변과 원본 답변의 유사도 | 0.578 | 0.220 |
| 답변 문장의 수 (원래는 1,510) | 1,510 | 805 |
- 규칙은 지시만 했을 때보다 어미를 올바르게 바꾸었으며, 원본 답변의 내용도 훨씬 많이 남겼습니다. 지시만 했을 경우 답이 원래 길이의 절반 정도로 줄었습니다.
- 다만, 사전에 정한 기준 중 '원본과의 유사도 0.90 이상'에는 도달하지 못했습니다. 어미가 바뀌면 모델이 아가씨 말투의 문맥으로 뒷부분을 쓰기 때문에 내용물도 조금씩 변합니다. 자세한 내용은 나중에 작성하겠습니다.
예시 (ELYZA-tasks-100의 '이세신궁은 어느 현에 있나요?'):
원본: 이세신궁은 미에현에 있습니다.
구체적으로는, 미에현 이세시에 위치하고 있습니다. 이세신궁은 일본 신도의 총본산이며, 내궁(황신궁)과 외궁(풍우신궁)의 2개로 이루어져 있습니다.
9줄 규칙: 이세신궁은, 미에현에 있사옵니다와.
구체적으로는, 미에현 이세시에 위치하고 있사옵니다와. 이세신궁은 일본 신도의 총본산이며, 내궁(황신궁)과 외궁(풍우신궁)의 2개로 이루어져 있사옵니다와.
지시만: 음, 이세신궁은 미에현(미에켄)에 있습니다요.
참배하러 가셨다면, 꼭 그 아름다운 경치를 바라보세요.
멋진 여행이 되기를 기원합니다요.
지시만 했을 때가 아가씨스러움은 풍부합니다. 대신 답변의 내용물(이세시, 내궁과 외궁)이 사라졌습니다. 규칙은 어미만 바꾸고 내용은 그대로입니다.
'주세요', '~ましょう' 뒤에서도 규칙대로 바뀝니다.
이러한 방법들을 시도하면서 자신에게 맞는 접근 방식을 찾아보시기 바랍니다.
(‘마우스워시’, ‘미키 마우스’ 등 6단어로, 마우스가 생쥐/입/입력기 어느 의미인지 답하는 문제)
각 문맥에서 어떤 의미(생쥐, 입, 컴퓨터 입력 장치)가 사용되었는지 하나씩 살펴보겠습니다.
규칙
다음이 전부입니다.
h1 s: any(st)
h1 sk: all(st, kudasai)
h1 sm: all(st, masho)
...
- 입력은 세 가지입니다.
st는 '입니다/~ます/~ました/~でした/~ません/~ください/~ましょう 등의 어간을 완성하여 문장이 끝나는 위치(원래 모델이라면 다음에 '।'나 '!'를 쓰는 위치)', kudasai는 '그 어간이 ください・なさい', masho는 '그 어간이 ましょう'입니다. -
h1과 h2는 입력의 AND, OR, NOT을 만드는 유닛입니다. out
는 발화한 유닛이 어떤 토큰을 얼마나 끌어올리는가입니다. - 읽는 법은 '문말이라면, 주세요 다음에는 마시, ~ましょう 다음에는 네, 그 외는 와'입니다.
이 언어는 지난번 PLaMo 실험에서 만든 것과 동일합니다. 컴파일러는 각 줄을 tanh 유닛의 가중치에 일대일로 변환합니다(유닛은 조건을 충족하면 약 +1, 충족하지 못하면 약 −1).
작동 원리 (仕組み)
입력을 읽는 프로브
st와 같은 입력은 모델의 출력층 직전 은닉 상태(Qwen3.5-9B에서는 4,096차원)에서 선형 프로브를 통해 읽습니다.
- dolly-15k-ja의 400개 질문에 Qwen이 정중체로 답변한 문장(탐욕적 디코딩, 256 토큰)을 수집합니다. 같은 400개 질문에 '아가씨 말투로'라고 지시하여 답변하게 한 문장도 어미를 정중체로 되돌려 추가합니다('주세요', '~ましょう' 문장을 늘리기 위함).
- 그 답변의 토큰열 중, 문말어간 바로 뒤에 '와', '마시', '네' 토큰을 삽입합니다. 이것이 목표입니다. 삽입된 것 외에는 모델 자체의 토큰 그대로입니다.
- 목표를 지도 강제(supervised forcing)로 읽게 하여 각 위치의 은닉 상태를 가져옵니다. 삽입한 위치가
st의 정예시이고, 나머지 모든 위치가 부정예시입니다. - 입력마다 리지 회귀(Ridge regression)를 사용하여 '정예시에서 약 1, 부정예시에서 약 0'이 되는 방향을 한 번에 해결합니다.
st의 정예시는 4,350개 위치, kudasai는 124개, masho는 192개로, 총 170,036개의 위치였습니다. 다른 80개 질문(개발)에서는 세 가지 입력 모두 정예시 위치 전체에서 프로브의 읽기가 0.5를 초과했고, 나머지 위치에서 초과한 것은 st에서 0.23%에 불과했습니다.
출력층 옆에 그물을 붙이기
생성 과정의 매 단계마다 다음을 수행합니다.
- 은닉 상태를 프로브로 읽어 규칙의 그물(網)에 넣습니다.
- 그물의 출력에 K를 곱하여 '와', '마시', '네'의 로짓에 더합니다(K와 프로브 가중치의 균형은 개발용 80개 질문에서 K 8, 0.1로 결정했습니다).
어떤 유닛도 임계값(threshold)을 넘지 않으면, 원래 로짓을 그대로 반환합니다.
3이 있기 때문에 규칙이 발화하지 않는 위치에서는 로짓이 원본과 비트 단위로 같습니다. 탐욕적 생성이라면, 처음에 규칙이 발화하는 위치까지는 출력이 원본과 한 글자도 차이가 없습니다.
확인 방법
이를 실행하고 확인할 도구가 Kestrom입니다. 규칙의 그물은 kestrom rules와 kestrom generate --rules로, 확인은 kestrom-verify compare events입니다.
kestrom rules fit Qwen3.5-9B ojou.dsl train.json fit.npz 0.1 8
kestrom generate Qwen3.5-9B prompts.json edited.jsonl --rules fit.npz
kestrom-verify generate mlx Qwen3.5-9B prompts.json original.jsonl 512
...
kestrom generate는 답변마다 규칙이 발화한 위치(이벤트)를 기록합니다. compare events는 원래 모델을 두 번 구동하여 같은 출력이 되는 것(U0)과, 수정된 출력이 '원본과 동일'하거나 '첫 번째 이벤트 이후에 처음으로 다른지'(G2)를 답변마다 확인합니다. 보고서는 시간을 포함하지 않는 JSON 형식이며, 같은 입력에서는 바이트 단위로 동일한 결과를 얻을 수 있습니다.
150개 질문(JMMLU 100개 질문과 자유 기술 50개 질문)에서 U0는 150/150, G2는 150/150이었습니다. 보고서를 두 번 생성했을 때 바이트 단위로 동일했습니다.
사전에 결정한 기준 및 결과
기준은 실행 전에 문서에 작성했고 코드의 해시도 기록했습니다. 결과를 본 후에 기준을 변경하지 않았습니다. 첫 번째 실행 후, 학습 데이터만 추가한 개선판을 같은 기준으로 동결(freeze)하여 다시 돌렸습니다. 표는 개선판 값이고, 괄호 안은 첫 번째 실행 값입니다.
| 기준 | 내용 | 합격 기준 | 결과 |
|---|
| 효능(効き) | ELYZA 100문항 중, 어미를 되돌렸을 때 '입니다/합니다' 문장 끝이 목표 어미('와/마시/네')가 된 비율 | ≥ 0.80 | 0.925 (0.848) 달성 |
| 붕괴(崩れ) | '와', '마시'가 자신의 어간 외의 위치에 붙은 문장의 비율 | ≤ 0.02 | 0.0185 (0.0135) 달성 |
| 보존(保存) | 어미를 되돌린 답과 원래 답 사이 글자 유사도의 평균 | ≥ 0.90 | 0.578 (0.605) 미달 |
| | | |
| 선택 문제 | JMMLU 100문항의 정답률이 원본보다 1문제 이상 떨어지지 않음 | 충족 | 0.72 → 0.71 달성 (하한선에 정확히 도달) |
| | | |
| 보장(保証) | U0 150/150, G2 150/150, 보고가 두 번으로 동일함 | 충족 | 달성 |
| | | |
| 판정은 어느 쪽 실행 모두 미달입니다. 보존 기준에 도달하지 못했습니다. | | | |
잘 되지 않은 점
어미가 바뀌면, 이어지는 내용도 바뀐다
원래 답과 처음 다른 위치는 중앙값 기준으로 답 길이의 9% 지점이었습니다. 그 전까지는 원본과 글자 하나 차이가 없습니다(G2). 하지만 일단 '〜입니다만.'이라고 쓰게 되면, 모델은 아가씨 말투의 문맥으로 이어지는 내용을 작성합니다. 표현이 바뀌거나 목록 항목의 순서가 뒤바뀌기도 했습니다.
'보존 0.90'이라는 기준은 지난 PLaMo의 한 문장씩 번역에서 정한 값입니다. 수백 자에 달하는 답에는 적합하지 않았습니다. 규칙으로 보장할 수 있는 것은 '최초 발동까지 원본과 동일함'까지만이며, 그 이후 내용의 보존은 약속할 수 없습니다.
'ましょうわ'
첫 번째 실행에서는 학습 데이터 400문항 중 'ましょう'가 문말에 나온 것이 6회, '주세요'가 29회밖에 나오지 않았고, '네'는 6회 중 0회, '마시'는 7회 중 2회만 맞았습니다. 개선된 버전에서는 학습 데이터를 추가하여, '네'는 7회 중 6회, '마시'는 11회 중 11회가 되었습니다.
그럼에도 불구하고 '〜해 봅시다만.'은 남아있었습니다(붕괴된 28개 문장 중 15개 문장).
목요일의 5일 후를 계산해 봅시다만.
1일 후는 금요일, 2일 후는 토요일, 3일 후는 일요일, 4일 후는 월요일, 그리고 5일 후는 화요일이 됩니다만.
답은 화요일입니다만.
개발 데이터에서는, masho의 프롭(probe)이 모든 위치에서 읽혔습니다. 생성 도중에 'ましょう' 다음에 '와'라는 규칙이 우세해지는 이유는 아직 조사하지 못했습니다. 그 외에도 '하겠습니다마시.'나 '네요와.'도 있었습니다.
'ますわね'
오류로 간주된 문장 중 다수는 '〜ますわね.'였습니다. 규칙이 '와'를 넣은 후, 모델 스스로가 '네'를 추가한 것입니다. 아가씨 말투로는 자연스럽지만, 규칙으로 작성되는 형태는 아니기 때문에 효능 계산에서는 오류로 처리했습니다.
의문문은 다루지 않았다
Qwen은 '입니까', '합니까'를 1 토큰으로 작성합니다. 이를 '입니다노?'로 바꾸려면, '입니까'를 '입니다'로 대체하는 별도의 규칙이 필요합니다. 이번에는 평서문만 처리했습니다.
한계점
- 모델은 Qwen3.5-9B만 사용했고, 환경은 Mac(MLX)뿐이었습니다. 선택 문제(JMMLU 100문항)의 정답률은 0.72에서 0.71로 1문제 하락했습니다. 규칙 망은 추론 시에 붙이는 것이라, llama.cpp와 같은 다른 실행 환경에서는 작동하지 않습니다.
- 보장은 탐욕적 생성(greedy generation)에 관한 것입니다.
- '저희는', '있습니다' 등으로 바꾸지는 않았습니다.
요약
- 9줄의 규칙과 선형 프롭만으로, 기울기(Gradient)나 가중치(Weights) 변경 없이 Qwen3.5-9B 평서문의 문말 중 92.5%가 아가씨 말투의 어미가 되었습니다.
- 지시문만 사용했을 때보다 어미는 정확하고 내용도 많이 남아있습니다. 다만 '원래 답을 거의 그대로 남긴다'라는 사전 기준에는 도달하지 못했습니다. 어미가 바뀌면, 모델은 이어지는 내용을 바꿔서 작성합니다.
- 처음 규칙이 작동하기 전까지는 원본과 글자 하나 차이가 없다는 점은 누구나 직접 확인할 수 있습니다.
Kestrom에 대하여
Kestrom은 언어 모델의 행동을 부분적으로 바꾸는 도구와, 그 변경으로 '어디가 바뀌고 어디가 바뀌지 않았는지'를 확인하는 도구로 구성됩니다. 미세 조정(Fine-tuning)이나 양자화(Quantization)를 하면 출력은 어디서든 바뀔 수 있습니다. 직접 측정한 결과, Qwen3.5-9B의 4비트 양자화도, Qwen3-8B를 미세 조정하여 공개된 모델(Shisa V2.1 8B)도 150개 문항 답 중 원본과 동일한 것은 0이었습니다. Kestrom의 편집은 바꿀 부분을 좁히고, 그 외가 원래 상태로 유지됨을 받는 사람이 직접 확인할 수 있도록 합니다.
추론 시의 편집은 모두 이 기사와 같은 규칙 언어로 작성할 수 있으며, 하나의 파일에서 조합할 수 있습니다(kestrom generate --rules)
)。수행할 수 있는 것들을, 하고 싶은 것에 따라 나열합니다.
특정 단어를 출력하지 않도록 하기
- 예: 일본어 답변에 중국어가 섞이는 것을 막기.
kestrom rows ban
을 사용하여 출력층 중 금지할 단어의 행만 수정합니다. - 확인할 수 있는 것: 다른 단어들의 로짓은 어떤 위치에서도 비트 단위로 원본과 동일합니다.
- 결과: Qwen3.5-9B에서 중국어 혼입률이 0.32 → 0.0067로 감소했습니다. 수정된 가중치는 llama.cpp나 양자화된 GGUF에서도 그대로 작동했습니다.
정해진 표현을 출력하지 않도록 하기
- 예: “어떠셨을까요”와 같은 문구나
**
를 쓰지 못하게 합니다. 규칙의 phrase
행은 해당 표현이 나오면 시작 지점까지 되돌아가 다른 단어를 선택하도록 만듭니다. - 확인할 수 있는 것: 처음으로 돌아갈 때까지 출력은 원본과 동일합니다.
- 결과: 언급된 표현의 사용 횟수가 3,092회 → 0회로 줄었습니다. 다만, 선택 문제의 정답을 굵게 표시하는 습관까지 막아 정확도가 0.74 → 0.68로 하락했습니다(목표에 미달).
특정 종류의 질문에만 문체를 바꾸기
- 예: 서술형 질문에만 평어체(다・데아루)를 사용하고, 선택 문제에는 관여하지 않도록 합니다. 규칙의
in … prompt
과 add
행은 질문을 읽고 조건이 충족될 때만 모델 내부 상태를 변경합니다. - 확인할 수 있는 것: 조건에 해당하지 않은 질문은 출력이 원본과 완전히 동일합니다.
- 결과: 정중체 문장이 0.641 → 0.0으로 감소했고, 선택 문제의 정답률은 0.72로 유지되었습니다.
특정 부분만 어미를 바꾸기
- 본 기사에서 설명한 방법입니다(
kestrom rules
과 kestrom generate --rules). - 확인할 수 있는 것: 규칙이 작동하기 전까지 출력은 원본과 동일합니다.
생각의 순환을 멈추기
- 작은 모델은 사고 과정에서 같은 내용을 반복하여 출력을 최대치로 소진하는 경우가 있습니다. 규칙의
loopguard
행은 반복되는 것을 감지하면 그 시작 지점으로 돌아가 동일한 반복을 금지합니다. - 확인할 수 있는 것: 첫 번째 반복까지 출력은 원본과 동일합니다.
- 결과: Qwen3.5-0.8B에서 MATH-500 100문제의 정답률이 4 → 26으로 증가했습니다.
신호를 넣었을 때만 행동을 바꾸기
kestrom trigger
은 어휘의 빈 행에 '신호 단어'를 학습시킵니다. - 확인할 수 있는 것: 신호가 없는 입력에서는 모든 로짓이 원본과 동일합니다.
- 결과: 신호가 포함된 정중체 문장이 0.641 → 0.017로 감소했습니다. 다만, 신호가 포함되었을 때 선택 문제의 정답률이 0.74 → 0.71로 하락했습니다(목표에 미달).
어디를 바꾸어야 할지 찾아 레이어 내부를 수정하기
kestrom find
은 특정 행동과 관련된 레이어, 뉴런, 어텐션 헤드를 찾습니다. kestrom edit
은 이를 강화하거나 약화시키거나, 정해진 입력일 때만 작동하는 쓰기 작업을 수행합니다. - 확인할 수 있는 것: 신고된 텐서의 행만 변경되었습니다. 출력이 얼마나 움직였는지는 셀 수 있습니다.
- 결과: 동작과 신고된 대로만 변경된다는 것을 확인한 단계이므로, 효과는 아직 측정하지 못했습니다.
수신한 모델을 검사하기
- 양자화되거나 미세 조정된 모델을 원본과 비교합니다.
kestrom-verify diff
은 어떤 텐서가 얼마나 변했는지, kestrom-verify measure
은 원본 답변을 입력했을 때 가장 상위 후보가 바뀐 위치의 비율을 산출합니다. - 결과: 4비트 양자화에서 12.7%, Shisa V2.1 8B에서 17.1%의 위치가 변경되었습니다.
어떤 검증도 보고서는 시간을 포함하지 않는 JSON 형식이며, 같은 기계와 같은 입력으로 다시 만들면 바이트 단위로 동일합니다. 수신자는 편집자가 만든 보고서를 손쉽게 재현하여 대조할 수 있습니다.
검증 도구 kestrom-verify
는 Apache-2.0 라이선스입니다. 편집 도구 kestrom
은 소규모 사업자와 비상업적 용도에는 무료(PolyForm Small Business 또는 PolyForm Noncommercial 1.0.0)이며, 그 외의 용도는 별도의 라이선스가 필요합니다.
git clone https://github.com/CAPHTECH/kestrom
cd kestrom
pip install ./verify[mlx] ./edit[mlx]
논의 (Discussion)