简介:这份面向中小制造企业IT负责人与ERP实施人员的实战指南,围绕DeepSeek私有化部署与ERP系统智能化改造展开,适合具备一定运维基础、希望把大模型能力落到生产业务中的技术团队。文档以“三步走”为主线:先完成服务器选型、操作系统与数据库等环境搭建及数据清洗准备,再推进模型下载部署、数据格式转换与微调评估,最后通过API接口、插件或深度代码集成方式接入既有ERP,并覆盖测试计划、性能优化与上线监控,末尾附有某中小制造企业的落地案例与经验启示。包内共1个PDF文件,约1.79MB,22页内容完整、目录与图表显示正常,按需求分析、部署实施、集成测试到案例复盘的脉络编排,便于按章节检索与对照实操。目前已有95人学习下载,可为制定私有化落地方案、评估集成路径提供可参考的流程框架与排错思路。
1. 中小制造企业为什么要在厂内跑 DeepSeek:ERP 智能化的三个硬约束
上个月去一家做注塑件的厂,计划员要在 ERP 里查一张工单,得先翻三个界面找到模具编号,再去共享盘里翻那份 PDF 工艺卡。他真正想问的不是「系统能不能查」,而是「能不能直接问它一句」。这就是中小制造企业做 DeepSeek 私有化部署最真实的起点:ERP 系统里躺着的是工单、BOM、库存这些结构化字段,而工艺卡、图纸说明、质量异常记录,绝大多数是非结构化的散文件,两者之间隔着一堵墙。
把 DeepSeek 放在厂内,解决的是三件事。数据不出厂,客户订单、报价单、材料成本不进外部接口;响应可控,车间网络时好时坏,本地推理不依赖外网;模型版本可控,今天问出来的答案和三个月后是同一套逻辑,不会因为对方升级模型而突然变样。但中小厂通常只有一到两个运维,没有算法团队,所以这件事必须拆成三步:先把模型在本地的机器上跑起来,再用一层接口把它和 ERP 的单据接上,最后才谈把输出嵌进报价、排产、质检这些真实流程。前两步是工程活,第三步是脏活。
2. 第一步:DeepSeek 本地化部署的硬件账与容器化落地命令
2.1 先算显存:参数量、量化等级与并发数的三角关系
买卡之前先算账,否则很容易出现「模型能加载但一并发就卡死」的情况。显存占用由三块组成:权重、KV 缓存、CUDA 上下文与碎片。权重随量化等级变化,KV 缓存随上下文长度和并发路数线性增长,上下文和碎片通常要额外留 20% 余量。
| 参数量级 | 常用量化 | 权重占用 | 4K 上下文 KV 缓存 | 单卡可承载并发 | 典型用途 |
|---|---|---|---|---|---|
| 7B | Q4_K_M | 约 5 GB | 约 1 GB / 路 | 8 ~ 16 | 字段补全、单据摘要 |
| 14B | Q4_K_M | 约 9 GB | 约 2 GB / 路 | 4 ~ 8 | 工艺问答、报表解释 |
| 32B | Q4_K_M | 约 20 GB | 约 4 GB / 路 | 2 ~ 4 | 复杂归因、报价推理 |
| 32B | FP16 | 约 64 GB | 约 8 GB / 路 | 2 ~ 3 | 不建议作为起步配置 |
表里的数字是量级估算,不是精确值,实际要以自己机器上的压测为准。中小厂的常见做法是单卡 24 GB 起步,跑 14B 的 Q4 量化,留给 ERP 侧 4 ~ 6 路并发;如果工艺文件长、要喂整份 PDF 做检索增强,再考虑 48 GB 单卡或双卡切分。CPU 推理只建议用在测试环境,14B 在纯 CPU 上单请求就要十几秒,计划员点一次查询等这么久,工具就废了。
2.2 用 Ollama 拉模型并跑通第一条推理命令
本地部署 DeepSeek 最省事的路径是 Ollama,它把权重下载、量化加载、并发调度都包掉了,适合没有专职算法团队的场景。
# 让同网段的 ERP 应用服务器能访问推理端口 export OLLAMA_HOST=0.0.0.0:11434 # 并行请求数,按显存反推,24G 卡跑 14B Q4 建议 4 export OLLAMA_NUM_PARALLEL=4 # 只驻留一个模型,避免多个模型互相换入换出把显存打满 export OLLAMA_MAX_LOADED_MODELS=1 # KV 缓存量化到 8 位,长上下文能省下可观显存 export OLLAMA_KV_CACHE_TYPE=q8_0 export OLLAMA_FLASH_ATTENTION=1 ollama serve & ollama pull deepseek-r1:14b # 验证:看单次问答耗时和显存峰值 time ollama run deepseek-r1:14b "用一句话说明注塑件缩水的主要原因" nvidia-smi --query-gpu=memory.used --format=csv -l 1这段命令的逻辑是:前四个环境变量决定了服务的对外可见性和资源上限,必须在ollama serve之前导出,否则不生效。OLLAMA_NUM_PARALLEL是最容易设错的一个,调大能让多人同时问,但每一路都要占 KV 缓存,设成 16 在 24 GB 卡上大概率触发反复重载,表现为首字延迟忽高忽低。OLLAMA_KV_CACHE_TYPE=q8_0会带来极轻微的精度损失,对查工单、解释报表这类任务完全可接受。最后那行nvidia-smi的循环采样是用来抓峰值的,单看一次快照往往看不出问题。
2.3 用 Docker Compose 把推理服务固定成可重启单元
裸ollama serve一关终端就没了,生产上要落到容器里,并且只对本机开放端口,外部统一走网关。
services: ollama: image: ollama/ollama:latest container_name: erp-llm restart: unless-stopped ports: - "127.0.0.1:11434:11434" # 只绑本机,外部访问统一走入口网关 volumes: - /data/ollama:/root/.ollama # 权重放数据盘,别放系统盘 environment: - OLLAMA_NUM_PARALLEL=4 - OLLAMA_MAX_LOADED_MODELS=1 - OLLAMA_KV_CACHE_TYPE=q8_0 deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu]ports里写127.0.0.1:11434而不是11434,是为了避免推理端口直接暴露在厂区网络上,谁都能调。volumes把模型权重放到独立数据盘,权重动辄十几 GB,塞进系统盘后扩容会很麻烦。restart: unless-stopped保证服务器重启后服务自己起来,夜班报工不会因为一次意外重启就断掉。GPU 声明部分要配合宿主机装好 NVIDIA Container Toolkit,否则容器起来也是纯 CPU 模式,速度差十倍以上。
2.4 部署验收:三个必测项
| 验收项 | 命令 | 合格线 |
|---|---|---|
| 服务可达 | curl http://127.0.0.1:11434/api/tags | 返回模型列表,无超时 |
| 单请求延迟 | time ollama run <模型> "问题" | 14B Q4 首字 1 s 内,整段 15 s 内 |
| 并发不崩 | 4 路并发压测 | 无 OOM,尾延迟不超过单路的 3 倍 |
三项都过了再往下走,尤其是并发这一项,很多厂是上线当天才发现三个人同时问就卡住。压测别用假数据,直接拿真实工单号去问,长度和业务分布才对得上。
3. 第二步:用 DeepSeek API 接进 ERP,把单据问答跑通
3.1 ERP 侧的数据出口:中间表比直连生产库更合适
第一个决定是模型怎么拿数据。直连 ERP 生产库看着最直接,但制造企业的库往往背着 MES、条码、财务多个系统,一个SELECT没写好就可能锁表。更常见的做法是建中间表,由定时任务或触发器把当天需要问的数据同步过来。
-- 单据索引中间表,只放问答需要的字段,不放金额明细 CREATE TABLE ai_doc_index ( id BIGINT PRIMARY KEY AUTO_INCREMENT, doc_type VARCHAR(32) NOT NULL COMMENT '工单/采购单/报工单', doc_no VARCHAR(64) NOT NULL, material_code VARCHAR(64), content TEXT NOT NULL COMMENT '拼好的可读文本', updated_at DATETIME NOT NULL, UNIQUE KEY uk_type_no (doc_type, doc_no), KEY idx_material (material_code) ) COMMENT='供模型检索的单据索引'; -- 同步语句示例:把工单头拼成一段自然语言,减少模型理解成本 INSERT INTO ai_doc_index (doc_type, doc_no, material_code, content, updated_at) SELECT '工单', w.order_no, w.material_code, CONCAT('工单号', w.order_no, ',物料', w.material_code, ',数量', w.qty, ',计划开工', DATE(w.plan_start), ',状态', w.status, ',对应模具', IFNULL(m.mould_no,'未指定')), NOW() FROM erp_work_order w LEFT JOIN erp_mould m ON m.material_code = w.material_code WHERE w.updated_at >= DATE_SUB(NOW(), INTERVAL 1 DAY);这里的关键是把结构化行拼成一段自然语言再落库,而不是让模型去读字段名。理由很实际:模型对plan_start_dt这种字段名的理解远不如「计划开工」四个字。content字段控制在 500 字以内,太长会挤压检索的召回精度。中间表的同步频率按业务定,报工单可以 5 分钟一次,采购单一天一次就够。
3.2 DeepSeek API 怎么调用:用 FastAPI 包一层统一入口
ERP 那边是 Java 或 .NET 写的,直接调 Ollama 的/api/generate也行,但模型一换、参数一改,所有调用点都要动。中间包一层 HTTP 服务,把模型细节收敛到一个地方。
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import httpx app = FastAPI() LLM = "http://127.0.0.1:11434/api/chat" MODEL = "deepseek-r1:14b" class Ask(BaseModel): question: str context: str = "" # ERP 中间表检索出来的业务上下文 max_tokens: int = 512 @app.post("/erp/ask") async def ask(req: Ask): prompt = f"以下是工厂 ERP 系统里的单据信息:\n{req.context}\n\n问题:{req.question}" payload = { "model": MODEL, "messages": [{"role": "user", "content": prompt}], "stream": False, "options": {"temperature": 0.1, "num_ctx": 4096, "num_predict": req.max_tokens}, } async with httpx.AsyncClient(timeout=60) as c: r = await c.post(LLM, json=payload) if r.status_code != 200: raise HTTPException(502, "推理服务不可用") return {"answer": r.json()["message"]["content"]}这段代码做了三件事:把业务上下文和用户问题拼成一条完整 prompt、把推理参数固定在服务端、把上游异常翻译成 ERP 能识别的错误码。temperature压到 0.1 是因为 ERP 场景要的是可复现,同一个工单问两次给两个交期,计划员立刻就不信了。num_ctx设 4096 是平衡显存和上下文长度,超过这个值 KV 缓存会明显吃紧。timeout给 60 秒,是因为 14B 模型生成 512 token 在并发下确实可能到这个量级,超时设太短会把正常请求掐掉。
3.3 工单与 BOM 的上下文拼装
检索环节不要一上来就上向量库。工单号、物料编码这类精确标识,用 SQL 的LIKE或全文索引命中率比向量检索高得多,也便宜。
def build_context(doc_no: str) -> str: rows = db.query( "SELECT content FROM ai_doc_index WHERE doc_no = %s LIMIT 5", doc_no ) if not rows: return "" # 空上下文交给上层决定是否追问 return "\n".join(r["content"] for r in rows)逻辑很直白:先按单据号精确定位,再取最多 5 条作为上下文。取 5 条而不是全部,是因为同一工单的多次报工记录堆进去会互相干扰。返回空字符串时,接口层应该直接回「没查到这张单,请确认单号」,而不是让模型硬编一个答案出来。
3.4 前端入口从哪进:ERP 内嵌页、企业微信与 vscode 调试口
入口决定使用率。车间和管理层的入口不一样:计划、采购、财务这类坐办公室的角色,直接在 ERP 菜单里挂一个内嵌页面,登录态复用现有账号体系,不用二次登录;车间报工、质检这类走动场景,企业微信接入 DeepSeek 更合适,扫工单二维码后直接在企业微信里问,省掉打开电脑这一步。开发阶段我自己会先用 vscode 接入 DeepSeek 的插件做联调,改 prompt 不用重新部署前端,验证完再往 ERP 里搬。
4. 第三步:让模型输出真正落进 ERP 业务流程
4.1 三类高频场景的 Prompt 模板与输出约束
模板的价值在于把「模型自由发挥」变成「模型填空」。ERP 业务流程要的是能对得上字段的结果,不是一段漂亮的散文。
| 场景 | 输入 | 输出约束 |
|---|---|---|
| 工单进度问答 | 工单头 + 最近 5 条报工 | 只答数量、状态、预计完工,不解释原因 |
| 工艺参数查询 | 物料号 + 工艺卡切片 | 给出参数值和来源文件页码,找不到就说找不到 |
| 质量异常归因 | 不良记录 + 同批次报工 | 列出最多 3 个可能原因,按可能性排序,标注依据 |
输出约束写在 system 提示里,而不是靠用户每次叮嘱。例如工艺查询场景要明确要求「必须标注来源页码」,否则模型会给出一个看起来合理但无从追溯的参数,工艺员照着改了参数,出了批量报废没人担得起。
4.2 工艺文件 RAG:切片粒度与混合检索
工艺卡、作业指导书这类 PDF 是非结构化数据的主体,走检索增强是标准做法。切片粒度是个容易踩的坑:按整页切,一份含三张表格的工艺卡会被混成一个语义块;按固定 200 字切,参数和它的单位、条件会被切开。
def chunk_by_section(text: str, max_len: int = 400): """按工艺卡的小节标题切,超长再按句号二次切分""" blocks, buf = [], "" for line in text.splitlines(): if line.strip().startswith(("工序", "参数", "注意")) and buf: blocks.append(buf.strip()); buf = "" buf += line + "\n" if buf.strip(): blocks.append(buf.strip()) # 二次切分,保证参数与条件落在同一块里 out = [] for b in blocks: out.extend([b[i:i+max_len] for i in range(0, len(b), max_len)] or [b]) return [x for x in out if len(x) > 20]按小节标题切是为了保住「参数 + 条件」的完整性,max_len=400是经验值,中文工艺卡一个完整工序说明大多在这个长度内。过滤掉 20 字以下的块,可以去掉页眉页脚这类噪声。检索时用关键词加向量的混合方式,关键词负责命中物料号、模具号这类精确串,向量负责命中「缩水」「飞边」这类口语化表达,两边各取前 5 再合并去重。
4.3 权限、审计与「答错了谁负责」
这是私有化部署相对公有云的一个实在优势,也是必须做的一环。
CREATE TABLE ai_audit_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(64) NOT NULL, question TEXT NOT NULL, context MEDIUMTEXT, answer TEXT, model VARCHAR(64), cost_ms INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_time (user_id, created_at) ) COMMENT='模型问答审计表';审计表要记下喂进去的上下文,不然出了问题无法复现。权限上做两件事:按用户所属部门过滤中间表数据,采购看不到成本明细;按角色限制问法,普通报工只能问自己工位的单据。审计日志保留 6 个月,工艺和报价相关的问答建议单独归档,出了问题能倒查到具体是哪次对话给出的参数。
4.4 四个必调参数与它们的实际取值
| 参数 | 建议值 | 说明 |
|---|---|---|
| temperature | 0.1 ~ 0.3 | 查询类取低,归因类可到 0.3 |
| top_p | 0.8 | 与低温度配合,减少胡编 |
| num_ctx | 4096 ~ 8192 | 有长工艺卡时上调,同时看显存 |
| num_predict | 256 ~ 512 | 上限设死,防止模型长篇输出拖慢响应 |
参数不是一次性调好的。上线第一周每天看审计表里的高频问题和耗时分布,把num_predict收到实际输出长度的 1.5 倍左右,超过这个值的请求基本都是模型在绕圈子。
5. 显存打满、并发抖动、字段对不上:上线后的排错与压测技巧
5.1 三类高频故障的定位顺序
显存打满的表现是服务突然无响应、日志里出现加载失败。先看nvidia-smi的显存曲线是不是锯齿状,锯齿说明模型在被反复换入换出,把OLLAMA_MAX_LOADED_MODELS收到 1,或者把OLLAMA_NUM_PARALLEL减半。并发抖动表现为平均延迟正常但尾延迟很高,这通常是 KV 缓存不够,把num_ctx从 8192 降到 4096 往往立竿见影。字段对不上是最隐蔽的一类,模型回答里出现「物料号 A001」而 ERP 里是「A001 」带尾空格,问题在拼 context 时没做清洗,TRIM()一下就能解决大半。
5.2 用真实问题做回归压测
压测脚本别用随机字符串,把审计表里最常被问的 50 个问题导出来,按真实并发跑。
# 从审计表导出高频问题,逐行作为请求体 mysql -N -e "SELECT question FROM ai_audit_log GROUP BY question ORDER BY COUNT(*) DESC LIMIT 50" \ > /tmp/questions.txt # 4 路并发回放,记录每条的耗时 cat /tmp/questions.txt | xargs -P 4 -I{} sh -c \ 'curl -s -o /dev/null -w "%{time_total}\n" -X POST http://127.0.0.1:8000/erp/ask \ -H "Content-Type: application/json" -d "{\"question\":\"{}\"}"' \ | sort -n | awk '{a[NR]=$1} END {print "P50="a[int(NR*0.5)]" P95="a[int(NR*0.95)]" MAX="a[NR]}'xargs -P 4控制并发数与OLLAMA_NUM_PARALLEL对齐,sort -n加awk是为了从原始耗时里直接算出 P50、P95 而不是只看平均值。我的经验是 P95 控制在 P50 的 2.5 倍以内算健康,超过就说明并发路数设高了。
5.3 每周一次的答案回放
模型版本不变,但数据在变。每周把这 50 个问题跑一遍,把这次的答案和上周的存档做 diff,出现差异的逐条人工看一遍,是数据更新导致的正常变化,还是模型开始编了。这一步做起来枯燥,但它是中小制造企业把 DeepSeek 私有化部署真正用住的关键,比任何参数调优都管用。
本文还有配套的精品资源,点击获取