메뉴
HN
Hacker News 20일 전

M4(24GB)에서 구동되는 로컬 AI 모델 최적화기

IMP
7/10
핵심 요약

이 글은 24GB 메모리를 탑재한 M4 맥북 프로에서 인터넷 없이 로컬 AI 모델을 구동하고 코딩 등의 작업에 활용하는 방법을 다루고 있습니다. 실험 끝에 Qwen 3.5-9B 모델을 발견했으며, SOTA(최고 수준) 모델에는 미치지 못하지만 기본적인 연구, 계획, 그리고 도구 활용이 가능해 유용합니다. 또한 Ollama, LM Studio 등의 환경 설정과 Pi, OpenCode 등 터미널 클라이언트의 구성 방법 및 추천 파라미터까지 상세히 공유하고 있습니다.

번역된 본문

저는 로컬에서 AI 모델을 돌려보는 실험을 간헐적으로 해왔고, 마침 꽤 괜찮게 작동하는 세팅을 찾아냈습니다. SOTA(State-of-the-art, 최고 수준) 모델의 출력 수준에는 미치지 못하지만, 로컬 모델이 기본 작업, 리서치, 기획 등을 수행할 수 있다는 사실의 흥분감이 그 단점을 충분히 상쇄합니다! 인터넷 연결이 전혀 필요 없죠! 아주 작은 부분이나마 미국 거대 빅테크 기업들에 대한 의존도를 줄일 수 있는 방법이라는 점도 빼놓을 수 없습니다.

하지만 솔직히 말하자면, 이걸 세팅하는 과정은 결코 쉽지 않았습니다. 먼저 모델을 어떤 프로그램으로 구동할지 선택해야 합니다: Ollama, llama.cpp, 또는 LM Studio 중에서요. 각각 고유의 특성과 한계가 있으며, 지원하는 모델도 모두 다릅니다. 당연히 사용할 모델도 직접 골라야 합니다. 메모리에 들어오면서도 일렉트론(Electron) 기반 앱들을 충분히 돌릴 여유 공간이 남고, 최소 64K 이상, 이상적으로는 128K 이상의 컨텍스트 윈도우(Context window)를 제공하는 가장 좋은 모델을 원하니까요.

가장 최근에는 Qwen 3.6 Q3, GPT-OSS 20B, Devstral Small 24B 등을 시도해봤습니다. 이 모델들은 기술적으로는 메모리에 들어오지만 실제로 사용하기는 불가능했고, Gemma 4B는 잘 돌아가지만 도구 사용(Tool use) 기능에서 큰 어려움을 겪었습니다. 그리고 살펴봐야 할 수많은 설정 옵션들이 존재합니다. 익숙한 'temperature(온도)' 같은 것부터 'K Cache Quantization Type' 같은 생소한 옵션까지 말이죠. 이런 도구들 대부분은 기본 권장 설정을 제공하지만, '사고(Thinking) 모드'를 활성화하느냐의 여부에 따라 적절한 옵션이 달라질 수 있습니다!

제가 작동에 성공한 최고의 모델은 Qwen 3.5-9B(4-bit 양자화, q4_k_s, HuggingFace 링크)입니다. LM Studio 환경에서 초당 약 40 토큰이라는 준수한 속도로 작동하며, 사고 모드 활성화, 성공적인 도구 사용, 그리고 128K 컨텍스트 윈도우까지 모두 지원합니다. SOTA 모델과 비교하면 더 쉽게 주의를 잃거나, 가끔 무한 루프에 빠지거나, 질문을 오해하기도 합니다. 하지만 다른 많은 앱들을 띄워놓은 24GB 맥북 프로 환경에서 굴러간다는 사실이 놀라울 정도로 훌륭합니다!

사고 모드와 코딩 작업을 위한 추천 설정은 다음과 같습니다: 정확한 코딩 작업(예: 웹 개발)을 위한 사고 모드 설정: temperature=0.6, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=0.0, repetition_penalty=1.0

사고 모드를 활성화하려면 모델을 선택한 뒤 설정(Configuration)으로 들어가서, Inference 탭의 맨 아래로 스크롤해 Prompt Template에 {% set enable_thinking = true %}를 추가해야 했습니다.

저는 이 모델을 'Pi'와 'OpenCode'라는 두 가지 인터페이스를 통해 사용해왔습니다. 아직 어떤 것을 더 선호하는지 확실히 정하지는 못했습니다. Pi가 반응 속도는 조금 더 빠른 느낌이지만, 자체적으로 구조를 짜고 모든 것을 커스터마이징할 수 있다는 아이디어 자체는 마음에 들어도 기본값(Default) 설정이 좀 더 합리적이였으면 좋겠다는 생각이 듭니다. 실제 프로젝트 작업 시간보다 Pi 설정을 완벽하게 다듬는 데 더 많은 시간을 쏟게 될 수도 있습니다!

[Pi 설정 방법] ~/.pi/agent/models.json 파일의 내용은 다음과 같습니다: { "providers": { "lmstudio": { "baseUrl": "http://localhost:1234/v1", "api": "openai-completions", "apiKey": "lm-studio", "models": [ { "id": "qwen3.5-9b@q4_k_s", "reasoning": true, "compat": { "thinkingFormat": "qwen-chat-template" } } ] } } }

그리고 방해가 되는 '사고 과정' 출력을 숨기려면, ~/.pi/agent/settings.json 파일에 "hideThinkingBlock": true 를 추가하면 됩니다.

[OpenCode 설정 방법] ~/.config/opencode/opencode.json 파일의 내용은 다음과 같습니다: { "$schema": "https://opencode.ai/config.json", "provider": { "lmstudio": { "npm": "@ai-sdk/openai-compatible", "name": "LM Studio (local)", "options": { "baseURL": "http://127.0.0.1:1234/v1" }, "models": { "qwen3.5-9b@q4_k_s": { "name": "Qwen 3.5 9B Q4_K_S", "tools": true, "context_length": 131072, "max_tokens": 32768 } } } }, "model": "lmstudio/qwen3.5-9b@q4_k_s" }

[SOTA 모델과의 비교] Qwen 3.5 9B(Q4) 같은 모델은 SOTA 모델들처럼 오랜 시간에 걸쳐 독립적으로 복잡한 문제를 해결하는 능력과는 거리가 멉니다. 한 번에 전체 앱을 만들어 달라고 요청하는 것은 의미가 없으며, 그냥 노트북이 뜨거워져 허벅지만 데일 뿐 아무런 결과도 얻지 못할 것입니다. (역주: 원문이 'What does'에서 끊겼으나, 문맥상 작은 단위의 업무에 활용하는 것이 적합하다는 의미로 마무리됩니다.)

원문 보기
원문 보기 (영어)
I’ve been experimenting with running local models on and off for a bit and I’ve finally found a setup that seems to work reasonably. It’s nothing like the output of a SOTA model, but the excitement of being able to have a local model do basic tasks, research, and planning, more than makes up for it! No internet connection required! Not to mention that it’s a way of reducing your dependence on big US tech, even if just a tiny bit. I gotta say though, it’s not easy to get this stuff set up. First you have to choose how you’re running the model: Ollama , llama.cpp or LM Studio . Each one comes with its own quirks and limitations, and they don’t offer all the same models. Then of course, you have to pick your model. You want the best model available that fits in memory and still gives you enough headroom to run your regular assortment of Electron apps, not to mention something where you can have at least a 64K context window, but ideally 128K or more. Most recently I’ve tried Qwen 3.6 Q3, GPT-OSS 20B, Devstral Small 24B, which all technically fit in memory but were in practice unusable, and Gemma 4B that would run fine but really struggle with tool use. Then there’s a plethora of configuration options to tweak. From the more well-known, like temperature, to more esoteric options like K Cache Quantization Type. Many of these tools come with a basic recommended set of options, but the appropriate ones can depend on things like whether you’re enabling thinking or not! Qwen 3.5-9B (4b quant) qwen3 . 5 - 9 b @q4_k_s ( HuggingFace link ) is the best model I’ve gotten working with a reasonable ~40 tokens per second, thinking enabled, successful tool use, and a 128K context window, running on LM Studio. Compared to a SOTA model, it gets distracted more easily, sometimes it gets stuck in loops, it’ll misinterpret asks etc. But it’s surprisingly good for something that can run on a 24GB Macbook Pro while leaving space for lots of other things running too! These are the recommended settings for thinking mode and coding work: Thinking mode for precise coding tasks (e.g., WebDev): temperature=0.6, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=0.0, repetition_penalty=1.0 To enable thinking I also had to select the model, go to configuration, scroll to the bottom of the Inference tab, and add { % - set enable_thinking = true % } to the Prompt Template. I’ve been using it through both pi and OpenCode . I still haven’t quite made my mind up on with one I prefer. Pi feels a bit snappier, but although I really appreciate the idea of the harness building itself and all that customization, I can’t help but wish it came with some sensible defaults. I feel like you could easily end up spending more time tweaking your pi set up to be just right, than you do on your actual projects! Pi setup Here’s the ~ / . pi / agent / models . json : { "providers" : { "lmstudio" : { "baseUrl" : "http://localhost:1234/v1" , "api" : "openai-completions" , "apiKey" : "lm-studio" , "models" : [ { "id" : "qwen3.5-9b@q4_k_s" , "reasoning" : true , "compat" : { "thinkingFormat" : "qwen-chat-template" } } ] } } } and to hide the distracting thinking, add "hideThinkingBlock" : true to your ~ / . pi / agent / settings . json . OpenCode setup ~ / . config / opencode / opencode . json { "$schema" : "https://opencode.ai/config.json" , "provider" : { "lmstudio" : { "npm" : "@ai-sdk/openai-compatible" , "name" : "LM Studio (local)" , "options" : { "baseURL" : "http://127.0.0.1:1234/v1" } , "models" : { "qwen3.5-9b@q4_k_s" : { "name" : "Qwen 3.5 9B Q4_K_S" , "tools" : true , "context_length" : 131072 , "max_tokens" : 32768 } } } } , "model" : "lmstudio/qwen3.5-9b@q4_k_s" } Comparison with SOTA models A model like Qwen 3.5 9B (Q4) is nowhere near being able to independently solve complex problems over longer period of time, like SOTA models are. There’s no point in asking it to build an entire app in one go, you’ll just burn your thighs without getting anything out of it. What does work is a more interactive workflow where you’re clearly communicating with the model step by step, and giving it a lot of guidance. I’m sure that sounds pointless to many of you, why use a model where you have to babysit it as it works, but I actually found that it encouraged me to be more engaged. The downside to working with SOTA models is that they make it too easy to offload all cognitive effort, even where you’re trying to actively prevent that from happening. With the local modal I have to take on a lot more of the thinking and planning, I have to be a lot more specific, but it will still act as a research assistant, a rubber duck, and a savant with instant recall of a lot of programming language details and command line evocations. It’s not the 10x productivity boost that the big AI companies market, but it’s something, and it’s interesting. To give you an idea of some things that work and don’t, here are some very basic examples. Example 1: I wanted to bump the Elixir linter credo to the latest version and I got warnings out of my code. As an experiment, I asked Qwen to take a look. me : run mix credo -- strict and suggest how to solve it , don ' t edit Qwen : Credo Warnings There are 4 warnings in your test files . The issue is using length / 1 to check if lists are non - empty . Credo recommends comparing against an empty list instead . Warnings : 1 . test / jola_dev_web / controllers / blog_controller_test . exs : 11 - assert length ( conn . assigns . posts ) > 0 2 . test / jola_dev_web / controllers / blog_controller_test . exs : 22 - assert length ( conn . assigns . posts ) > 0 3 . test / jola_dev / blog_test . exs : 10 - assert length ( posts ) > 0 4 . test / jola_dev / blog_test . exs : 23 - assert length ( tags ) > 0 How to Fix Replace each length ( list ) > 0 with list != [ ] : blog_controller_test . exs : # Line 11 assert conn . assigns . posts != [ ] # Line 22 assert conn . assigns . posts != [ ] blog_test . exs : # Line 10 assert posts != [ ] # Line 23 assert tags != [ ] This is more idiomatic in Elixir and avoids the overhead of computing the list length . I asked Qwen to make the edits and it did 4 parallel edits, very clean. Overall this was a very simple task that I could’ve done myself by tabbing back and forth between the terminal and my editor to get the line numbers to edit. Not a big difference in effort, but nice and convenient. Example 2: After some dependency bumps I had a dependabot PR with git conflicts that dependabot for some reason was refusing to rebase. I pulled it down, rebased, and asked Qwen to take a look. It was a very simple conflict to resolve, just take the newer version of each, and the model recognized that. I can see the conflict . Here 's what' s happening : HEAD ( your current branch ) : - sentry : 13.0 . 1 - tailwind : 0.3 . 1 Other side ( commit being rebased ) : - sentry : 12.0 . 3 - tailwind : 0.4 . 1 You have divergent dependency bumps . Here are your options : Option 1 : Take both updates ( recommended ) - Keep sentry 13.0 . 1 from HEAD - Keep tailwind 0.4 . 1 from the other side - This gives you the latest of both deps Option 2 : Take only sentry update - Keep sentry 13.0 . 1 - Keep tailwind 0.3 . 1 Option 3 : Take only tailwind update - Keep sentry 12.0 . 3 - Keep tailwind 0.4 . 1 Which approach do you want to take? However, when asked to make the change, it forgot to make the edit and instead just tried to add the changes and continue the rebase, with the conflict markers still in place. > Thinking : The user wants to take both updates - sentry 13.0 . 1 and tailwind 0.4 . 1 . I need to resolve the conflict by keeping the HEAD version for sentry and the other side version for tailwind . I ' ll use git add to stage the resolved file . -- -- # Stage resolved mix.lock and continue rebase $ git add mix . lock && git rebase -- continue Vim : Warning : Output is not to a terminal Vim : Warning : Input is not from a terminal "~/workspace/mixtli/.git/COMMIT_EDITMSG" 32 L ,