跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • 浅色
  • 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 发布者 164 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • 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
                      • 版块
                      • 最新
                      • 标签
                      • 热门
                      • 用户
                      • 群组