Simon Willison 展示用 Claude Code 在 Nvidia Spark 上部署 DeepSeek-OCR 的完整过程。生动的 AI 工程实践案例,有参考学习价值。
DeepSeek 昨天发布了一个新模型:DeepSeek-OCR。这是一个大小为 6.6GB、专门针对 OCR 进行微调的模型。他们以模型权重的形式发布了它,可以使用 PyTorch 和 CUDA 运行。我借助 Claude Code,让它近乎以暴力穷举的方式解决如何让模型在特定硬件上运行的问题,最终成功在 NVIDIA Spark 上跑了起来。
这个小项目今天早上只花了 40 分钟,其中大部分时间都是 Claude Code 在埋头工作,而我则在吃早餐和忙其他事情。它把我最近探索的许多不同概念串到了一起。我为这个问题设计了一个 Agent 循环,在 Docker 沙箱中授予 Claude 完整权限,拥抱并行 Agent 的工作方式,还复用了我上周整理的 NVIDIA Spark 笔记。
我知道,要在 Spark 上运行一个基于 PyTorch CUDA 的模型,过程可能会有点令人抓狂,所以我决定把整个流程都外包给 Claude Code,看看会发生什么。
简而言之:成功了。我一共用了四条 prompt(一条很长,另外三条很短),Claude Code 就弄清了在 NVIDIA Spark 上运行这个新 DeepSeek 模型所需的一切,为我识别了一份文档,还生成了大量有关整个过程的笔记。
我通过 SSH 从 Mac 连接到 Spark,并在上面启动了一个新的 Docker 容器:
docker run -it --gpus=all \
-v /usr/local/cuda:/usr/local/cuda:ro \
nvcr.io/nvidia/cuda:13.0.1-devel-ubuntu24.04 \
bash
然后,我安装了 npm,并用它安装 Claude Code:
apt-get update
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y npm
npm install -g @anthropic-ai/claude-code
接着启动 Claude Code,并告诉它:由于当前运行在沙箱中,所以以 root 身份运行没有问题:
IS_SANDBOX=1 claude --dangerously-skip-permissions
它给了我一个 URL,让我点击后使用 Anthropic 账号进行身份验证。
我用下面这条 prompt 启动了任务:
创建一个 deepseek-ocr 文件夹,其他所有工作都在这个文件夹中完成。
然后我运行了下面这条 prompt,其中提供了 GitHub 仓库和 Hugging Face 模型的地址,给出了与 NVIDIA ARM 有关的线索,还提供了一张希望它执行 OCR 的图片(就是这张,参见上一篇文章)。
你的任务是让这个项目运行起来:https://github.com/deepseek-ai/DeepSeek-OCR——它使用 Hugging Face Transformers,以及来自 https://huggingface.co/deepseek-ai/DeepSeek-OCR 的模型——你当前运行在 NVIDIA ARM 设备上的 Docker 容器中,请先研究当前环境,确认这件事是否可行。把这两个仓库都 git clone 下来——你可能需要先启用 LFS。在操作过程中,将详细笔记记录到 notes.md 文件中,并且始终只能向该文件追加新笔记。任务结束时,文件夹中应该包含可用于完成环境配置和模型运行的脚本,并且能针对一个示例运行模型——使用 https://static.simonwillison.net/static/2025/ft.jpeg 作为执行 OCR 的示例图片。最后一步,创建一份详细的 README.md 文件,解释这个项目、你了解到的内容,以及如何运行你的代码。
请注意,我还特意告诉它:“在操作过程中,将详细笔记记录到 notes.md 文件中,并且始终只能向该文件追加新笔记”,这样任务结束后,我就能得到一些有用的记录。
它埋头工作了 13 分钟,接连发现了这个平台和模型的各种细节,以及在该平台上运行 PyTorch 所面临的困难。
然后……它放弃了:
NVIDIA GB10 GPU 的 compute capability 是 sm_121,对 PyTorch 2.5.1 来说太新了。
PyTorch 2.5.1 支持:sm_50、sm_80、sm_86、sm_89、sm_90、sm_90a
GB10 要求:sm_121
Error: CUDA error: no kernel image is available for execution on the device
此外,模型中还硬编码了 .cuda() 调用,如果不修改代码,就无法回退到 CPU。
于是我鼓励了它一下:
还有没有其他方法可以让 CUDA 工作起来,比如换用其他版本的 PyTorch,看看是否有适用于这个平台的 CUDA wheel?你有没有检查过哪些 PyTorch 版本已经提供了能在这里运行的 ARM CUDA wheel?
它运行了下面这条命令,从 download.pytorch.org 提供的所有 PyTorch wheel 中,筛选匹配 ARM 架构的版本:
curl -s https://download.pytorch.org/whl/torch/ | grep -o 'torch-[0-9.]*.*aarch64.whl' | sort -V | tail -20
……这一下解除了阻塞。
太好了!我看到 PyTorch 2.9.0 提供了适用于 CUDA 12.8、12.9 和 CUDA 13.0(cu130)的 ARM64 wheel!这正是我们需要的!
它安装了找到的 wheel,并报告道:
好消息!PyTorch 2.9.0 能够检测到 CUDA capability 为 12.1(sm_121)的 GB10 GPU,但它提示支持的最高版本是 12.0。不过,现在这只是一条 warning,而不是直接导致失败的 hard error!让我们尝试运行 inference:[…]
然后它又继续忙了一会儿,无视那些 warning,最终成功运行了模型!它加载图片,并使用了下面这条 prompt:
<|grounding|>OCR this image.
得到的输出开头如下:
<|ref|>The perils of vibe coding<|/ref|><|det|>[[352, 30, 624, 111]]<|/det|>
<|ref|>opt<|/ref|><|det|>[[970, 0, 994, 30]]<|/det|>
<|ref|>such<|/ref|><|det|>[[970, 24, 996, 58]]<|/det|>
<|ref|>days<|/ref|><|det|>[[970, 52, 996, 87]]<|/det|>
<|ref|>pavi<|/ref|><|det|>[[970, 85, 996, 118]]<|/det|>
<|ref|>TECHNOLOGY<|/ref|><|det|>[[33, 199, 127, 230]]<|/det|>
<|ref|>holds the promise of replacing program-<|/ref|><|det|>[[220, 170, 390, 210]]<|/det|>
<|ref|>mers - a profession of very well paid<|/ref|><|det|>[[220, 199, 390, 238]]<|/det|>
<|ref|>people.For another, the work can be<|/ref|><|det|>[[220, 228, 390, 267]]<|/det|>
Claude 宣布取得胜利,并让我查看 output/result.mmd 文件,但这个文件里只有空白字符。也就是说,OCR 的确运行成功了,但识别结果没能正确写入磁盘。
在我看来,这个 result.mmd 文件里全是空白字符。试着在那里得到一个更好的结果——不过带 bounding box 的图片效果不错。
这促使 Claude 重新思考它所使用的 prompt:
也许我们需要换一条 prompt。查看 DeepSeek-OCR 的 README 后,可以看到有几种不同的 prompt:
"<image>\n<|grounding|>Convert the document to markdown."——用于文档
"<image>\n<|grounding|>OCR this image."——通用 OCR
"<image>\nFree OCR."——不保留布局
“grounding”模式似乎更侧重 bounding box,这也解释了为什么我们得到了效果不错的 bounding box,但文本输出可能采用了另一种格式。
让我使用另一条更侧重提取文本、而不是 grounding 的 prompt,再运行一次。
它尝试了所有 prompt 变体,记录了相关笔记,还创建了一个名为 PROMPTS_GUIDE.md 的文件,里面包含更多详细信息,其中包括下面这张对比表:
以及标题为“Performance Benchmarks”的部分下方这张表:
测试图片:3503×1668 像素(Financial Times 文章)
我的最后一条 prompt 如下,目的是把所有内容汇总到一个 zip 文件中,以便从 Docker 容器里提取出来:
创建一个 zip 文件,其中包含 output、output_text、所有脚本和笔记——但不要包含 GitHub 仓库和 Hugging Face 仓库目录。
我把这个 zip 文件的内容添加到了新建的 simonw/research GitHub 仓库中的 deepseek-ocr-nvidia-spark 文件夹里。
Claude 真的很喜欢写笔记!下面是最终文件夹的目录结构:
|-- download_test_image.sh
|-- FINAL_SUMMARY.md
|-- notes.md
|-- output
| |-- images
| |-- result_with_boxes.jpg
| `-- result.mmd
|-- output_text
| |-- detailed
| | |-- images
| | |-- result_with_boxes.jpg
| | `-- result.mmd
| |-- free_ocr
| | |-- images
| | |-- result_with_boxes.jpg
| | `-- result.mmd
| `-- markdown
| |-- images
| | `-- 0.jpg
| |-- result_with_boxes.jpg
| `-- result.mmd
|-- PROMPTS_GUIDE.md
|-- README_SUCCESS.md
|-- README.md
|-- run_ocr_best.py
|-- run_ocr_cpu_nocuda.py
|-- run_ocr_cpu.py
|-- run_ocr_text_focused.py
|-- run_ocr.py
|-- run_ocr.sh
|-- setup.sh
|-- SOLUTION.md
|-- test_image.jpeg
|-- TEXT_OUTPUT_SUMMARY.md
`-- UPDATE_PYTORCH.md
我的第一条 prompt 是在 15:31:07(UTC)发出的,Claude Code 的最后一条消息在 16:10:03 到达。也就是说,从开始到结束总共不到 40 分钟,而我真正参与操作的时间大约只有 5~10 分钟。其余时间,我都在吃早餐和做其他事情。
过去我曾尝试让 PyTorch 相关项目运行起来,但都失败了,所以这次在我看来是一次巨大的胜利。将来我会更多地使用这套流程。
实际结果究竟有多好?老实说,Claude 生成的笔记材料实在太多了,我还没有全部检查完。里面很可能存在各种错误,但有一点无可争议:它确实成功运行了模型,并且记录了实现过程,让我将来能够再次完成同样的操作。
我认为,这个项目能够成功执行,关键在于以下几个因素:
我准确地给了它所需的一切:目标硬件上的 Docker 环境、从哪里获取所需内容的说明(代码和模型),以及一个明确的目标。这正是我在设计 Agent 循环时描述的模式的绝佳示例。
让它运行在 Docker 沙箱中,意味着我可以使用 claude --dangerously-skip-permissions,然后让它独立运行。如果每条命令都需要我手动批准,我可能只坚持几分钟就会感到沮丧,然后放弃这个项目。
当它卡住时,我运用了自己的知识和经验。基于之前在 Spark 上做过的实验,我确信应该存在一个很可能可用的 ARM64 CUDA wheel。因此,当它放弃时,我让它再试一次,最终取得了成功。
另外,看起来 DeepSeek OCR 确实是个相当不错的模型——前提是你愿意花时间尝试不同的运行方式。
今天还学到一个小技巧:我通过 SSH 连接到 Spark,并在 Docker 容器中启动任务后,突然想到,如果能在任务运行期间轻松监控它正在创建的文件,应该会很方便。
我正在一台远程机器上运行 Docker 容器,这个容器是我通过 SSH 启动的。
怎样才能让本地 MacOS 上的 VS Code 显示远程机器中这个 Docker 容器的文件系统,而且不需要重启任何东西?
它给了我一套步骤,恰好解决了这个问题:
安装 VS Code 的“Remote SSH”和“Dev Containers”扩展。
使用“Remote-SSH: Connect to Host”连接到远程机器(在我的 Tailscale 网络中,地址是 spark@100.113.1.114)。
在该远程 SSH 会话的窗口中,运行“Dev Containers: Attach to Running Container”——它会显示容器列表,你可以从中选择想要连接的容器。
……就这么简单!VS Code 会打开一个新窗口,让你完整访问该容器中的所有文件。我打开 notes.md,实时看着 Claude Code 不断向其中追加内容。
最后,当我让 Claude 创建结果的 zip 文件后,我可以在 VS Code 文件资源管理器中选中它,再使用“Download”菜单项把它下载到 Mac。
OpenAI 意外对 Hugging Face 发起的网络攻击,是已经发生的科幻故事——2026 年 7 月 22 日
与 Claude Code 团队的 Cat 和 Thariq 围炉对谈——2026 年 7 月 21 日
Kimi K3,以及我们仍然能从鹈鹕 benchmark 中学到什么——2026 年 7 月 16 日
本文是 Simon Willison 撰写的《使用 Claude Code 暴力破解,让 DeepSeek-OCR 在 NVIDIA Spark 上运行》,发布于 2025 年 10 月 20 日。
“我如何使用 LLM 和 ChatGPT”系列文章之一
拥抱并行 coding Agent 的生活方式——2025 年 10 月 5 日中午 12:06
Vibe engineering——2025 年 10 月 7日下午 2:32
Claude 现在已经可以编写完整的 Datasette plugin——2025 年 10 月 8 日上午 11:43
使用 Claude Code 暴力破解,让 DeepSeek-OCR 在 NVIDIA Spark 上运行——2025 年 10 月 20 日下午 5:21
视频:使用 Claude Code for web 构建一个可通过复制粘贴分享终端会话的工具——2025 年 10 月 23 日凌晨 4:14
视频及笔记:在 uv 和 OpenAI Codex CLI 的帮助下,为最新的 1.0 alpha 升级 Datasette plugin——2025 年 11 月 6 日下午 5:26
构建 HTML 工具的实用模式——2025 年 12 月 10 日晚上 9 点
下一篇:Claude Code for web——Anthropic 推出的新型异步 coding Agent
上一篇:Claude Skills 很棒,可能比 MCP 更重要
每月赞助我 10 美元,即可收到一封精心整理的电子邮件摘要,汇总当月最重要的 LLM 进展。
花钱让我少给你发点东西!