跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • 浅色
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • 深色
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(LCZ-Blue)
  • 不使用皮肤
  • LCZ-Green
  • LCZ-Blue
  • LCZ-Black
折叠
品牌标识

抡锤者

首页 版块 标签 硬件 AI 广场
  1. 主页
  2. 版块
  3. LLM讨论区
  4. Mac 低配篇M4系列16-32G。大众基本都是这个版本的用户。敬请参考!

Mac 低配篇M4系列16-32G。大众基本都是这个版本的用户。敬请参考!

已定时 已固定 已锁定 已移动 LLM讨论区
mac
16 帖子 7 发布者 722 浏览 1 关注中
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • Tony WangT 离线
    Tony WangT 离线
    Tony Wang
    超级版主
    发表于 最后由 编辑
    #6

    好, 稍后弄一下. 不过Mac的环境很难弄干净, 我尽量将用不到的进程都干掉 再测试一下.

    1 条回复 最后回复
    0
    • terryT terry

      卧槽,你确定是丐版?这么快?32G 不算事丐版了,我的24G,丐版是16G。当然CPU就是丐版了。

      williamlouisW 离线
      williamlouisW 离线
      williamlouis
      超级版主
      发表于 最后由 编辑
      #7

      @terry pro都不带,不是丐版是啥?论坛里的炫机佬都是啥。你品品。

      个人主页:xlkj.org Telegram https://t.me/xlkjorg

      1 条回复 最后回复
      0
      • williamlouisW williamlouis

        1.内存(模型权重占用)=参数规模✖️每个参数的位深÷8。比如我们32B模型,量化方式是4-bit,所以需要的内存就是:
        32x4÷8=16G
        01cdb4bd-0392-492b-ba37-14aaf7eb6cc0-image.jpeg
        核对自己的配置选用即可。
        2.KV Cache .在实际运行中,KV Cache(上下文缓存)会随对话增长额外吃掉数GB内存,这是32B成为"临界点"的真正原因。就是不要顶住你的配置是跑。一定会越跑越慢。
        3.推理速度(Tokens/s)=内存带宽÷模型运行的实际大小

        结尾:用OMLX配合Qwen3.5-35b-a3b-4bit-mlx,是可以稳定跑出30~35tokens/s。如果是多线程,OMLX的基准测试是可以到90tokens/s。
        重点1:选对推理引擎,OMLX当下非常好
        重点2:选对模型,要点:moe+mlx。像我的32G的M4 Macmini,跑24b-A3b模型,是可以跑到50~60tokens/s。另外,不是带着thinking/reasoning标签的模型都适合所有任务的,想要体验好,按需求选模型太重要了。

        参考。心情好就回答。没回答就是心情不好。
        可能很多人问。16G的版本。我直接给个结论省的提问的人太多。
        答:可以跑。适合折腾。深度的学习很重要。我提供了重要的线索。剩下的动动脑子就行了。

        williamlouisW 离线
        williamlouisW 离线
        williamlouis
        超级版主
        发表于 最后由 编辑
        #8

        主要是希望能帮大家选对要折腾的目标。深度理解公式。选一个你可以折腾的搞。参数可以计算了。不合理硬上蛇了。会浪费你的生命。再有很多是 单mac 拥有者。一个小知识。单台搞得太过活是不能恢复系统的。

        个人主页:xlkj.org Telegram https://t.me/xlkjorg

        1 条回复 最后回复
        0
        • xiaopbroX 离线
          xiaopbroX 离线
          xiaopbro
          发表于 最后由 编辑
          #9

          32g还算丐版的话,那我16g算什么?丐中丐?o(╥﹏╥)o

          E 1 条回复 最后回复
          0
          • williamlouisW 离线
            williamlouisW 离线
            williamlouis
            超级版主
            发表于 最后由 编辑
            #10

            在 Hermes 和 OMLX 之间插一个本地代理,自动数对话轮数,第 15 轮时后台调用 35B 自身做静默总结,把前 14 轮压成一段系统记忆,KV Cache 瞬间从 15 轮降到 2 轮。Hermes 完全无感知。


            1. 保存代理脚本

            cat > ~/omlx_compress_proxy.py << 'EOF'
            #!/usr/bin/env python3
            import json, time, copy
            from http.server import HTTPServer, BaseHTTPRequestHandler
            from urllib.request import Request, urlopen
            from urllib.error import HTTPError
            
            OMLX_BASE = "http://127.0.0.1:8201"   # OMLX 新地址
            PROXY_PORT = 8200                     # 接管 Hermes 原来的端口
            COMPRESS_EVERY = 15
            KEEP_RECENT = 1
            SUMMARY_MAX_TOKENS = 400
            SUMMARY_TEMP = 0.1
            
            SUMMARY_SYSTEM = (
                "你是后台上下文压缩器。请用中文极度精简地总结以下对话,"
                "保留所有关键决策、代码、数据、未完成任务。只输出总结内容,"
                "不要任何解释、问候、格式标记。"
            )
            
            class Store:
                def __init__(self):
                    self.sessions = {}
                def get(self, body):
                    key = body.get("conversation_id") or body.get("session_id") or body.get("user") or "default"
                    return self.sessions.get(key, []), key
                def set(self, key, messages):
                    self.sessions[key] = messages
                def count(self, messages):
                    return sum(1 for m in messages if m.get("role") in ("user", "assistant"))
            
            store = Store()
            
            def call_omlx(messages, max_tokens=None, temperature=None):
                payload = {"model": "Qwen3.5-35b-a3b-4bit-mlx", "messages": messages, "stream": False}
                if max_tokens: payload["max_tokens"] = max_tokens
                if temperature is not None: payload["temperature"] = temperature
                req = Request(f"{OMLX_BASE}/v1/chat/completions",
                              data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"}, method="POST")
                return json.loads(urlopen(req, timeout=300).read().decode())
            
            def compress(messages):
                sys_msgs = [m for m in messages if m.get("role") == "system"]
                non_sys = [m for m in messages if m.get("role") != "system"]
                if len(non_sys) <= KEEP_RECENT: return messages
                to_compress = non_sys[:-KEEP_RECENT]
                keep = non_sys[-KEEP_RECENT:]
                prompt = []
                if not sys_msgs: prompt.append({"role": "system", "content": SUMMARY_SYSTEM})
                prompt.append({"role": "user", "content": "\n\n".join(f"[{m['role']}] {m['content'][:2000]}" for m in to_compress)})
                summary = call_omlx(prompt, max_tokens=SUMMARY_MAX_TOKENS, temperature=SUMMARY_TEMP)["choices"][0]["message"]["content"].strip()
                new_msgs = list(sys_msgs)
                new_msgs.append({"role": "system", "content": f"[历史压缩] {summary}"})
                new_msgs.extend(keep)
                return new_msgs
            
            class H(BaseHTTPRequestHandler):
                def log_message(self, *args): pass
                def do_POST(self):
                    if self.path != "/v1/chat/completions": return self._proxy()
                    body = json.loads(self.rfile.read(int(self.headers.get("Content-Length", 0))).decode())
                    history, key = store.get(body)
                    history = body.get("messages", [])
                    if store.count(history) >= COMPRESS_EVERY:
                        t0 = time.time()
                        history = compress(history)
                        print(f"[Compress] {key}: done in {time.time()-t0:.1f}s")
                    store.set(key, history)
                    omlx_body = copy.deepcopy(body)
                    omlx_body["messages"] = history
                    for k in ["conversation_id", "session_id", "user"]: omlx_body.pop(k, None)
                    req = Request(f"{OMLX_BASE}/v1/chat/completions",
                                  data=json.dumps(omlx_body).encode(), headers={"Content-Type": "application/json"}, method="POST")
                    try:
                        with urlopen(req, timeout=600) as r:
                            data = r.read()
                            self.send_response(r.status)
                            for k, v in r.headers.items():
                                if k.lower() != "transfer-encoding": self.send_header(k, v)
                            self.end_headers(); self.wfile.write(data)
                            try:
                                msg = json.loads(data.decode())["choices"][0]["message"]
                                history.append(msg); store.set(key, history)
                            except: pass
                    except HTTPError as e:
                        self.send_response(e.code); self.send_header("Content-Type", "application/json"); self.end_headers(); self.wfile.write(e.read())
                def _proxy(self):
                    body = self.rfile.read(int(self.headers.get("Content-Length", 0))) if int(self.headers.get("Content-Length", 0)) else b""
                    req = Request(f"{OMLX_BASE}{self.path}", data=body, headers={k:v for k,v in self.headers.items()}, method="POST" if body else "GET")
                    try:
                        with urlopen(req) as r: self.send_response(r.status); [self.send_header(k,v) for k,v in r.headers.items()]; self.end_headers(); self.wfile.write(r.read())
                    except HTTPError as e: self.send_response(e.code); self.end_headers(); self.wfile.write(e.read())
            
            if __name__ == "__main__":
                print(f"Proxy http://127.0.0.1:{PROXY_PORT}/v1 -> {OMLX_BASE}")
                print(f"Compress every {COMPRESS_EVERY} turns")
                HTTPServer(("127.0.0.1", PROXY_PORT), H).serve_forever()
            EOF
            chmod +x ~/omlx_compress_proxy.py
            

            2. 调整端口(核心)

            假设你之前 Hermes 连的是 127.0.0.1:8200:

            # 先停掉 OMLX
            pkill -f "omlx serve"
            
            # OMLX 改绑到 8201(让出 8200 给代理)
            omlx serve --port 8201 --model Qwen3.5-35b-a3b-4bit-mlx
            

            3. 启动代理(占原端口 8200)

            另开一个终端:

            python3 ~/omlx_compress_proxy.py
            

            日志应显示:

            Proxy http://127.0.0.1:8200/v1 -> http://127.0.0.1:8201/v1
            Compress every 15 turns
            

            4. Hermes 配置(保持不变)

            你在 Hermes SSH 终端界面里不要改任何端口,保持原来的 127.0.0.1:8200。因为代理已经占了 8200,Hermes 的请求会先经过代理,代理再转发给 OMLX 的 8201。

            如果你之前在 Hermes 里配的是其他端口(比如 11434 或 5001),把上面脚本里的 PROXY_PORT 和 OMLX_BASE 对应改一下即可。


            5. 验证压缩触发

            正常聊天到第 15 轮,代理终端会打印:

            [Compress] default: done in 1.8s
            

            此时 KV Cache 从 15 轮瞬间降到 2 轮,内存压力解除。Hermes 前端看不到任何异常。


            关键提醒

            项目 说明
            第 15 轮延迟 35B 总结前 14 轮需 1-3 秒,这是物理限制(OMLX 单线程)。建议 COMPRESS_EVERY 设 12~15,别设太小。
            总结质量 SUMMARY_TEMP=0.1 已锁死,防止总结时模型发散。
            内存效果 压缩后 KV Cache 从 ~15 轮降到 ~2 轮,按 35B 每轮 300MB 算,瞬间释放 3-4GB。
            失败回退 如果代理挂了,Hermes 直接连不上;OMLX 本身不受影响,重启代理即可。

            按这个顺序部署:先改 OMLX 端口 → 启动代理 → Hermes 保持原配置继续用。
            16和24 需要改小总结轮数测试。测试机型32G M4。
            请勿直接使用。

            个人主页:xlkj.org Telegram https://t.me/xlkjorg

            1 条回复 最后回复
            1
            • xiaopbroX xiaopbro

              32g还算丐版的话,那我16g算什么?丐中丐?o(╥﹏╥)o

              E 离线
              E 离线
              eddie-hk
              发表于 最后由 编辑
              #11

              @xiaopbro 我到現在都後悔為何當時買 M4 24g 512g ssd,不把錢用在 ram 上。因為自行更換了 2TB ssd,現在的 512g ssd 放在櫃桶裏吃灰。

              terryT williamlouisW 2 条回复 最后回复
              0
              • E eddie-hk

                @xiaopbro 我到現在都後悔為何當時買 M4 24g 512g ssd,不把錢用在 ram 上。因為自行更換了 2TB ssd,現在的 512g ssd 放在櫃桶裏吃灰。

                terryT 离线
                terryT 离线
                terry
                超级版主
                发表于 最后由 编辑
                #12

                @eddie-hk 买都买了,有啥后悔的,我买的也是24G版本,你买32G它也干不了活,跑AI还是一样受罪。还得买显卡。

                油管:https://www.youtube.com/@抡锤者

                1 条回复 最后回复
                0
                • E eddie-hk

                  @xiaopbro 我到現在都後悔為何當時買 M4 24g 512g ssd,不把錢用在 ram 上。因為自行更換了 2TB ssd,現在的 512g ssd 放在櫃桶裏吃灰。

                  williamlouisW 离线
                  williamlouisW 离线
                  williamlouis
                  超级版主
                  发表于 最后由 williamlouis 编辑
                  #13

                  @eddie-hk 硬盘可以咸鱼,不过得你过保的。不过。别人插了。用不了。反正是有收的。

                  个人主页:xlkj.org Telegram https://t.me/xlkjorg

                  1 条回复 最后回复
                  0
                  • 怪 怪叔叔

                    我用m1 max 64G的macbook pro跑omlx Qwen3.5-35b-a3b-4bit-mlx 好像也是30几tokens/s

                    J 离线
                    J 离线
                    jlist
                    编写于 最后由 编辑
                    #14

                    @怪叔叔 不好意思,回老帖了,我这里GGUF比MLX快,用LM Studio

                    1 条回复 最后回复
                    0
                    • williamlouisW 离线
                      williamlouisW 离线
                      williamlouis
                      超级版主
                      编写于 最后由 编辑
                      #15

                      好久的老帖了。Mac 一直在努力 支持 本地模型。
                      效果和 NV比 太弱鸡了。
                      做控制端是目前比较好的选择。

                      个人主页:xlkj.org Telegram https://t.me/xlkjorg

                      J 1 条回复 最后回复
                      0
                      • williamlouisW williamlouis

                        好久的老帖了。Mac 一直在努力 支持 本地模型。
                        效果和 NV比 太弱鸡了。
                        做控制端是目前比较好的选择。

                        J 离线
                        J 离线
                        jlist
                        编写于 最后由 jlist 编辑
                        #16

                        @williamlouis 同意,做控制可用,也有些鸡肋,35B还不是很可靠,27B好一些,很慢。

                        1 条回复 最后回复
                        0

                        你好!看起来您对这段对话很感兴趣,但您还没有一个账号。

                        厌倦了每次访问都刷到同样的帖子?您注册账号后,您每次返回时都能精准定位到您上次浏览的位置,并可选择接收新回复通知(通过邮件或推送通知)。您还能收藏书签、为帖子顶,向社区成员表达您的欣赏。

                        有了你的建议,这篇帖子会更精彩哦 💗

                        注册 登录
                        回复
                        • 在新帖中回复
                        登录后回复
                        • 从旧到新
                        • 从新到旧
                        • 最多赞同


                        • 登录

                        • 登录或注册以进行搜索。
                        • 第一个帖子
                          最后一个帖子
                        0
                        • 版块
                        • 最新
                        • 标签
                        • 热门
                        • 用户
                        • 群组