🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

本地生图 LisonEvf/dsh-qwen-image

DeepSeek Harness
LisonEvf34 天前有提交已提供中文文档扫描:含敏感能力

在对话里直接生图改图,模型跑在自己显卡上

30.2/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/5 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add LisonEvf/dsh-qwen-image
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · vision
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 34 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/11/3) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包@lisonevf/dsh-qwen-image(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/6 14:18:37

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 150 处,展示前 116 条)
  • 发起网络请求
    lib/index.cjs:371
    const res = await fetch(this.url(path), {
  • 发起网络请求
    lib/index.cjs:397
    return this.request("/health", { timeoutMs: 15e3 });
  • 发起网络请求
    lib/index.cjs:400
    return this.request("/capabilities", { timeoutMs: 15e3 });
  • 发起网络请求
    lib/index.cjs:403
    return this.request(`/logs?count=${count}`, { timeoutMs: 15e3 });
  • 发起网络请求
    lib/index.cjs:406
    return this.request("/load", {
  • 发起网络请求
    lib/index.cjs:413
    return this.request("/unload", {
  • 发起网络请求
    lib/client.js:7
    "use strict";var Qe=Object.create;var Q=Object.defineProperty;var Ve=Object.getOwnPropertyDescriptor;var We=Object.getOwnPropertyNames;var Je=Object.g
  • 写入剪贴板
    lib/client.js:7
    "use strict";var Qe=Object.create;var Q=Object.defineProperty;var Ve=Object.getOwnPropertyDescriptor;var We=Object.getOwnPropertyNames;var Je=Object.g
  • 写入剪贴板
    lib/client.js:8
    `)}var l=ce(require("react"),1);var n=l.createElement,st="linear-gradient(45deg,#8884 25%,transparent 25%),linear-gradient(-45deg,#8884 25%,transparen
  • 读取本地文件
    scripts/build.mjs:21
    if (existsSync(c)) return { cmd: c, args: [] }
  • 读取本地文件
    scripts/build.mjs:34
    if (existsSync(p)) {
  • 删除本地文件
    scripts/build.mjs:36
    rmSync(p)
  • 执行外部命令来自配置/变量
    scripts/build.mjs:43
    const r = spawnSync(cmd, [...args], {
  • 读取本地文件
    scripts/build.mjs:60
    if (!existsSync(join(ROOT, 'lib', 'index.cjs'))) {
  • 读取本地文件
    installer/lib/detect.mjs:233
    entries = readdirSync(root, { withFileTypes: true })
  • 读取本地文件
    installer/lib/detect.mjs:315
    entries = readdirSync(dir, { withFileTypes: true })
  • 写入本地文件
    installer/lib/detect.mjs:431
    writeFileSync(probeFile, 'ok')
  • 删除本地文件
    installer/lib/detect.mjs:432
    rmSync(probeFile, { force: true })
  • 读取本地文件
    installer/lib/pluginstep.mjs:35
    const manifest = JSON.parse(readFileSync(pkg, 'utf8'))
  • 读取本地文件
    installer/lib/pluginstep.mjs:51
    return JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf8'))
  • 读取本地文件
    installer/lib/pluginstep.mjs:150
    const original = existed ? readFileSync(patchPath, 'utf8') : '# 本 profile 的 patch 层:顶层是一个 YAML 数组\n[]\n'
  • 写入本地文件
    installer/lib/pluginstep.mjs:155
    copyFileSync(patchPath, backup)
  • 读取本地文件
    installer/lib/pystep.mjs:153
    for (const f of readdirSync(srcDir)) {
  • 读取本地文件
    installer/lib/pystep.mjs:156
    if (isFile(from)) writeFileSync(to, readFileSync(from))
  • 写入本地文件
    installer/lib/pystep.mjs:156
    if (isFile(from)) writeFileSync(to, readFileSync(from))
  • 读取本地文件
    installer/lib/pystep.mjs:159
    writeFileSync(localBin, readFileSync(found), { mode: 0o755 })
  • 写入本地文件
    installer/lib/pystep.mjs:159
    writeFileSync(localBin, readFileSync(found), { mode: 0o755 })
  • 删除本地文件递归
    installer/lib/pystep.mjs:272
    rmSync(dir, { recursive: true, force: true })
  • 写入本地文件
    installer/lib/pystep.mjs:329
    writeFileSync(pth, donorSitePackages + '\n')
  • 写入本地文件
    installer/lib/ui.mjs:34
    mkdirSync(dirname(logPath), { recursive: true })
  • 写入本地文件
    installer/lib/ui.mjs:35
    appendFileSync(logPath, `\n===== dsh-qwen-image 安装器 ${new Date().toISOString()} =====\n`, 'utf8')
  • 写入本地文件
    installer/lib/ui.mjs:44
    appendFileSync(logPath, text, 'utf8')
  • 写入本地文件
    installer/lib/ui.mjs:154
    mkdirSync(dir, { recursive: true })
  • 读取本地文件
    installer/lib/ui.mjs:155
    return existsSync(dir)
  • 读取本地文件
    installer/lib/util.mjs:51
    return existsSync(p)
  • 读取本地文件
    installer/lib/util.mjs:59
    return statSync(p).isDirectory()
  • 读取本地文件
    installer/lib/util.mjs:67
    return statSync(p).isFile()
  • 读取本地文件
    installer/lib/util.mjs:87
    st = lstatSync(p)
  • 写入本地文件
    installer/lib/util.mjs:95
    if (!exists(p)) mkdirSync(p, { recursive: true })
  • 读取本地文件
    installer/lib/util.mjs:101
    return JSON.parse(readFileSync(p, 'utf8'))
  • 写入本地文件
    installer/lib/util.mjs:110
    writeFileSync(tmp, text, 'utf8')
  • 删除本地文件
    installer/lib/util.mjs:112
    rmSync(p, { force: true })
  • 写入本地文件
    installer/lib/util.mjs:116
    renameSync(tmp, p)
  • 读取本地文件
    installer/lib/util.mjs:128
    entries = readdirSync(cur, { withFileTypes: true })
  • 执行外部命令来自配置/变量
    installer/lib/util.mjs:211
    const r = spawnSync(spec.cmd, spec.args, {
  • 执行外部命令来自配置/变量
    installer/lib/util.mjs:250
    const child = spawn(spec.cmd, spec.args, {
  • 发起网络请求
    installer/lib/util.mjs:303
    req = mod.request(url, { method, timeout: timeoutMs, headers: { 'user-agent': 'dsh-qwen-image-installer' } }, (res) => {
  • 删除本地文件
    installer/lib/util.mjs:338
    rmSync(part, { force: true })
  • 写入本地文件
    installer/lib/util.mjs:365
    const out = createWriteStream(part)
  • 删除本地文件
    installer/lib/util.mjs:376
    rmSync(destPath, { force: true })
  • 写入本地文件
    installer/lib/util.mjs:380
    renameSync(part, destPath)
  • 写入本地文件
    scripts/build-client.mjs:74
    mkdirSync(join(ROOT, 'lib'), { recursive: true })
  • 读取本地文件
    scripts/build-client.mjs:81
    const fresh = readFileSync(tmpOut, 'utf8')
  • 删除本地文件
    scripts/build-client.mjs:83
    unlinkSync(tmpOut)
  • 读取本地文件
    scripts/build-client.mjs:89
    committed = readFileSync(OUTPUT, 'utf8')
  • 读取本地文件
    scripts/check-host-deps.mjs:77
    const raw = readFileSync(BUNDLE, 'utf8')
  • 读取本地文件
    scripts/check-profile-resolution.cjs:27
    if (!existsSync(PROFILE)) {
  • 读取本地文件
    scripts/check-profile-resolution.cjs:56
    check(`解析 ${PKG}(host 半)`, existsSync(main) && statSync(main).size > 1000, `${main} (${existsSync(main) ? statSync(main).size : 0} B)`)
  • 读取本地文件
    scripts/check-profile-resolution.cjs:56
    check(`解析 ${PKG}(host 半)`, existsSync(main) && statSync(main).size > 1000, `${main} (${existsSync(main) ? statSync(main).size : 0} B)`)
  • 读取本地文件
    scripts/check-profile-resolution.cjs:71
    check(`解析 ${PKG}/client(client 半)`, existsSync(client) && statSync(client).size > 1000, `${client} (${existsSync(client) ? statSync(client).size : 0}
  • 读取本地文件
    scripts/check-profile-resolution.cjs:71
    check(`解析 ${PKG}/client(client 半)`, existsSync(client) && statSync(client).size > 1000, `${client} (${existsSync(client) ? statSync(client).size : 0}
  • 读取本地文件
    scripts/check-profile-resolution.cjs:91
    const yml = readFileSync(patchPath, 'utf8')
  • 读取本地文件
    scripts/check-release.mjs:47
    return existsSync(p) ? readFileSync(p, 'utf8') : null
  • 读取本地文件
    scripts/check-release.mjs:47
    return existsSync(p) ? readFileSync(p, 'utf8') : null
  • 读取本地文件
    scripts/check-release.mjs:52
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
  • 执行外部命令固定命令
    scripts/check-release.mjs:63
    return { ok: true, out: execFileSync('git', args, { cwd: ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }) }
  • 读取本地文件
    scripts/check-release.mjs:104
    check(`${rel}(${why})`, existsSync(join(ROOT, rel)))
  • 读取本地文件
    scripts/check-release.mjs:122
    check('.gitattributes 存在(钉死行尾,否则 install.sh 在 Windows 上会被转成 CRLF)', existsSync(join(ROOT, '.gitattributes')))
  • 读取本地文件
    scripts/check-release.mjs:130
    if (existsSync(join(ROOT, '.git'))) {
  • 执行外部命令来自配置/变量
    scripts/check-release.mjs:275
    execFileSync(process.execPath, [script], { cwd: ROOT, stdio: 'ignore' })
  • 发起网络请求
    scripts/preset-bench.mjs:44
    const res = await fetch(`${base}${path}`, {
  • 执行外部命令固定命令
    scripts/preset-bench.mjs:58
    const child = spawn('python', [
  • 读取本地文件
    scripts/smoke-host-e2e.mjs:39
    if (!existsSync(BUNDLE)) {
  • 写入本地文件
    scripts/smoke-host-e2e.mjs:49
    mkdirSync(OUT_DIR, { recursive: true })
  • 写入本地文件
    scripts/smoke-host-e2e.mjs:137
    writeFileSync(png, bytes)
  • 写入本地文件
    scripts/smoke-host-e2e.mjs:160
    writeFileSync(sidecar, JSON.stringify(meta, null, 2))
  • 读取本地文件
    scripts/smoke-host-e2e.mjs:335
    const s = statSync(t.targetKey)
  • 读取本地文件
    scripts/smoke-host-e2e.mjs:349
    readBytes: async (t) => new Uint8Array(readFileSync(t.targetKey)),
  • 读取本地文件
    scripts/smoke-host-e2e.mjs:350
    readText: async (t) => readFileSync(t.targetKey, 'utf8'),
  • 写入本地文件
    scripts/smoke-host-e2e.mjs:352
    mkdirSync(dirname(t.targetKey), { recursive: true })
  • 写入本地文件
    scripts/smoke-host-e2e.mjs:353
    writeFileSync(t.targetKey, content, 'utf8')
  • 读取本地文件
    scripts/smoke-host-e2e.mjs:577
    check('PNG 真实落盘', existsSync(genValue.files[0]))
  • 读取本地文件
    scripts/smoke-host-e2e.mjs:578
    check('manifest.json 已写', existsSync(join(OUT_DIR, 'manifest.json')))
  • 写入本地文件
    scripts/smoke-host-e2e.mjs:924
    writeFileSync(orphanPng, Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
  • 写入本地文件
    scripts/smoke-installer.mjs:267
    writeFileSync(patchPath, '# 用户自己的 patch\n- id: other-plugin\n config:\n a: 1\n', 'utf8')
  • 读取本地文件
    scripts/smoke-installer.mjs:270
    const afterFirst = readFileSync(patchPath, 'utf8')
  • 读取本地文件
    scripts/smoke-installer.mjs:275
    const afterSecond = readFileSync(patchPath, 'utf8')
  • 写入本地文件
    scripts/smoke-installer.mjs:280
    writeFileSync(emptyPatch, '[]\n', 'utf8')
  • 读取本地文件
    scripts/smoke-installer.mjs:282
    check('空数组 patch 也能正确落地(不会被残留的 [] 干扰)', readFileSync(emptyPatch, 'utf8').indexOf('[]') < 0)
  • 写入本地文件
    scripts/smoke-installer.mjs:285
    writeFileSync(tplPatch, '# Your patch layer for this dsh profile\n# a top-level YAML array\n[]\n', 'utf8')
  • 读取本地文件
    scripts/smoke-installer.mjs:287
    const tplAfter = readFileSync(tplPatch, 'utf8')
  • 写入本地文件
    scripts/smoke-installer.mjs:293
    writeFileSync(broken, '# comments\n[]\n\n' + block, 'utf8')
  • 读取本地文件
    scripts/smoke-installer.mjs:295
    const brokenAfter = readFileSync(broken, 'utf8')
  • 删除本地文件递归
    scripts/smoke-installer.mjs:298
    rmSync(tmp, { recursive: true, force: true })
  • 读取本地文件
    scripts/smoke-integration.mjs:46
    const PYTHON = getArg('python', process.env.DSH_QWEN_PYTHON || (existsSync(VENV_PYTHON) ? VENV_PYTHON : 'python'))
  • 读取本地文件
    scripts/smoke-integration.mjs:70
    if (!existsSync(BUNDLE)) {
  • 执行外部命令来自配置/变量
    scripts/smoke-integration.mjs:93
    spawn(spec) {
  • 执行外部命令来自配置/变量
    scripts/smoke-integration.mjs:95
    const child = spawn(spec.argv[0], spec.argv.slice(1), {
  • 执行外部命令固定命令
    scripts/smoke-integration.mjs:155
    spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true })
  • 读取本地文件
    scripts/smoke-integration.mjs:208
    const s = statSync(t.targetKey)
  • 读取本地文件
    scripts/smoke-integration.mjs:215
    readBytes: async (t) => new Uint8Array(readFileSync(t.targetKey)),
  • 读取本地文件
    scripts/smoke-integration.mjs:216
    readText: async (t) => readFileSync(t.targetKey, 'utf8'),
  • 读取本地文件
    scripts/smoke-integration.mjs:313
    check('PNG 真实落盘', existsSync(file), file)
  • 执行外部命令固定命令
    scripts/smoke-integration.mjs:407
    if (process.platform === 'win32') spawn('taskkill', ['/pid', String(c.pid), '/T', '/F'], { windowsHide: true })
  • 执行外部命令固定命令
    scripts/smoke-worker-offline.mjs:124
    const stdout = execFileSync('python', ['-c', PY], {
  • 发起网络请求
    scripts/smoke-worker.mjs:60
    const res = await fetch(`${base}${path}`, {
  • 执行外部命令固定命令
    scripts/smoke-worker.mjs:88
    const child = spawn(
  • 读取本地文件
    scripts/smoke-worker.mjs:260
    check('PNG 已落盘', existsSync(img.file), img.file)
  • 读取本地文件
    scripts/smoke-worker.mjs:261
    check('sidecar JSON 已落盘', existsSync(img.sidecar), img.sidecar)
  • 读取本地文件
    scripts/smoke-worker.mjs:262
    if (existsSync(img.file)) {
  • 读取本地文件
    scripts/smoke-worker.mjs:263
    const bytes = statSync(img.file).size
  • 读取本地文件
    scripts/smoke-worker.mjs:264
    const head = readFileSync(img.file).subarray(0, 8)
  • 读取本地文件
    scripts/smoke-worker.mjs:270
    if (existsSync(img.sidecar)) {
  • 读取本地文件
    scripts/verify-against-real-dsh.mjs:50
    ].filter((b) => b && existsSync(b))
  • 读取本地文件
    scripts/verify-against-real-dsh.mjs:81
    if (!existsSync(BUNDLE)) {
  • 读取本地文件
    scripts/verify-against-real-dsh.mjs:386
    s.resourceBase?.kind === 'directory' && typeof s.resourceBase.path === 'string' && existsSync(s.resourceBase.path),
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 对话内直接生图改图
  • 模型运行在本地显卡
  • 支持多张参考图

在 AI 会话窗口里直接生图 / 改图 —— dsh 插件,本地跑 Qwen-Image-2.1(原生 2K / 原生透明 PNG / 最多 10 张参考图)。安装器自动适配 CUDA 与已有 ComfyUI/conda 环境

README

由 DeepSeek 最新模型翻译生成
@lisonevf/dsh-qwen-image 在 AI 会话窗口里直接生图 / 改图,模型跑在你自己的显卡上。 给 dsh(DeepSeek Harness)用的对话式生图 / 改图插件,专用于 Qwen-Image-2.1(原生 2K、原生透明 PNG、支持改图)。 - 在对话里说「画一只戴墨镜的柴犬」→ 对话流直接出图,不用切终端、不用另开 WebUI - 「把刚才那张的背景换成黄昏海滩」→ 改图(最多 10 张参考图) - 权重体检 + 下载代办、作品相册、worker 显存控制(image_worker unload) 快速开始 第 0 步:把插件下载到本地 git clone https://github.com/lisonevf/dsh-qwen-image.git 或在仓库页面点 Code → Download ZIP 再解压。下面提到的 install.bat / install.sh 都在这个目录里。 前置:Node.js 18+ 与 dsh(npm install -g @deepseek-ai/dsh)。缺 dsh 时安装器仍会装好环境与权重,只是不注册插件,之后装好 dsh 重跑一次即可。
展开完整文档(README 剩余 1 节)
@lisonevf/dsh-qwen-image

在 AI 会话窗口里直接生图 / 改图,模型跑在你自己的显卡上。

给 dsh(DeepSeek Harness)用的对话式生图 / 改图插件,专用于 Qwen-Image-2.1(原生 2K、原生透明 PNG、支持改图)。

- 在对话里说「画一只戴墨镜的柴犬」→ 对话流直接出图,不用切终端、不用另开 WebUI
- 「把刚才那张的背景换成黄昏海滩」→ 改图(最多 10 张参考图)
- 权重体检 + 下载代办、作品相册、worker 显存控制(image_worker unload)

快速开始

第 0 步:把插件下载到本地

git clone https://github.com/lisonevf/dsh-qwen-image.git

或在仓库页面点 Code → Download ZIP 再解压。下面提到的 install.bat / install.sh 都在这个目录里。

前置:Node.js 18+ 与 dsh(npm install -g @deepseek-ai/dsh)。缺 dsh 时安装器仍会装好环境与权重,只是不注册插件,之后装好 dsh 重跑一次即可。

Windows:双击 install.bat | Linux / macOS:sh install.sh

装完重启 dsh(客户端界面半只在进程启动时加载;用哪个 profile 安装器收尾会打印,通常是 web):

dsh --profile web # 若安装时挑的是别的 profile,把 web 换成它打印的那个

先体检不修改:install.bat --check | 只看要做什么:install.bat --dry-run(Linux / macOS 换成 sh install.sh --check / sh install.sh --dry-run)

出图大概要等多久(P40 实测):draft 768²/12步 ≈ 2.5 分钟、standard 1024²/24步 ≈ 5.0 分钟、native 2048²/40步 ≈ 43 分钟。

普通用户请先读 使用说明.txt;完整文档见 docs/INSTALL.md。

⚠️ 装完后请不要删除或移动本插件目录(插件是链接安装,删掉目录会导致 dsh 启动时加载失败)。

为什么做成插件,而不是自己拉个脚本跑

本地跑 Qwen-Image-2.1 的难点从来不是"调一次 API",而是装和管:

| 问题 | 本插件的做法 |
| 我该装哪个 CUDA 版本的 torch? | 安装器探测驱动,自动在 cu128/cu126/cu124/cu121/cu118 之间选,装不上逐级降级 |
| 我已经有 ComfyUI / conda 环境了 | 复用它的 site-packages,省约 3GB 下载,且不动它本身 |
| 机器上没有 Python | 自动下载 uv 并托管 Python 3.12 |
| 权重 30GB 从哪下、断了下不动 | 权重体检 + 下载代办,走 hf-mirror,失败回落官方 / ModelScope |
| 显存被占了,我想把它还回去 | image_worker unload 一条命令卸载模型 |
| 出过的图去哪了 | 对话内联卡片 + 作品相册 + /raw?id= 直链,可回放 |

它怎么适配不同的机器

| 情况 | 安装器的做法 |
| 已有 torch + diffusers(含 ComfyUI 便携版/conda) | 借它的 site-packages 建 venv,省约 3GB 下载,不动它本身 |
| 完全没有 Python | 自动下载 uv 并托管 Python 3.12 |
| 驱动从 CUDA 11.8 到 12.9 | 自动选 cu128/cu126/cu124/cu121/cu118,装不上逐级降级到 CPU;Linux + AMD 走 ROCm 6.3 |
| 没有 NVIDIA 显卡 | 装 CPU 版并明确警告速度 |
| macOS | 装 PyPI 版 torch,但本插件只认 NVIDIA CUDA,实际按 CPU 推理(1024² 一张要数小时,仅供试跑) |
| 国内网络 | pip 源按实测延迟选镜像;权重走 hf-mirror,失败回落官方/ModelScope |
| 磁盘不够 / 想换盘 | --model-dir,并在下载前就报出空间需求 |

硬件门槛(实测,不是估算)

数据来自本机 P40 / fp16 / 32GB RAM 的实测记录(完整口径见 docs/HARDWARE.md 与 acceptance/LOG.md):

| 项 | 实测 | 说明 |
| 峰值显存(单条件图) | ~17 GB | 1024×1536 |
| 峰值显存(双条件图) | ~21.6 GB | 已逼近 24 GB 上限 |
| worker 卸载后常驻内存 | 20.2 GB | 32GB 机器上建议每 3–4 张图重启一次 worker |
| 冷启动 | 每张约 85 秒 | 每图重付,32GB 内存装不下 30.86GB 权重的必然结果 |

一句话结论:24GB 显存的卡能舒服跑,显存更小请先看 docs/HARDWARE.md 再决定。
我们不在这里给你一个乐观的数字。

开发

npm install
npm run build # host 半 + client 半 → lib/
npm test # 离线冒烟(含安装器设备矩阵 108 项)powershell
npm run test:gpu # 真 GPU worker 协议端到端
npm run test:integration # host × 真 worker 全链路(能真出一张图)
npm run check:release # ★ 发布闸门:一键安装所依赖的硬约束(见下)

改完 src/ 必须重建并提交 lib/

本项目是零依赖一键安装:普通用户下载后直接双击 install.bat,不会跑 npm install。
所以 lib/index.cjs 与 lib/client.js 是随仓库分发的构建产物,必须入库(.gitignore 里是刻意不忽略 lib/ 的)。
改了 src/ 却忘了重建并提交 lib/,用户装到的就是旧代码 —— 发布前用下面这条兜住: powershell
npm run check:release:full # 重建 lib/ 并核对已提交的产物是否过期;另查 gitignore/行尾/占位符/本机路径/权重泄漏

架构与决策记录见 docs/PLAN.md,实测数据见 docs/HARDWARE.md 与 acceptance/LOG.md。

在 AI 会话里对话式生成和编辑图片 —— 在自己的 GPU 上跑 Qwen-Image-2.1。

这是 dsh 的插件。在聊天里说「画一只戴墨镜的柴犬」,图片就直接出现在对话里;说「把上一张的背景换成日落海滩」就能改图(最多 10 张参考图)。原生 2K 输出,原生透明 PNG。

在本地跑 Qwen-Image-2.1,难的地方从来不是单次调用,而是环境搭建。安装程序会探测你的驱动,挑出合适的 CUDA 构建(cu128 → cu118,再降级到 CPU),复用已有的 torch/diffusers 安装(ComfyUI 便携版、conda),省下约 3GB 下载量;你机器上没装 Python,就用 uv 引导安装;30GB 权重走 hf-mirror 镜像。image_worker unload 能把显存还给你。

在 P40 上实测(fp16,32GB 内存):单张图冷启动约 85 秒,单条件图片显存峰值约 17GB,双条件约 21.6GB。显卡小的话,先看 docs/HARDWARE.md 再决定。

许可

- 本插件代码:Apache-2.0(见 LICENSE)。
- Qwen-Image-2.1 权重:采用 Qwen Research License(仅限研究用途,商用需另行授权)。
本仓库不携带、不分发任何权重文件,权重由使用者在安装时自行从 HuggingFace / ModelScope 下载;使用权重与生成内容须同时遵守模型自身的许可条款。
预览分享卡片长什么样 ↓LisonEvf/dsh-qwen-image
分享 · DeepSeek Harness Hub
LisonEvf/dsh-qwen-image

在对话里直接生图改图,模型跑在自己显卡上

这是你的仓库?认领条目

回到顶部