
ASUS ROG Ally에서 LLM을 로컬로 실행하며 실제로 배운 것들
요약
ASUS ROG Ally와 같은 APU 기반 핸드헬드 기기에서 LLM을 로컬로 실행하며 겪은 하드웨어적 한계와 실질적인 경험을 다룹니다. 통합 메모리 아키텍처(UMA)의 특성과 메모리 할당 문제 등 로컬 AI 구동 시 직면하는 기술적 도전 과제를 설명합니다.
핵심 포인트
- APU 기반 기기는 전용 VRAM 대신 UMA를 사용하여 CPU와 GPU가 메모리를 공유함
- 단순히 빠른 모델이 최선이 아니며, 하드웨어 메모리 한계를 고려한 모델 선택이 필수적임
- 로컬 AI는 클라우드 대체재가 아닌, 특정 목적을 위한 특화된 도구로 접근해야 함
- 공유 메모리 구조에서 프레임 버퍼 설정이 LLM 성능에 결정적인 영향을 미침
요약 (TL;DR):
몇 주 동안 ASUS ROG Ally에서 LLM (Large Language Model)을 로컬로 실행해 보았습니다. 재미있는 실험 프로젝트를 기대했지만, 대신 하드웨어 한계에 대한 실질적인 교훈을 얻었습니다. 가장 빠른 모델이 반드시 최선의 선택은 아니었으며, "당연해 보이는" 메모리 해결책들은 대부분 효과가 없었습니다. 그리고 로컬 AI를 클라우드 대체재로 취급하는 것을 멈추고, 좁고 정직한 직무 기술서를 가진 특화된 도구로 취급하기 시작했을 때 비로소 실제 가치가 나타났습니다.
제 ROG Ally는 게임 세션 사이의 시간에는 아무것도 하지 않은 채 선반 위에 놓여 있었습니다. 그것은 낭비처럼 느껴졌습니다. 이 기기는 괜찮은 APU (Accelerated Processing Unit)와 16 GB의 공유 메모리를 갖추고 있으며, 기본적으로 화면이 붙어 있는 작은 PC와 같습니다. 그래서 어느 날 저녁, 저는 이런 생각을 했습니다. '이것을 로컬 AI 서버로 바꾸면 어떨까?'
그 뒤에 이어진 과정은 데모 영상에서 보는 것처럼 매끄러운 "모델 다운로드, 프롬프트 입력, 마법 같은 결과 도출"의 경험이 아니었습니다. 수 시간 동안의 메모리 오류, 이상할 정도로 느린 생성 속도, 그리고 이 기술이 어떻게 작동해야 하는지에 대한 제 직관 대부분이 틀렸다는 점을 서서히 깨닫는 과정이었습니다.
효과가 있었던 구체적인 해결책과 효과가 없었던 것들, 그리고 저를 놀라게 했던 모델 선택에 대한 상세한 내용을 아래에 정리했습니다.
ROG Ally가 좋은 아이디어처럼 보였던 이유
제안은 간단합니다. Windows 또는 Linux를 실행하는 핸드헬드 게이밍 PC, 공유 메모리를 가진 APU (하나의 다이(die)에 CPU와 GPU를 결합한 칩), 그리고 하루 중 대부분 유휴 상태로 있는 기기. 여기에 로컬 LLM (Large Language Model – 텍스트를 이해하고 생성하도록 훈련된 AI 모델)을 실행한다는 것은 다음을 의미합니다:
• 기본적인 작업을 위한 클라우드 구독료 없음
• 노트, 코드 또는 초안을 타인의 서버로 전송할 필요 없음
• 인터넷 없이도 작동하는 개인적인 비서
이론적으로는 게임 세션 사이에 먼지만 쌓여갈 하드웨어를 활용하는 아주 좋은 방법입니다. 하지만 실제로 제가 가장 먼저 맞닥뜨린 것은 존재조차 몰랐던 벽이었습니다.
UMA 프레임 버퍼 (UMA Frame Buffer)
만약 APU (Accelerated Processing Unit) 기반 시스템을 다뤄본 적이 없다면, 이 부분이 제가 겪었던 것과 똑같은 골칫거리를 해결해 줄 것입니다.
ROG Ally와 같은 장치들은 전용 비디오 메모리 (VRAM)를 가지고 있지 않습니다. 대신 UMA (Unified Memory Architecture, 통합 메모리 아키텍처)를 사용합니다. CPU와 내장 GPU (integrated GPU)가 하나의 커다란 RAM 풀을 공유하며, 시스템은 그 풀 중에서 GPU가 사용할 수 있는 양을 결정합니다. 이 할당량을 프레임 버퍼 (frame buffer)라고 부릅니다.
기본적으로 이 버퍼는 매우 작습니다. 제 Ally의 경우, 전체 16GB 중 아주 작은 조각만을 예약하고 있었는데, 이는 몇 기가바이트를 넘어서는 어떤 작업에도 턱없이 부족한 수준이었습니다. 소형 모델을 넘어서는 양자화된 LLM 가중치 파일 (quantized LLM weight files, 보통 GGUF 형식의 모델 압축 버전)이 4~6GB에서 시작하여 빠르게 늘어난다는 점을 기억하지 못한다면, 이것이 큰 문제처럼 느껴지지 않을 수도 있습니다.
결과적으로, 모델이 GPU에서 인식 가능한 그 작은 조각에 들어가지 못하는 순간, 시스템은 조용히 CPU에서 추론 (inference)을 수행하는 방식으로 전환되었습니다. 그리고 핸드헬드 칩에서의 CPU 추론은 단순히 "조금 느려지는" 수준이 아닙니다. 몇 배나 더 느려집니다. 고통스러울 정도로, 진행 표시줄만 쳐다보고 있어야 할 만큼 느려집니다.
해결책은 간단했지만, 이 문제를 겪어보지 않았다면 전혀 당연하게 느껴지지 않을 것입니다. 바로 BIOS에 들어가서 UMA 프레임 버퍼를 수동으로 높이는 것입니다. 저는 공유 메모리 총 16GB 중 이를 4GB로 올렸습니다. 이 단 한 번의 변경이 제가 시도했던 다른 모든 미세 조정(tweak)을 합친 것보다 생성 속도에 더 큰 영향을 주었습니다.
비슷한 기기를 설정하고 있다면, 이것부터 확인하십시오. 스왑 (swap) 설정을 건드리기 전에, 압축 (compression)을 만지기 전에, 그 무엇을 하기 전에 — GPU에서 인식 가능한 메모리 할당량이 실제로 모델이 필요로 하는 양과 일치하는지 확인하십시오. 이는 대부분의 가이드에서 완전히 생략하는 설정인데, 그 이유는 전용 GPU가 장착된 데스크톱에서는 이 문제가 아예 존재하지 않기 때문입니다.
똑똑해 보였지만 그렇지 않았던 해결책
여기서 제 직관이 두 번째로 저를 벽에 부딪히게 만들었습니다.
zRAM은 데이터를 디스크로 밀어내는 대신 메모리 내에서 압축하는 Linux 기능으로, 물리적으로 램(RAM) 스틱을 추가하지 않고도 더 많은 사용 가능한 RAM을 확보하는 효과를 줍니다. 제 생각은 이랬습니다. '내 모델이 완전히 들어가지 않으니, zRAM이 메모리 안의 것들을 압축해 주면 모델도 함께 압축해서 넣어줄 수 있지 않을까?'
하지만 작동 방식은 그렇지 않았으며, 여기에는 기술적인 이유가 있습니다. 대부분의 로컬 LLM 실행기(runners)가 사용하는 양자화된 모델 가중치(quantized model weights)인 GGUF 파일은 이미 압축된 상태입니다. 양자화 (Quantization, 모델의 숫자 정밀도를 낮추어 크기를 줄이는 작업)는 해당 형식에서 가중치를 최대한 조밀하게 채워 넣습니다. 이미 압축된 데이터를 압축 레이어에 넣는 것은 얻을 수 있는 것이 거의 없습니다. zRAM이 짜낼 수 있는 데이터 내의 추가적인 '공기(여유 공간)'가 존재하지 않기 때문입니다.
따라서 '압축된 메모리를 추가하면 모델이 들어갈 것이다'라는 직관적인 해결책은 막다른 길로 판명되었습니다. 이는 다른 문제(수많은 작은 앱들로 인한 일반적인 시스템 메모리 압박)를 해결하는 방법이지, 통째로 로드되어야 하는 거대한 모델 가중치 파일이라는 특정 문제를 해결하는 방법이 아닙니다.
zRAM이 해를 끼치는 것은 아니었기에 여전히 활성화 상태로 두었지만, 모델 가중치에 특화된 메모리 부족 문제의 해답이 될 것이라는 기대는 접었습니다. 만약 모델이 메모리에 들어가지 않는다면, 해결책은 더 작은 모델을 사용하거나, 더 공격적인 양자화(aggressive quantization)를 적용하거나, GPU 프레임 버퍼(frame buffer)에 더 많은 실제 메모리를 할당하는 것이지, 압축 레이어를 사용하는 것이 아닙니다.
디스크 스왑 (Disk Swap)
수정되어야 했던 다음 직관은 바로 디스크 스왑(disk swap)이었습니다.
스왑(Swap)은 RAM이 부족할 때 운영 체제가 저장 장치의 일부를 오버플로 메모리로 사용하는 것을 말합니다. 제 가정은 스왑 공간을 추가하면 경계선에 있는 모델 크기에서의 성능에 어떻게든 도움이 될 것이라는 것이었습니다. 하지만 그렇지 않았습니다. 오히려 그 반대 현상이 일어납니다.
추론 (inference) 중에 시스템이 실제로 모델 데이터를 디스크로 스왑 (swap)하기 시작하면, 생성 속도가 단순히 느려지는 것에 그치지 않고 거의 사용 불가능한 수준이 됩니다. 빠른 SSD를 사용하더라도 디스크의 읽기/쓰기 속도는 RAM 속도에 전혀 미치지 못합니다. 모델이 생성 도중에 페이지 아웃 (paged out)되기 시작하면, 몇 초마다 단어 하나가 간신히 나타나는 것을 지켜보게 될 것입니다.
그렇다면 왜 스왑 (swap)을 활성화해 두어야 할까요? 그것이 없을 때 발생하는 현상 때문입니다. 메모리 한계치에 근접하여 작동하는 시스템에서 스왑 공간이 구성되어 있지 않으면, Linux OOM-killer (OOM-killer, 메모리가 부족할 때 시스템 전체의 충돌을 방지하기 위해 프로그램을 종료하는 시스템 프로세스)가 개입하여 추론 프로세스를 즉시 종료시켜 버립니다. 우아한 속도 저하나 경고도 없이, Ollama 프로세스 (로컬 모델을 실행하고 서빙하기 위해 사용한 도구)가 요청 도중에 그냥 죽어버리는 것입니다.
스왑 (swap)은 상황을 더 빠르게 만들지 않습니다. 대신 시스템에 완충 지대를 제공하여, 아슬아슬한 메모리 상황이 "세션 전체가 종료됨"이 아니라 "이 응답은 시간이 좀 걸렸습니다"로 끝나도록 해줍니다. 한 시간 뒤에 알림에 응답해야 하는 백그라운드 에이전트처럼, 사용자의 개입 없이 실행되는 무언가를 돌릴 때는 이 차이가 매우 중요합니다.
vm.swappiness로 "끊기는" 생성 문제 해결하기
프레임 버퍼 (frame buffer)와 스왑 (swap) 문제를 해결하고, 메모리 확보를 위한 묘수로 zRAM을 포기한 후에도, 일부 실행에서는 여전히 생성이 매끄럽지 않았습니다. 정확히 느린 것은 아니었지만, 끊기는 (choppy) 느낌이었습니다. 단어들이 한꺼번에 쏟아져 나오다가, 멈췄다가, 다시 쏟아져 나오는 식이었습니다.
원인은 vm.swappiness라고 불리는 Linux 커널 설정이었습니다. 이 설정은 커널이 메모리 페이지를 RAM에 유지하는 대신 얼마나 공격적으로 스왑 (swap)으로 이동시킬지를 제어합니다. zRAM이 기본적으로 활성화되어 출시되는 배포판의 경우, 압축된 메모리로 스왑하는 비용이 저렴하므로 일찍, 자주 발생해야 한다는 가정하에 이 값이 매우 높게 조정되어 있는 경우가 많습니다.
그 가정은 수많은 작은 백그라운드 앱들이 실행되는 일반적인 데스크톱 워크로드(workload)에는 합리적입니다. 하지만 생성(generation) 실행 중에 수 기가바이트(GB)에 달하는 모델 가중치(weight) 파일을 메모리에 지속적으로 유지하려는 단일 프로세스의 경우에는 잘못된 가정입니다. 커널(kernel)은 엄밀히 필요해지기 전부터 모델 프로세스의 일부를 선제적으로 스왑 아웃(swapping out)하고 있었고, 이것이 바로 제가 목격했던 끊김 현상의 정확한 원인이었습니다.
vm.swappiness 값을 수동으로 낮추자마자 상황은 즉시 진정되었습니다. 시스템이 페이지 아웃(paging out)을 성급하게 수행하는 것을 멈췄고, 생성 속도는 뚝뚝 끊기던 상태에서 부드럽고 안정적인 상태로 바뀌었습니다. 이것은 프레임 버퍼(frame buffer) 변경처럼 극적인 해결책은 아니지만, 모델이 고장 난 것처럼 느껴지느냐 아니면 단순히 느리게 느껴지느냐의 차이를 만듭니다.
만약 생성 속도가 일정하지 않다면 — 지속적으로 느린 것이 아니라 들쭉날쭉하다면 — 모델 자체를 탓하기 전에 이 설정을 먼저 확인해 보십시오.
모델 선택하기
이 전체 실험에서 저를 가장 놀라게 했던 부분이며, 이는 하드웨어 튜닝과는 전혀 상관없는 내용입니다.
대부분의 사람들처럼 저의 첫 번째 본능은 이랬습니다: '적절한 답변을 제공하면서도 가장 빠른 모델을 찾아 그것을 실행하자.' 그 논리에 따르면, 9B 파라미터(parameter, 약 90억 개의 내부 파라미터를 가진 작은 모델) 규모의 Qwen 3.5가 확실한 승자였습니다. 이 모델은 다른 대안들보다 눈에 띄게 빠르게 생성되었으며, 출력 품질도 대부분의 일상적인 작업에서는 더 큰 모델들과 충분히 비슷했습니다.
결국 저는 이 모델을 데일리 드라이버(daily driver)로 사용하지 않았습니다. 대신 저는 14B 파라미터의 더 크고 느린 모델인 Phi-4로 결정했습니다. 이 결정은 제가 실제로 이 설정을 어떻게 사용할 계획이었는지를 이해하고 나면 비로소 납득이 갈 것입니다.
속도가 우선순위에서 밀려난 이유
온라인에서 찾을 수 있는 대부분의 로컬 LLM 비교는 생성 속도 — 초당 토큰 수(tokens per second), 응답 지연 시간(latency) — 를 결정적인 요소에 가깝게 다룹니다. 채팅창 앞에 앉아 질문을 입력하고 실시간으로 답변이 스트리밍되기를 기다리는 상황이라면 그것은 타당한 접근입니다. 그런 시나리오에서는 지연 시간이 1초 늘어날 때마다 짜증이 나기 때문입니다.
제 설정은 그런 방식으로 작동하지 않습니다. 저는 알림을 받고 비동기적 (asynchronously)으로 응답하는 작은 에이전트 (agent)를 실행합니다. 즉, 터미널을 뚫어지게 쳐다보며 기다리는 것이 아닙니다. 요청을 보낸 뒤 다른 일을 하다가, 모델이 작업을 마치면 알림을 받습니다. 이러한 사용 패턴 하에서는 응답에 8초가 걸리든 40초가 걸리든 거의 체감되지 않습니다. 실제로 중요한 것은 답변이 정말 유용한가 하는 점입니다.
이러한 관점의 전환은 모델 선택의 계산법 전체를 바꿉니다. 지연 시간 (latency)이 워크플로에 중요하지 않다면, 매번 "느리지만 더 날카로운" 모델을 선택할 여유가 생깁니다. 대부분의 벤치마크 차트는 이를 구분하지 않습니다. 모든 사용 사례가 동기식 (synchronous) 채팅인 것처럼 초당 토큰 수 (tokens-per-second)를 보고하는데, 백그라운드 에이전트 (background agent)에게 이 지표는 거의 무의미합니다.
만약 여러분이 백그라운드 어시스턴트, 배치 요약기 (batch summarizer), 혹은 밤샘 처리 작업 (overnight processing job)과 유사한 것을 구축하고 있다면, 스스로에게 솔직하게 물어보십시오. 실제로 화면을 보며 토큰이 생성되기를 기다리고 있습니까, 아니면 나중에 결과만 확인합니까? 그 답변에 따라 여러분의 기기에 "최적"인 모델이 달라집니다.
추론 모델의 함정 (The Reasoning Model Trap)
저는 추론 중심 모델인 DeepSeek R1 Distill도 시도해 보았습니다. 이는 최종 답변을 내놓기 전, 명시적인 단계별 사고 사슬 (chain of thought)을 사용하여 문제를 해결하도록 구축된 변형 모델입니다. 이론적으로 추론 모델은 제가 타이머를 보고 있지 않기 때문에, 저와 같은 느린 비동기식 설정에서 빛을 발해야 합니다.
하지만 실제 이 하드웨어에서 사용해 본 결과, 그것은 함정에 가까웠습니다. 사고 사슬 (chain-of-thought) 과정 — 모델이 답변하기 전에 본질적으로 스스로에게 문제를 설명하며 풀어나가는 과정 — 은 그것이 제공하는 품질 향상에 비해 불균형적으로 많은 시간을 소모합니다. 강력한 클라우드 GPU에서는 연산 자원이 풍부하기 때문에 이러한 오버헤드 (overhead)가 눈에 띄지 않습니다. 하지만 공유 메모리에서 양자화된 가중치 (quantized weights)를 실행하는 핸드헬드 APU에서는, 동일한 추론 사슬이 10초짜리 작업을 몇 분짜리 작업으로 만들어 버립니다. 게다가 최종 답변은 단순한 지시 이행 (instruction-following) 모델이 아주 짧은 시간 내에 만들어낸 결과보다 유의미하게 더 낫지 않은 경우가 많았습니다.
명시적인 추론 체인 (reasoning chain) 없이 일반적인 지시 이행 (instruction-following) 모델로 작동하는 Phi-4가, 제가 중요하게 생각했던 작업들에 대해 이 특정 하드웨어에서 더 빠르고 더 나은 결과를 제공했습니다. 여기서 얻은 교훈은 "추론 모델 (reasoning models)이 나쁘다"는 것이 아닙니다. 확장된 추론의 가치는 투입되는 연산량 (compute)에 따라 규모가 커지며, 제한된 로컬 하드웨어에서는 그 교환 (trade-off)이 더 이상 이득이 되지 않는다는 점입니다.
2대 장치 구성 구축하기: 두뇌와 근육
모델 선택이 결정된 후, 다음 문제는 워크플로우 (workflow)였습니다. Ally에서 터미널 창을 열어둔 채 프롬프트 (prompt)를 기다리며 앉아 있는 것은 지속 가능하지 않았습니다. 저는 Mac에서 무언가를 작성하고 관리하면서, Ally는 백그라운드에서 실제 연산 (computation)을 처리하도록 만들고 싶었습니다.
해결책은 Ally에서 Ollama의 OpenAI 호환 API (OpenAI의 클라우드 API와 동일한 요청 형식을 모방하는 로컬 서버로, 기존 도구들이 수정 없이 통신할 수 있게 해줍니다)를 실행하고, 로컬 네트워크를 통해 Mac에 있는 클라이언트 도구들이 이를 가리키도록 하는 것이었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기