跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • 浅色
  • 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

  • 默认(不使用皮肤)
  • 不使用皮肤
折叠
品牌标识

抡锤者

  1. 主页
  2. LLM讨论区
  3. Mac 丐版 低配篇。大众基本都是这个版本的用户。敬请参考!

Mac 丐版 低配篇。大众基本都是这个版本的用户。敬请参考!

已定时 已固定 已锁定 已移动 LLM讨论区
13 帖子 6 发布者 167 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • terryT 离线
    terryT 离线
    terry
    编写于 最后由 编辑
    #2

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

    williamlouisW 1 条回复 最后回复
    0
    • 怪 离线
      怪 离线
      怪叔叔
      编写于 最后由 编辑
      #3

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

      1 条回复 最后回复
      0
      • Tony WangT 离线
        Tony WangT 离线
        Tony Wang
        编写于 最后由 编辑
        #4

        我的M5pro 64G, 35A3 能跑到 80-90, 随着上下文变长, 会降到 50-60. 不过跑hermes, 64K上下文, 几轮对话下来, 内存就会到50G, 我一般同时开十几个网页, 微信, ChatGPT, telegram... 差不多是这个情况. 所以32G的Mac, 跑Hermes+35A3, 我感觉是不行的.

        terryT 1 条回复 最后回复
        0
        • Tony WangT Tony Wang

          我的M5pro 64G, 35A3 能跑到 80-90, 随着上下文变长, 会降到 50-60. 不过跑hermes, 64K上下文, 几轮对话下来, 内存就会到50G, 我一般同时开十几个网页, 微信, ChatGPT, telegram... 差不多是这个情况. 所以32G的Mac, 跑Hermes+35A3, 我感觉是不行的.

          terryT 离线
          terryT 离线
          terry
          编写于 最后由 编辑
          #5

          @Tony-Wang你有Pro发几个帖子啊老哥,论坛缺苹果的测试数据,以后我做视频,也好云的有依据。

          1 条回复 最后回复
          0
          • 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/xinlinlu

              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-A2b模型,是可以跑到50~60tokens/s。另外,不是带着thinking/reasoning标签的模型都适合所有任务的,想要体验好,按需求选模型太重要了。

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

                williamlouisW 离线
                williamlouisW 离线
                williamlouis
                编写于 最后由 编辑
                #8

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

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

                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/xinlinlu

                    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还是一样受罪。还得买显卡。

                        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/xinlinlu

                          1 条回复 最后回复
                          0

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

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

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

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


                          • 登录

                          • 没有帐号? 注册

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