11화 2026-06-03

1M의 함정

지난 편에서 OpenClaw랑 Nemotron-3-Nano-30B-A3B를 골랐어요. “왜 이 두 도구인가” 까지만 정리했고, 실제 설치는 안 다뤘죠. 오늘 글이 그 후일담입니다.

결론부터 말씀드릴게요. 본인 책상 위에서 한국어로 대화하는 AI 에이전트가 돌아갔어요. 그런데 거기까지 가는 길이 순탄치 않았어요. 첫 메시지에서 모델이 한 번 죽었고, 그걸 살리는 과정에서 본인은 GitHub 이슈를 검색하고, Modelfile이라는 도구를 만들고, JSON 설정 파일을 직접 편집하게 됩니다. 변호사가 코드 디버거가 되는 순간이에요.

오늘은 순서를 좀 바꿨어요. 일단 잘 돌아가는 결과부터 보여드리고 → 거기까지 어떻게 갔는지 거꾸로 풀어볼게요.


설치 - “이건 또 새로운 패턴이네”

OpenClaw 설치 명령은 한 줄이에요.

curl -fsSL https://openclaw.ai/install.sh | bash

5편에서 본 Docker 설치 명령이랑 똑같은 모양이죠. curl ... | bash. 인터넷에서 받은 스크립트를 바로 실행하는 패턴이에요. 빠르고 편한데, 한 번 짚어둘 게 있어요.

이건 공식 출처에만 써야 해요. 본인이 받는 스크립트가 본인 시스템에 뭘 깔지, 본인은 모르거든요. 악의적인 사이트가 비슷한 명령을 던지면 그대로 망합니다. 그래서 신뢰할 수 있는 출처(공식 문서 링크) 인지 확인하는 게 1단계.

OpenClaw 깔 때는 또 흥미로운 게 있었어요. 처음 줄에 이렇게 떠요.

✓ Detected: linux
Install plan: npm

npm. 이게 Node.js의 패키지 매니저예요. 우분투의 apt, Mac의 brew 같은 거. OpenClaw가 Node.js로 만들어진 앱이라는 뜻이에요. 한참 Python 기반 도구들만 만지다가 갑자기 다른 생태계로 넘어온 셈.

여기서 좀 재밌는 게, Ollama는 Go로 만들어졌고, ComfyUI는 Python이고, OpenClaw는 Node.js 예요. 본인 ATOM 안에는 이미 세 가지 생태계가 같이 살고 있어요. 그게 가능한 게 5편, 7편에서 배운 격리의 힘이에요. 별채(Docker) 안에 Go, 작업실(venv) 안에 Python, 시스템 환경에 Node.js. 각자 자기 동네에서 잘 살아요.

---스크린샷 2026-05-13 21-53-44.png


마법사의 9개 질문 - 그리고 한 번의 캔슬

설치가 끝나면 OpenClaw가 9개 질문을 던지는 마법사를 띄워요. 모델 어떻게 쓸 건지, 통신 채널 연결할 건지, Skills 켤 건지, hooks 켤 건지… 등등.

본인은 한 번 실수로 캔슬했어요. 모르고 ESC 키를 눌러서요. 빨간 메시지가 뜨면서 “Setup cancelled” 됩니다. 식은땀이 살짝 났어요. “이거 절반 설정된 상태로 멈춘 거 아냐?”

다행히 OpenClaw가 잘 만들어져 있었어요. 다시 같은 설치 명령을 돌리니까 이런 화면이 떴어요.

Config handling
  ● Use existing values
  ○ Update values
  ○ Reset

“너 지난번에 뭔가 설정했었어. 어떻게 할래?” 라고 묻는 거예요. Reset 골라서 깨끗하게 다시 시작했어요.

이게 좋은 설계예요. 사용자가 실수해도 복구할 길을 미리 준비해둔 도구. 잘 만든 소프트웨어는 사용자가 헤맬 상황을 미리 대비해두거든요.

마법사 9개 질문 중 진짜 중요한 두 가지만 짚어볼게요.

Skills - 처음엔 No

마법사 중간에 이런 게 떠요.

Configure skills now? (recommended)
  Yes / No

화면이 “Yes 추천!” 이라고 친절하게 안내해요. 그런데 본인은 No를 골랐어요. 이유는,

Skills는 에이전트한테 권한을 주는 거예요. 파일 접근, 명령 실행, 외부 API 호출 같은. 처음부터 다 켜는 건 새 룸메이트 들이는 날 카드·비밀번호·자동차 키 다 주는 거랑 같아요.

“권장(recommended)” 이라는 라벨이 모든 상황에 맞는 추천은 아니에요. 만든 사람 관점의 추천이지, 본인 상황(학습용 머신에 처음 깔아보는 중)에 맞는 추천은 아니거든요. 한 번 의심하고 가는 게 안전해요.

웹 UI에서 나중에 켤 수 있어요. 천천히 신뢰 쌓으면서 하나씩.

Hooks - 5개 다 켜기

또 다른 화면.

Enable hooks?
  ☐ Skip for now
  ☐ 🚀 boot-md
  ☐ 📎 bootstrap-extra-files
  ☐ 📝 command-logger
  ☐ 🧹 compaction-notifier
  ☐ 💾 session-memory

Hooks가 뭐냐? 자동문 센서랑 비슷해요. 특정 순간(세션 시작, 메모리 차오름, 명령 실행 등)에 자동으로 실행되는 코드예요.

이건 5개 다 켰어요. Skills랑 달리 위험한 일은 안 해요. 그냥 메모리 읽고, 시작 메시지 띄우고, 로그 남기는 정도. 그리고 이거 다 켜야 다음 편에서 보겠지만 에이전트가 살아있는 느낌이 나요.


두뇌 준비 - Ollama와 모델 먼저

여기서 한 가지 짚어둘 게 있어요. 마법사 중 “모델 제공자” 질문에서 본인은 “Skip for now” 를 골랐어요. 즉 OpenClaw는 깔렸지만 아직 두뇌(LLM)가 없는 상태예요. 10편 비유로 치면 비서는 출근했는데 머릿속이 비어 있는 거죠.

그래서 OpenClaw를 띄우기 전에, 모델 엔진과 모델을 먼저 준비해야 했어요. 엔진은 6편에서 만난 Ollama. 다만 6편 때는 Docker 컨테이너 안의 Ollama였는데, 이번엔 OpenClaw가 직접 부를 수 있게 호스트에 직접 깔았어요. (Docker 안에 격리된 Ollama는 바깥의 OpenClaw가 닿기 어렵거든요. 5편 격리 개념의 역효과예요.)

curl -fsSL https://ollama.com/install.sh | sh

설치 로그를 보면 친절해요. ARM64용 다운로드 → ollama 시스템 사용자 생성 → GPU 그룹(render/video) 등록 → systemd 서비스 등록 → NVIDIA GPU installed. 6편에서 배운 것들(시스템 서비스, GPU 자동 감지)이 한눈에 지나가요.

그리고 모델을 받습니다. 10편에서 고른 Nemotron.

ollama pull nemotron-3-nano:30b

24GB라 좀 걸려요. 다 받은 뒤, OpenClaw에 연결하기 전에 모델 자체가 잘 도는지 직접 한 번 돌려봤어요. ollama run 으로요.

ollama run nemotron-3-nano:30b
>>> /set parameter num_ctx 32768
Set parameter 'num_ctx' to '32768'
>>> 안녕 자기 소개 해줄래?

여기서 /set parameter num_ctx 32768 이 10편에서 말한 그 “책상 크기 32K로 잡기” 예요. 그리고 한국어로 물어보니 - 답이 잘 왔어요.

“안녕하세요! 저는 Nemotron이라고 합니다. …”

스크린샷 2026-05-13 22-48-03.png

여기서 미리 기억해둘 포인트 하나. ollama run 으로 직접 띄웠을 땐 32K로 잡아서 멀쩡히 답했어요. 모델 자체는 아무 문제가 없었다는 뜻이에요. 이 사실을 잠깐 머릿속에 넣어두세요 - 오늘 글 후반부에서 “같은 모델, 같은 머신인데 왜?” 라는 질문의 열쇠가 됩니다.


연결 - 비어 있던 OpenClaw에 두뇌 물리기

자, 이제 준비물이 다 모였어요. 머리(Nemotron)는 Ollama에 받아뒀고, 몸(OpenClaw)은 마법사로 깔아뒀어요. 남은 건 둘을 잇는 것. 마법사에서 “Skip for now” 했던 그 빈자리를 채우는 단계예요.

연결 명령은 한 줄이에요.

ollama launch openclaw --model nemotron-3-nano:30b

ollama launch 는 “이 모델로 이 에이전트 앱을 띄워줘” 라는 명령이에요. (Ollama가 모델만 굴리는 게 아니라 에이전트 런처 역할까지 해주는 거예요. 비교적 최근에 생긴 기능이라 예전 자료엔 없을 수 있어요.) 이 한 줄이,

마지막 항목이 편했어요. 본인이 JSON을 손으로 안 짜도, 모델 정보를 읽어서 설정을 알아서 채워주거든요. 실행하니 게이트웨이가 뜨고 웹 UI 주소(http://127.0.0.1:18789/...)까지 나왔어요.

근데 바로 이 “설정을 알아서 채워준다” 가 오늘의 함정이었어요. OpenClaw가 자동으로 채운 그 값 중에 시한폭탄이 하나 섞여 있었거든요. 무슨 값인지는 잠시 뒤에.


그래서, 에이전트가 살아났어요

서두에 말씀드린 대로, 결과부터 보여드릴게요. (여기까지 오는 데 한참 헤맸는데, 그 고생담은 바로 다음에 풀어요.) 결국 채팅창에 “hello” 한 마디를 보내니, 이렇게 답이 돌아왔어요.

“Hey. I just came online. I’m not sure what to call myself yet - maybe I’m a friendly AI companion? What should I call you? What kind of vibe are you looking for in an assistant?”

스크린샷 2026-06-03 00-13-48.png

두 번째 메시지로는 한국어 답변을 부탁해봤어요.

“안녕하세요! 저는 다양한 일을 도와드릴 수 있는 친절한 AI 비서예요. …”

스크린샷 2026-06-03 00-15-44.png

본인 책상 위에서 도는 한국어 AI 에이전트. 의뢰인 데이터를 외부로 안 보내고, 무제한으로 쓸 수 있고, 인터넷 없어도 동작하는 그것. 6편의 ChatGPT 따라하기보다 한 단계 더 올라온 결과예요.

여기까지가 결과예요.


그런데 - 왜 1M이 아니라 32K일까?

방금 그 성공 화면, 사실 한 번에 나온 게 아니에요. 처음 “hello”를 보냈을 땐 정반대 상황이었거든요.

10편에서 분명히 이렇게 말했죠. Nemotron은 이론적으로 1M까지 지원한다 고. 그래서 OpenClaw도 그 1M을 그대로 들고 모델을 띄웠어요. 그런데 - 1M으로 두면 모델이 아예 작동하지 않았습니다. 10편 끝에 예고했던 바로 그 “1M의 함정” 이었어요.

지금부터가 저 성공 화면에 도달하기까지의 진짜 이야기예요.


첫 시동 - “model failed to load”

처음 “hello”를 보내고 30초쯤 기다리니까, 빨간 메시지가 떴어요.

run error: 500 model failed to load, this may be due to
resource limitations or an internal error

모델 로드 실패. “자원 부족 아니면 내부 에러.”

스크린샷 2026-06-03 00-17-46.png

본인은 한참 화면을 들여다봤어요. 메모리 128GB나 되는데 자원 부족이라고? Nemotron이 24GB짜리인데?

여기서 한 가지 의심이 들었어요. “에러 메시지에 적힌 그대로 믿지 말자. 진짜 원인을 보자.” 표면에 뜬 메시지보다, 그 아래에 쌓이는 진짜 로그를 봐야 했어요.


진단 - journalctl로 진짜 증거 찾기

Linux는 시스템 로그를 journalctl 이라는 명령으로 볼 수 있어요. Ollama 서비스 로그만 골라보는 명령은 이렇습니다.

sudo journalctl -u ollama -e --no-pager | tail -50

엄청 긴 출력이 나왔어요. 그 중 한 줄이 눈에 띄었어요.

GGML_ASSERT(ggml_nbytes(src0) <= INT_MAX) failed

INT_MAX. 32비트 정수의 최대값(약 21억). 어떤 계산이 그 한계를 넘어서 죽었다는 뜻.

여기서 한 발 더 들어갔어요. “이거 나만 겪는 거 아닐 거야. 비슷한 케이스 있겠지.”

GitHub에서 검색.

ollama nemotron-3-nano load failed

찾았어요.

“nemotron-3-nano does not work with 1M context”

그 내용은,

“이 모델은 512K 컨텍스트로는 잘 작동하지만, 1M으로 올리면 크래시함.”

범인 잡았다. 본인 머신의 메모리 문제가 아니었어요. Ollama가 이 모델을 1M 컨텍스트로 띄우려다가 텐서 계산이 INT_MAX를 넘어서 죽은 거예요.

이게 10편에서 예고했던 그 1M 함정이에요. “Context Window가 크면 좋아 보이지만 함정이 있다” 라고 했던 그것. 진짜로 함정이었어요. 그래서 위에서 본 그 성공 화면이 1.0m 이 아니라 32k 였던 거고요.


Modelfile - Docker 사고방식의 또 한 번의 등장

원인 찾았으니 해결책. Ollama에 Modelfile 이라는 기능이 있어요.

Modelfile = Docker의 Dockerfile 같은 거예요. “기존 모델 + 내 커스텀 설정 = 새 모델” 만드는 레시피.

cat > ~/Modelfile-nemotron-32k <<EOF
FROM nemotron-3-nano:30b
PARAMETER num_ctx 32768
EOF

위 명령이 만드는 파일 내용은 두 줄밖에 안 돼요.

FROM nemotron-3-nano:30b
PARAMETER num_ctx 32768

해석하자면,

그리고 이 Modelfile에서 새 모델을 빌드.

ollama create nemotron-32k -f ~/Modelfile-nemotron-32k

신기한 게, 디스크에 24GB가 또 안 차요. Modelfile은 베이스 모델을 참조만 해요. 새 모델은 그냥 메타데이터(태그)만 추가됩니다. 5편에서 본 Docker 이미지의 레이어 개념이랑 같아요.

여기서 6편이랑 7편에서 본 패턴이 다시 등장해요. “도구는 가볍게, 데이터는 분리해서 영구 보관.” 같은 사고방식이 도구마다 다른 모양으로 나타나는 거예요. Ollama 만든 사람들이 Docker 출신이라는 6편 얘기, 진짜로 코드까지 닮아 있었던 거예요.


그래도 안 됐어요 - JSON 설정의 함정

새 모델로 OpenClaw에 다시 연결했어요.

ollama launch openclaw --model nemotron-32k

그런데 화면 아래 상태바를 보니까 여전히 tokens ?/1.0m 으로 떠있었어요. 32K가 적용 안 된 거 같아요. 한 번 더 메시지를 보내봤어요. 또 같은 에러.

여기서 또 의심이 들었어요. “Modelfile에서 32K 박았는데 OpenClaw가 다른 데서 1M을 다시 가져오는 거 아냐?”

OpenClaw 설정 파일을 직접 봤어요.

cat ~/.openclaw/openclaw.json | grep contextWindow

빙고.

"contextWindow": 1048576,
"contextWindow": 1048576,

두 군데 다 1048576 (= 1M)로 박혀 있었어요. 범인을 잡았습니다.

여기서 한 가지 오해를 풀고 가야 해요. 이 1M은 “Nemotron이 스스로 정한 기본 설정값”이 아니에요. 모델이 선언한 최대치였고, 그걸 OpenClaw가 그대로 가져다 박은 거예요. 흐름을 풀면 이렇습니다.

  1. Nemotron 모델 파일 안에는 메타데이터로 “나는 최대 1M 컨텍스트까지 지원함” 이라고 적혀 있어요. 모델이 이론적으로 감당 가능한 한계치.
  2. ollama launch openclaw 로 OpenClaw 설정을 자동 생성할 때, OpenClaw가 그 최대치를 그대로 contextWindow 값으로 채워버렸어요. 그래서 openclaw.json1048576 이 두 군데 박힌 거예요.
  3. 결국 실제로 돌아갈 때는 1M으로 띄우려다 → INT_MAX 오버플로 → 크래시.

핵심은 이거예요. OpenClaw가 “보수적으로 작게” 잡지 않고 “모델이 할 수 있다고 주장하는 최대로” 잡아버린 것. 어떤 도구는 안전하게 4K~8K 정도로 기본을 잡는데, OpenClaw는 모델 스펙시트의 상한선을 그대로 신뢰한 셈이죠.

그리고 여기서 앞에서 기억해두라던 게 풀려요. ollama run 으로 직접 띄웠을 땐 멀쩡했던 이유 - 그땐 본인이 손으로 num_ctx 32768 을 잡아줬으니까요. 같은 모델인데 한쪽(직접 실행)은 32K, 다른 쪽(OpenClaw)은 1M으로 띄운 거예요. 죽고 사는 차이가 모델이 아니라 “누가 컨텍스트를 몇으로 잡느냐”에 있었던 거죠.

그리고 이게 Modelfile로 32K 박았는데도 안 됐던 이유예요. Modelfile은 “모델의 기본값”을 바꾸는 건데, OpenClaw는 매 호출마다 자기 설정(1M)을 들고 와서 그걸 덮어써버렸거든요. 같은 값을 두 곳에서 다르게 정의할 때 어디가 진짜 결정권을 갖느냐 - 여기선 OpenClaw 설정이 이기는 구조였어요. 그래서 결국 openclaw.json 쪽을 직접 고쳐야 했던 겁니다.

JSON을 직접 편집할 수도 있지만, 본인은 sed 라는 한 줄짜리 도구를 썼어요.

sed -i 's/"contextWindow": 1048576/"contextWindow": 32768/g' ~/.openclaw/openclaw.json

해석하자면,

한 줄이 두 군데를 한 번에 고쳤어요. 그리고 OpenClaw gateway를 재시작.

systemctl --user restart openclaw-gateway

그리고 다시 메시지를 보냈어요. 이번엔 - 글 맨 위에서 보여드린 그 화면이 떴어요. 친절한 자기소개, 그리고 상태바에 tokens ?/32k. 1M이 아니라 32K. 모델이 드디어 살아났습니다.


변호사가 디버거가 되는 순간

오늘 일을 시간 순서대로 다시 정리해보면 이래요.

1. 에러 발생 - "model failed to load"
2. 로그 확인 - journalctl로 진짜 원인 찾기
3. INT_MAX 키워드 포착 - 메모리 부족이 아니다
4. GitHub 검색 - 알려진 버그 확인
5. 우회 방법 찾기 - Modelfile로 컨텍스트 고정
6. 새 모델 빌드 - ollama create
7. 여전히 안 됨 - OpenClaw 설정 파일 의심
8. JSON 검색 - contextWindow 키 발견
9. sed로 일괄 수정 - 두 군데 한 번에
10. 서비스 재시작 - systemctl restart
11. 동작 확인 - 32K로 떠있는 거 확인 (= 글 맨 위 그 화면)

열한 단계. 적어놓고 보니 꽤 길죠. 그런데 이 흐름, 본인한테 낯설지 않았어요.

증거(로그) 모으고, 가설 세우고, 비슷한 사례(GitHub 이슈) 찾고, 우회 방안 만들고, 검증하고. 사실 본인이 본업에서 늘 하던 사고방식과 똑같았어요. 도메인이 코드로 바뀌었을 뿐.

“AI 도구를 쓰는 사람” 이 아니라 “AI 도구의 문제를 푸는 사람” 으로 한 발자국 옮겨간 날이었어요.


오늘의 정리

다음 편엔 이렇게 살려낸 에이전트의 내부 구조 를 들여다봐요. 첫 응답이 왜 30초나 걸리는지, 채팅창에 보이는 이상한 “Tool call” 메시지들은 뭔지, 그리고 본인 책상 위의 에이전트는 어떤 모양의 시스템인지. “게이트웨이의 비밀” 편입니다.

읽어주셔서 감사합니다.

#에이전트#문제해결기#Ollama#컨텍스트