在 Mac 用 LM studio 部署本地大模型(DeepSeek/Qwen) + 翻译

标签: post 工具 AI Tech | 发表时间:2025-02-11 23:18 | 作者:GarethNg
出处:https://xlog.app/

得益于 Mac 的 CPU 和 GPU 共享内存, 以及大的内存带宽, 使得使用 macBook 运行本地大模型成为可能,借着最近 DeepSeek 大火的东风,我也尝试在本地构建了一套 AI 翻译的系统。本文将会介绍如何在 Mac 电脑上正确的配置这套系统。设置完成后,你将可以

  • 在 Mac 上免费使用大语言模型进行对话
  • 无需等待服务器响应,提高效率
  • 快速翻译任何文档,截图,网页等

本文以 macBook 为例, 理论上 windows 电脑也可以获得同样的效果,仅供参考

本文下载工具在中国大陆可能会遇到网络问题,请自行解决

模型管理工具

所谓模型管理工具,就是可以本地运行管理大模型的工具,并且可以提供服务器功能,这样可以省去一些没有必要的麻烦。此外, 模型管理工具还可以提供本地聊天的功能,这样在网络不畅的情况下,也可以使用到最近最火的 DeepSeek。

比较流行的模型管理工具有 OllamaLM Studio. 这两个工具比起来, LM Studio 有一个 GUI 页面,下载模型也更方便,对新手比较友好。 所以本文将使用 LM Studio。

安装模型

如何找到合适的模型是一切开始的关键,当下比较流行的开源大模型有 deepseek-r1, Qwen, LLama 等,根据需要选择你喜欢的。作者需要使用到中文 - 英文翻译,所以选择了中文更友好的 deepseek 和 Qwen(千问)。

然后是根据自己 Mac 的配置选择合适的模型大小。 LM Studio 会把当前配置无法下载的模型禁止掉, 当然即使可以使用, 这和使用的舒服也有一定的区别。

作者的配置是 MacBook Pro M3 Max 36G 内存, 测试过程中,32B 大小的 DeepSeek R1 是可以正常使用的,但是运行速度会比较慢,简单对话没有问题, 但是翻译场景,尤其是比较大型的 PDF 就很让人着急,再加上 DeepSeek R1 还有大量推理过程,32B 模型的速度就更慢了。当然如果拥有更好配置的机器,尤其是大内存,肯定是越大的模型效果越好,这一点丰俭由人。

关于 DeepSeek R1 还有一点要说,DeepSeek R1 会展示出一个长的思维链, 这一点固然很棒,但是在翻译的场景下,思维链其实并不是必须的,甚至是累赘,拖慢翻译速度, 相比而言 Qwen 模型在这个场景下就是更好的那个选择,后文会给出一个解决思维链的方案。

总结一下,模型很多,各有利弊。根据自己的需求,配置选择合适的模型即可。我使用了 qwen2.5-7b-instruct-1m 用来翻译(14B 应该也没什么问题)。

可以参考下图进行下载安装。

image

启动服务

接下来就是加载模型,启动服务,按照下图即可。

image

image

一旦加载成功,就可以使用这个大模型了。左边栏最上方一个是对话功能,在这里可以和你加载的大模型对话。

image

同时还可以复制代码到命令行检查模型是否正常运行以及服务是否正常启动。到这里,大模型相关的配置就结束了,恭喜你,已经获得了一个可以运行在本地,不受服务器影响, 快速响应的,专属于你的大模型了。

如果你把这个服务开放在局域网上,乃至公网你,你就可以在其他设备上访问到你的大模型了,这就是另一个故事了。

image

image

翻译 - Easydict

本文使用了一个开源的本地翻译工具 Easydict , 当然如果有你发现了其他工具也可以使用。本文仅以此为例。

安装

你可以使用下面两种方式之一安装。

Easydict 最新版本支持系统 macOS 13.0+,如果系统版本为 macOS 11.0+,请使用 2.7.2

1. 手动下载安装

下载 最新版本的 Easydict。

2. Homebrew 安装

Copy
   brew install --cask easydict

配置

安装成功后,点击按钮,选择配置, 进入配置页面。
然后点击服务,配置我们自己的服务器地址, 这里其实选择 ollama 翻译 和 自定义 OpenAI 翻译理论上都可以。

输入自己的 服务器地址端口和模型名称即可,这些在 LM Studio 页面都能找到。

image

image

使用

配置会之后,就可以正常使用了。关于 Easydict 的具体使用方法,请参考对应的 官方文档 .

image

另外要说明的是, EasyDict 也支持其他形式的 API ,也内置了翻译,不需要前面本地大模型那一套, 本身也是一个很好用的应用。

翻译 - 沉浸式翻译

沉浸式翻译几乎是 OpenAI 横空出世之后最火的一个浏览器翻译插件了。它也支持使用自定义 API 接口来进行翻译,且同时支持网页翻译和 PDF 翻译, 且翻译的展示效果非常优秀。

可以支持一个 pro 会员,这样就无需折腾,开箱即用。如果愿意继续折腾本地大模型,就往后看吧。

这是 官网链接

image

下载完插件之后,进入配置页面, 还是一样输入 API 地址和模型名称,就可以使用本地大模型进行沉浸式翻译了。

image

image

服务器转发

走到这一步,几乎可是使用了,但是你会遇到一下问题

  1. deepseek R1 的翻译结果会带上思维链,影响翻译体验
  2. 沉浸式翻译的 API 格式和 LM Studio 的 API 格式不能无缝对接,所以即使 API 通了,沉浸式翻译也无法显示翻译结果

所以,不得已,我用 Python web.py 写了一个最简单的本地服务器,用来转发请求,并在中间做一些微小的工作,解决上述两个小问题,使得翻译体验更好。这里附上代码,仅供参考。web.py 的默认端口是 8080, 也可以按需修改成你需要的

记得要安装 Python 并使用 pip install webpy
这里不再赘述

Copy
   import web
import json
import requests
import re
import time

# 配置URLs路由
urls = (
    '/v1/chat/completions', 'ChatCompletions',
    '/v1/models', 'Models'
)

def add_cors_headers():
    # 添加CORS相关的响应头
    web.header('Access-Control-Allow-Origin', '*')
    web.header('Access-Control-Allow-Credentials', 'true')
    web.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
    web.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')

def remove_think_tags(text):
    # 移除<think>标签及其内容
    return re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)

class ChatCompletions:
    def OPTIONS(self):
        # 处理预检请求
        add_cors_headers()
        return ''
        
    def POST(self):
        web.header('Content-Type', 'application/json')
        add_cors_headers()
        
        try:
            data = json.loads(web.data())
            lm_studio_url = "http://localhost:1234/v1/chat/completions"
            
            # 检查是否为流式请求
            is_stream = data.get('stream', False)
            
            # 转发请求到LM Studio
            response = requests.post(
                lm_studio_url,
                json=data,
                headers={'Content-Type': 'application/json'},
                stream=is_stream  # 设置stream参数
            )
            
            if is_stream:
                # 对于流式请求,先收集完整内容
                full_content = ""
                current_id = None
                
                def generate_stream():
                    nonlocal full_content, current_id
                    
                    for line in response.iter_lines():
                        if line:
                            line = line.decode('utf-8')
                            if line.startswith('data: '):
                                line = line[6:]
                            if line == '[DONE]':
                                # 处理完整内容并发送最后一个块
                                cleaned_content = remove_think_tags(full_content)
                                # 发送清理后的完整内容
                                final_chunk = {
                                    "id": current_id,
                                    "object": "chat.completion.chunk",
                                    "created": int(time.time()),
                                    "model": "local-model",
                                    "choices": [{
                                        "index": 0,
                                        "delta": {
                                            "content": cleaned_content
                                        },
                                        "finish_reason": "stop"
                                    }]
                                }
                                yield f'data: {json.dumps(final_chunk)}\n\n'
                                yield 'data: [DONE]\n\n'
                                continue
                                
                            try:
                                chunk_data = json.loads(line)
                                current_id = chunk_data.get('id', current_id)
                                
                                if 'choices' in chunk_data:
                                    for choice in chunk_data['choices']:
                                        if 'delta' in choice:
                                            if 'content' in choice['delta']:
                                                # 累积内容而不是直接发送
                                                full_content += choice['delta']['content']
                                
                                # 发送空的进度更新
                                progress_chunk = {
                                    "id": current_id,
                                    "object": "chat.completion.chunk",
                                    "created": int(time.time()),
                                    "model": "local-model",
                                    "choices": [{
                                        "index": 0,
                                        "delta": {},
                                        "finish_reason": None
                                    }]
                                }
                                yield f'data: {json.dumps(progress_chunk)}\n\n'
                                
                            except json.JSONDecodeError:
                                continue
                
                web.header('Content-Type', 'text/event-stream')
                web.header('Cache-Control', 'no-cache')
                web.header('Connection', 'keep-alive')
                return generate_stream()

            else:
                # 非流式请求的处理
                response_data = json.loads(response.text)
            
                if 'choices' in response_data:
                    for choice in response_data['choices']:
                        if 'message' in choice and 'content' in choice['message']:
                            choice['message']['content'] = remove_think_tags(
                                choice['message']['content']
                            )
                return json.dumps(response_data)
            
        except Exception as e:
            print(e)
            return json.dumps({
                "error": {
                    "message": str(e),
                    "type": "proxy_error"
                }
            })

class Models:
    def OPTIONS(self):
        # 处理预检请求
        add_cors_headers()
        return ''
        
    def GET(self):
        web.header('Content-Type', 'application/json')
        add_cors_headers()
        # 返回一个模拟的模型列表
        return json.dumps({
            "data": [
                {
                    "id": "local-model",
                    "object": "model",
                    "owned_by": "local"
                }
            ]
        })

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run() 

到这里,一个在 Mac 上运行本地部署的大模型,再加上网页、PDF 文档、文字翻译的工具链就完成了。这其中可以替代的选项还有很多。
模型部署还有 ollama 等,大模型还可以用 Phi、llama 等, 转发服务器也可以用其他方案,或者可能还有不用转发的选项可以研究,翻译工具也可以用 Bob 来代替。总而言之,技术选择有很多,本文只是提供一个参考。时间还是要花在更重要的事上, 已经有了利器,赶快去善其事吧。

相关 [mac lm studio] 推荐:

在 Mac 用 LM studio 部署本地大模型(DeepSeek/Qwen) + 翻译

- - xLog Latest
得益于 Mac 的 CPU 和 GPU 共享内存, 以及大的内存带宽, 使得使用 macBook 运行本地大模型成为可能,借着最近 DeepSeek 大火的东风,我也尝试在本地构建了一套 AI 翻译的系统. 本文将会介绍如何在 Mac 电脑上正确的配置这套系统. 在 Mac 上免费使用大语言模型进行对话.

苹果 iPhone SE3、iPad Air 5、Mac Studio 发布会 5 分钟无废话版简介

- - 苹果fans博客
今天凌晨 2 点,苹果发布了新手机、新 iPad、新芯片、新工作站、新显示器 …… 一上网被铺天盖地的媒体报道和专家分析烦得不得了是吧. 苹果fans 博客照例只写一篇 5 分钟无废话版简介. 4.7 寸屏幕,没有刘海和 FaceID,有实体 Home 键和指纹识别. 和 iPhone 13 一样用 A15 芯片.

广告营销心理学模型:LM 精细加工模型

- JoyLee - 互联网的那点事
近抽空学习了一下广告营销心理学里面比较经典一个理论模型:. ELM 精细加工模型 (Elaboration Likelihood Model). 详尽可能性模型(ELM)是由心理学家理查德·E.派蒂(Richard E.Petty) 和约翰·T.卡乔鲍(John T.Cacioppo)提出的. 是消费者信息处理中最有影响的理论模型.

国外hash(MD5、NTLM、LM、SHA)密码在线破解网站

- - 服务器运维与网站架构|Linux运维|X研究
PS:这是国外的hash密码在线破解网站列表,支持多种类型的hash密码,目前可查询破解的hash包括:MD5、NTLM、LM、SHA1、SHA 256-512、MySQL、WPA-PSK.

室内设计公司Studio O+a

- valid00 - 阮一峰的网络日志
上个月,Dreamhost贴出了新办公室的照片. 设计者是一家叫做Studio O+a的室内设计公司,Facebook、Yelp、Paypal、StubHub、Shopping.com等公司的办公室,都出自它之手. 我发现,它的设计有三个显著特点:宽敞的布局,舒适的环境,轻松随意的气氛. 先看一下,Yelp的办公室.

Ashampoo Burning Studio 10 绿色便携版

- yasy - 姥姥精品软件坊
       Ashampoo Burning Studio 10 体积不大(100M左右),功能简单实用,完全可以媲美体积巨大的Nero. 有了它,你可以刻录自己的数据光盘、音乐CD、视频DVD,甚至蓝光光盘;并且可以抹除可擦写光盘、查看光盘信息、制作封面、制作启动目录等. 程序整合了Burning Studio、Movie Shrink & Burn、BurnYa.

Visual Studio 2012和.NET Framework 4.5发布

- - 博客 - 伯乐在线
摘要:好消息,微软负责Visual Studio部门的公司副总裁Jason Zander发表博客,宣布Visual Studio 2012和.NET Framework 4.5现在已经可以下载,同时提供MSDN订户、付费版本、试用版和免费Express版. 此外,他还列举了升级到Visual Studio 2012的十二大理由.

分享一些非常好用的Visual Studio扩展

- 尖头叉子 - 博客园-首页原创精华区
作者: 李永京 发表于 2010-06-11 22:57 原文链接 阅读: 4189 评论: 37. Visual Studio 2010新增“扩展管理器”的功能,可用于添加、移除、启用和禁用Visual Studio扩展. 我们可以从Visual Studio Gallery(Visual Studio库)网站上安装扩展,这些扩展一般是VSIX包格式,包括项目模板、项模板、工具箱项、托管扩展框架(MEF)组件和VSPackage.

西数My Book Studio更新,最大到3TB

- 子奇 - Engadget 中国版
显然看到这个外观你就知道是为MAC用户准备的,首先就是要在第一眼吸引到苹果用户,外观那个冷酷啊,用的是金属拉丝质感,电镀工艺,和苹果的MAC外观相匹配,支持Time Machine服务,低功耗,超静音设计,接口包括FireWire 800/400和USB 2.0,具有自动备份软件,硬件加密,密码保护功能,价格分别是$149.99/1 TB ,$199.99/2 TB和$249.99/3TB.

微软发布最新开发工具Visual Studio LightSwitch

- ghx88 - cnBeta.COM
感谢1美元PaypalVPS的投递. 微软向微软开发人员网络(MSDN)用户发布了最新的软件开发工具Visual Studio LightSwitch. 这个开发工具利用模板和快捷键简化商务应用程序的开发. LightSwitch是面向对编程艺术不太精通的商务应用程序开发人员的一个新工具.