ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

cgminer 3.1.1 Windows:OpenCL老卡验证与调优避坑

cgminer 3.1.1 Windows:OpenCL老卡验证与调优避坑

简介:cgminer 3.1.1 Windows 版是一套面向比特币及其衍生币挖矿的多线程、多矿池挖矿程序,支持 ATI GPU 监控、超频与风扇转速调节,并兼容 FPGA、ASIC 设备,适合需要在 Windows 环境下搭建或调试矿机的进阶用户。压缩包共 43 个文件,约 6.83MB,包含 txt 说明文档、dll 运行库、bit/bin 与 ncd FPGA 配置文件、cl OpenCL 内核,以及 exe 可执行程序、conf 示例配置和 php/java/class/c 形式的 API 示例,覆盖运行、设备适配与接口调用等环节。已有 559 人学习下载。包内提供 cgminer.exe 与 cgminer-nogpu.exe 两种入口,并附 README、API 说明和多种 scrypt 内核,便于读者快速完成本地部署、对照配置参数、排查设备识别与算力异常,也可作为理解矿机软件目录结构和二次开发的参考。

1. 四十兆的老包,为什么还留在硬盘里

先说结论:cgminer 3.1.1 windows 这个包,是 OpenCL 显卡计算时代少数几个"解压就能跑、不用装框架"的现成环境。你在 Windows 上想验证一张老卡到底能不能被 OpenCL 正确调用、内核编译要多久、显存被吃满时驱动是什么反应,从头搭一套工具链要折腾半天,而这个包里 exe、依赖 dll、内核源码、示例配置全齐,双击目录里那个 bat 就能看到实时界面。它适合三类人:手里还留着老卡想榨干最后一滴算力的人、做 OpenCL 负载与调度实验的人、需要复现十几年前软件行为的人。不适合指望它跑新卡的人——3.x 之后内核调度逻辑变过,新卡上去大概率是花屏式报错。下面按解压、配置、监控、排错、验收的顺序讲,中间会翻几次车。

2. 解压之后先别双击:依赖链、内核文件与 --ndevs 自检

绝大多数人拿到这个包的第一反应是双击 cgminer.exe,然后看到一个黑窗口闪一下就没了,于是认为包是坏的。实际上它没坏,只是启动链路里某一环没接上,而 Windows 控制台程序退出得又太快,连报错都来不及看清。这一章先把"谁依赖谁"讲清楚,再用三条命令把问题定位到具体那一层,避免后面调参数时把驱动问题误判成配置问题。

2.1 exe、dll 与 .cl 内核:缺了谁会报什么

cgminer 主体是纯 C 写的控制台程序,在 Windows 上用 MinGW 交叉编译出来,静态部分很少,运行时严重依赖同目录下的几个动态库。它的挖矿后端分两类:一类走网络协议(stratum、getwork),一类走设备驱动(OpenCL、串口矿机)。网络侧依赖 libcurl 和 OpenSSL 的库做 HTTP 与签名,线程侧依赖 pthread 的 Windows 移植版。这些库必须和 exe 放在同一层目录,系统 PATH 里有没有都不算数,因为程序是按相对路径找的。

另一块容易漏的是内核文件。cgminer 把真正跑在显卡上的计算逻辑写成 OpenCL C 源码,扩展名 .cl 文件,放在 exe 同级的一个子目录里,程序启动时才调用驱动现场编译。也就是说,显卡驱动同时扮演了"运行库"和"编译器"两个角色。目录缺失、路径含中文、路径里有空格,都可能让编译阶段直接失败,而失败信息往往只写一行 "Failed to load kernel",看不到具体是哪个文件。

组成职责缺失或异常时的现象
cgminer.exe主循环、设备调度、协议解析无法启动
pthread 运行库线程池与锁启动即报找不到入口点,窗口一闪而过
libcurl / OpenSSL 库矿池 HTTP 通信与签名能进界面但连不上池,日志里是连接失败
zlib 库部分协议数据压缩偶发解析错误,长连接后被断开
.cl 内核目录GPU 计算逻辑源码提示内核加载失败,或设备列表为空
cgminer.conf参数持久化每次启动都要手敲一长串命令行

我第一次拆这个包时就栽在路径上。当时图省事解压到了桌面一个带中文的文件夹,--ndevs能列出显卡,但一加载内核就报错,日志里翻半天只有一句内核编译失败。换到D:\cgm311这种纯英文短路径后,同样的参数直接跑通。这件事说明一个道理:这类老程序的路径处理没有做宽字符适配,任何非 ASCII 字符和空格都是雷。

2.2 OpenCL 的三层对应:驱动、ICD 注册表与 32 位进程

要理解为什么"驱动装了但 cgminer 说找不到设备",得把 Windows 上 OpenCL 的调用链拆成三层。最上面是应用层,也就是 cgminer.exe;中间是 ICD loader,就是系统目录里那个 OpenCL.dll,它本身不含任何实现,只负责按注册表里的厂商列表去加载真正的实现库;最下面是显卡驱动提供的运行时,负责把内核源码编译成机器码并调度到计算单元上。

关键在于,cgminer 3.1.1 的 Windows 版本是 32 位程序。32 位进程只能加载 32 位的实现库,读的也是注册表的 32 位视图,也就是HKLM\SOFTWARE\Wow6432Node\Khronos\OpenCL\Vendors这个分支。现在很多新驱动默认只装 64 位组件,结果是 64 位程序一切正常,32 位程序死活看不到设备。这种情况不是 cgminer 的锅,是运行时没装全。

判断方法很直接:用 reg query 看 32 位视图下有没有厂商条目,有的话再看--ndevs能不能枚举出设备名和显存。如果注册表条目存在但枚举失败,问题在驱动版本与老程序的兼容性;如果条目根本不存在,先把 32 位 OpenCL 运行时补上再谈其他。这一步不做,后面所有参数调优都是空转。

提示:不要用第三方"运行库合集"去补 OpenCL 组件,那些包里的版本往往和显卡驱动不匹配,装上之后 ICD 加载到错误实现,报错反而更隐蔽。老老实实装对应显卡的官方运行时。

2.3 三条命令跑通首次自检

下面这段是每次拿到新机器我都会走一遍的流程,从建目录到枚举设备,三步之内就能判断这个包在当前环境能不能跑。命令用 PowerShell 写,因为要读注册表和批量解压,比 cmd 省事。

# 1) 落到纯英文短路径,避免中文与空格导致内核加载失败 New-Item -ItemType Directory -Force -Path 'D:\cgm311' | Out-Null Expand-Archive -Path '.\cgminer-3.1.1-windows.zip' -DestinationPath 'D:\cgm311' -Force # 2) 查 32 位 OpenCL ICD 是否注册(cgminer 是 32 位进程,必须看 Wow6432Node) reg query "HKLM\SOFTWARE\Wow6432Node\Khronos\OpenCL\Vendors" # 3) 枚举设备,这一步不依赖任何矿池,纯本地自检 Set-Location 'D:\cgm311' .\cgminer.exe --ndevs

第一段只是把包解到一个干净路径,-Force保证重复执行不会因为文件已存在而中断。第二段的输出正常情况下会列出若干条厂商实现的路径,每条对应一个OpenCL实现库;如果返回"系统找不到指定的注册表项",说明 32 位运行时确实没装,先解决这个。第三段是核心,--ndevs会打印设备编号、设备名称等基本信息,不连接网络、不提交任何任务,是最安全的验证方式。

如果--ndevs输出了设备,说明 exe、dll、OpenCL 三层都通了,接下来才轮到挖矿参数。如果它输出了"List of devices:"但下面是空的,通常是驱动把这张卡标记成了不支持计算,或者卡正被别的进程独占。如果直接报平台初始化失败,回到第二段去查注册表。这三步的顺序不要颠倒,先证链路,再证设备,最后才证业务。

设备确认之后,可以用一条最小命令做端到端连通性验证。注意这里的矿池地址是占位符,换成你自己的即可,参数含义逐个说明:

@echo off cd /d %~dp0 cgminer.exe -o stratum+tcp://pool.example.com:3333 ^ -u worker1 -p x ^ -I 13 -g 2 -w 256 --thread-concurrency 8192 ^ --scrypt -T -L cgm.log

-o指定矿池地址,stratum+tcp://前缀告诉程序走 stratum 协议;-u和-p是工作名与口令,很多池的口令可以随便填;-I 13是强度,scrypt 算法下常用的起点值;-g 2表示每张卡起两个线程;-w 256是内核的局部工作组大小;--thread-concurrency控制单卡拉起的线程总数,直接吃显存;--scrypt切换算法分支,不加这个参数会按 SHA-256 走,算力数字会完全不同;-T强制文本界面,避免在无桌面会话里卡住;-L把日志落到文件,后面排错全靠它。

第一次跑建议把强度压到 8 左右,只观察三十秒,看到界面上有算力输出、设备温度在动,再往上加。上来就拉满强度,很容易在驱动层触发超时重置,屏幕上出现一闪而过的花屏,然后设备号消失,那种情况下你根本分不清是配置问题还是硬件问题。

3. 把参数写进 cgminer.conf:强度、worksize 与 thread-concurrency 的换算

命令行跑通之后,下一步是把参数固化下来。理由很实际:这套参数动辄十几个,还带显卡电压、风扇曲线这种容易被手误改错的项,每次开机手敲一遍迟早出事。cgminer 支持用一个 JSON 文件承载全部配置,启动时-c指过去就行,配置文件里的键名和命令行参数一一对应,但写法上有几个必须注意的细节,写错了程序会静默忽略而不是报错。

3.1 配置文件骨架:pools 数组与全局段

配置文件是标准 JSON,顶层两个部分:pools数组放矿池,其余键是全局参数。矿池数组里每个对象至少要有url、user、pass三个字段,第二个及以后的池子默认作为故障转移候选。全局段里所有数值型参数在配置文件里都要写成字符串,这是 cgminer 的解析习惯,写成数字反而可能读不到;布尔型参数才写 true/false。

{ "pools": [ { "url": "stratum+tcp://pool.example.com:3333", "user": "worker1", "pass": "x" }, { "url": "stratum+tcp://backup.example.com:3333", "user": "worker1", "pass": "x" } ], "failover-only": true, "intensity": "13", "worksize": "256", "thread-concurrency": "8192", "gpu-threads": "2", "scrypt": true, "queue": "1", "scan-time": "1", "expiry": "30", "no-submit-stale": true, "api-listen": true, "api-port": "4028", "temp-target": "72", "temp-overheat": "82", "temp-cutoff": "88", "auto-fan": true }

failover-only设为 true 表示只有主池彻底不可用时才切备用池,避免两个池来回横跳导致算力统计被劈成两半。queue在 scrypt 下建议设 1,队列越深延迟越大,反而拉高过期份额比例。scan-time和expiry控制任务的有效期判断,scan-time 1让程序更频繁地检查新任务。no-submit-stale是省钱的关键开关,过期份额直接丢弃不提交,减少无效请求。api-listen打开本地 JSON 接口,下一章专门讲。

temp-target、temp-overheat、temp-cutoff三个温度阈值必须按顺序递增,写反了程序的行为会很怪:temp-target是自动风扇的目标温度,temp-overheat触发降频,temp-cutoff直接退出保护硬件。这三个值不要照抄,风道差的机器把 cutoff 设到 88 度等于没有保护。

注意:改完配置先别急着跑长任务,用cgminer.exe -c cgminer.conf -T启动,看第一屏打印出来的参数是不是你写的那些。cgminer 遇到不认识的键会直接跳过,一个拼写错误能让整段配置白写。

3.2 强度、worksize、thread-concurrency 的三角关系

这三个参数是最容易被抄错的,因为它们之间存在资源约束,不是一个一个独立调的。thread-concurrency是单卡同时展开的线程总数,每个线程在 scrypt 算法下要占一块显存做中间状态,所以它和显存容量是硬绑定关系;worksize是这些线程被切成多大的工作组交给显卡调度,必须是 2 的幂,多数卡在 256 附近表现最好;intensity决定每个线程内部循环的展开程度,强度越高,单次内核执行时间越长,显卡被占用的时间片也越长。

调参的纪律是"一次只动一个,动完看三个数":算力、拒绝率、温度。三个数里任何一个变差就退回去。我见过太多人一次性把强度、线程数、工作大小全改一遍,跑出来算力涨了百分之五,就以为找到了最优解,实际上拒绝率也涨了一截,净收益是负的,只是界面上那个大数字好看。

参数常见起点调大的收益调过头的症状
thread-concurrency8192算力上升,显存占用线性增长分配失败、Error -4、设备从列表里消失
worksize256工作组调度更充分算力不升反降,日志里出现内核执行错误
intensity13单次内核执行更饱满桌面卡顿、拒绝率上升、驱动超时重置
gpu-threads2提高设备利用率显存被吃穿,两个线程互相抢资源

显存这块给一个粗略的换算经验:scrypt 下每 1024 个并发线程大约要预留 128 MB 级别的显存做中间缓冲区,所以 2 GB 的卡把 thread-concurrency 顶到 16384 已经很紧张,还要留出系统与桌面合成的余量。稳妥的做法是从 8192 起步,每次加 2048,加到算力不再明显增长或者出现分配错误为止,然后退回上一档。这个搜边界的过程比抄别人的参数表可靠得多,因为同一型号的卡,显存颗粒批次不同,能顶到的上限也不一样。

worksize的调整空间其实很小,128 和 256 两个值就能覆盖绝大多数情况。它本质上是告诉驱动"这批线程按多大的小组去抢计算单元",小组太小调度开销大,太大则尾部的空闲线程多。判断方法很简单:固定其他参数,只改 worksize,看十秒平均算力的抖动幅度,选抖动最小的那个。抖动比均值更能说明问题,均值高但抖得厉害,说明内核执行时间不均匀,长跑下去容易出现超时。

3.3 批处理启动与日志落盘

配置固化之后,还需要一层进程守护。这类长时间跑 GPU 的程序,遇到驱动重置、显存碎片、网络闪断都可能自己退出,无人值守的机器上退出就等于白跑一整晚。批处理里套一个循环,退出后等十几秒再拉起,是最省事的做法,比装一堆监控服务轻量得多。

@echo off setlocal enabledelayedexpansion cd /d %~dp0 set LOGDIR=%~dp0logs if not exist "%LOGDIR%" mkdir "%LOGDIR%" :loop rem 用当前时间拼日志名,避免多次重启互相覆盖 set TS=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2% set TS=!TS: =0! cgminer.exe -c cgminer.conf -T -L "%LOGDIR%\cgm_!TS!.log" rem 退出后等 15 秒再拉起,给驱动释放显存的时间 timeout /t 15 /nobreak >nul goto loop

cd /d %~dp0把工作目录切到脚本所在位置,这一步不能省,因为 cgminer 找内核目录是按当前工作目录算的,用计划任务开机自启时默认目录是系统目录,内核会找不到。enabledelayedexpansion配合!TS!是为了在循环体内取到实时更新的变量值,用%TS%会一直取到第一次展开的结果,日志名全一样。

日志按时间戳分文件的好处是排错时能精确对上"几点几分掉了一次卡"。单个日志文件不宜太大,跑一天下来几十兆很常见,定期清理或者按天归档,别让磁盘被日志吃满,磁盘满导致的退出最难查,因为日志本身也写不进去了。

timeout那 15 秒不是随便定的。驱动重置后显存释放有延迟,立刻重启常常会拿到一个"设备存在但分配失败"的状态,多等十几秒能显著提高重启成功率。如果你的机器掉卡频繁,把这个值加到 30 秒,顺便去查第 5 章里关于驱动超时的部分。

4. 拿下 4028 端口:API 命令集、轮询脚本与温度联动

界面上看到的数字是给人看的,要做事得让程序把数据吐出来。cgminer 内置一个 JSON 接口,默认监听本地 4028 端口,一条 TCP 连接发一个 JSON 命令,服务端回一个 JSON 结果。这个接口的价值在于:它把算力、温度、风扇转速、每个池的接受与拒绝计数全部结构化输出,你可以直接接到自己的采集脚本里,不用再对着屏幕截图识别数字。

4.1 API 命令集与返回结构

接口是纯文本协议,请求体就是{"command":"xxx"},返回是一段 JSON 后跟一个空字符结尾。常用命令不多,记住下面这几个就够日常用:

命令作用关键返回字段
summary整体运行状态MHS av、Accepted、Rejected、Elapsed
devs每张卡的明细Temperature、Fan Speed、MHS av、Device Elapsed
pools各矿池连接状态Stratum Active、User、Diff、Stratum Difficulty
gpu显卡引擎与显存频率GPU Clock、Memory Clock、Vddc
switchpool切换主池,参数带池编号返回切换结果
restart重启挖矿进程返回状态
quit退出程序返回状态

summary里的MHS av是全程平均算力,单位是兆哈希每秒,看瞬时值意义不大,因为 scrypt 算法的算力天然是锯齿状的,采样间隔越短抖得越厉害。devs里的温度是驱动读回来的传感器值,和第三方监控软件偶尔会差一两度,属于正常现象。pools里的Stratum Difficulty很关键,池子调整难度时这个值会变,难度变了之后本地的拒绝率统计才有可比性。

接口默认只允许本地访问,远程访问要额外开--api-network并配合白名单,这个开关别乱开,一个没有认证的裸接口暴露在网络上,后果自己掂量。

4.2 一个能直接用的轮询脚本

下面这段 Python 干三件事:连上去取一次汇总、取一次设备明细、把关键指标打到一行里。写法上注意两点,一是连接建立后服务端只处理一条命令就关闭,所以每次查询都要新建连接;二是返回数据尾部有空字符,解析前要剥掉,否则 json 解析会直接抛异常。

import json import socket import time def ask(cmd, host="127.0.0.1", port=4028, timeout=3.0): """发一条 API 命令,返回解析后的 dict;失败返回空 dict。""" try: with socket.create_connection((host, port), timeout=timeout) as s: s.sendall((json.dumps({"command": cmd}) + "\n").encode("utf-8")) raw = b"" while True: chunk = s.recv(4096) if not chunk: # 服务端发完就关连接 break raw += chunk # cgminer 的返回以 \x00 结尾,必须先剥掉再解析 text = raw.decode("utf-8", "ignore").strip("\x00 \r\n") return json.loads(text) if text else {} except (OSError, json.JSONDecodeError) as e: print(f"[api] {cmd} 失败: {e}") return {} def snapshot(): s = ask("summary").get("SUMMARY", [{}])[0] d = ask("devs").get("DEVS", []) temps = "/".join(str(x.get("Temperature", "?")) for x in d) fans = "/".join(str(x.get("Fan Speed", "?")) for x in d) print( f"MHS={s.get('MHS av', 0):.2f} " f"接受={s.get('Accepted', 0)} 拒绝={s.get('Rejected', 0)} " f"温度={temps} 风扇={fans}" ) if __name__ == "__main__": while True: snapshot() time.sleep(10)

ask里用with管理连接,保证异常时也能关掉,不会在长时间轮询里堆积半开连接。recv循环读到空字节为止,是因为接口不支持长连接,单次请求的数据量很小,一般一次就收完,但网络抖动时可能分包,循环读更稳。strip("\x00 \r\n")这一句是血泪经验,最初没剥空字符,json.loads一直报"Expecting value",排查了半小时才反应过来是结尾那个不可见字符。

snapshot只取每张卡的第一条汇总数据,如果机器上有多张卡,DEVS数组会逐条返回,温度和风扇拼成斜杠分隔的字符串方便一眼扫。实际接入监控系统时,建议把这段改成返回结构化 dict,再由上层决定告警阈值,别把判断逻辑写死在采集端。

4.3 温度、风扇与自动降频的联动边界

cgminer 自己带了一套温度保护:超过temp-overheat就降频,超过temp-cutoff就直接退出。这套机制在多数场景下够用,但有两个边界必须知道。第一,自动风扇依赖驱动上报的风扇控制接口,部分型号的卡在 Windows 下这个接口是残的,auto-fan打开后风扇转速纹丝不动,这时候要么用--gpu-fan写死一个固定百分比,要么交给显卡厂商自己的控制面板。第二,降频动作有延迟,温度是几秒采样一次,积热快的卡在两次采样之间就可能冲到危险值,所以temp-cutoff要比你心理预期低三到五度。

--auto-gpu打开后程序会在超温时自动下调引擎频率,代价是算力掉一截,需要手动或重启才能恢复。我的建议是:把自动降频当作最后一道保险,日常靠固定频率加良好风道控制温度,让保护机制永远不触发。真正需要精细化温控的机器,用 4.2 那个脚本读到温度后,由外部逻辑调用 API 做切换和启停,比依赖程序内部逻辑可控得多。

还有一个容易忽略的点:多卡机器上,devs返回的温度顺序和物理卡槽顺序不一定一致。第一次部署时用--ndevs输出的设备编号和 PCI 位置对一遍,把映射关系记下来,否则你会盯着"卡 1 温度 90 度"去拆机箱,结果发现过热的是藏在后面的卡 3。

5. 避坑:Windows 上跑 3.1.1 最容易翻的五个车

这一章全是踩过的坑,按"看到什么现象、为什么、怎么解决"三句话写清楚。每一条我都至少遇到过一次,有的是环境问题,有的是这个版本本身的限制,分清楚能省很多时间。

5.1 双击就闪退,命令行能看到窗口但瞬间关闭

现象:资源管理器里双击 cgminer.exe,黑窗口一闪而过,什么信息都留不下。

原因:控制台程序退出太快,报错信息来不及看;更常见的是同目录缺运行库,进程加载阶段就失败了,任务管理器里连进程名都抓不到。

解决:永远不要双击,用 cmd 或 PowerShell 进到目录里执行.\cgminer.exe --ndevs,让输出留在窗口里。如果这时报"找不到 xxx.dll",把缺失的库补到同目录,注意必须和 exe 位宽一致,32 位程序塞 64 位库会报入口点错误。补库之后还闪退的,用-T强制文本模式再跑一次,排除界面渲染问题。

5.2 --ndevs 只列出 CPU 或者干脆列表为空

现象:显卡驱动明明装好了,设备列表里只有一个处理器设备,或者显示"List of devices:"之后什么都没有。

原因:32 位 OpenCL 运行时没装全,或者注册表 32 位视图下的厂商条目缺失。新驱动默认只装 64 位组件,32 位程序读不到实现库,自然枚举不出设备。

解决:先reg query "HKLM\SOFTWARE\Wow6432Node\Khronos\OpenCL\Vendors"确认条目存在,再补装对应显卡的 32 位运行时。装完重启,重新跑--ndevs。如果条目在、设备还是空,去设备管理器看显卡是否被标记为异常,以及有没有别的程序独占显卡。

5.3 启动后算力恒为 0,日志里出现 Error -4 或 Error -5

现象:程序正常启动,设备也识别了,但算力一直是 0,日志里反复刷错误码。

原因:这两个错误码对应显存分配失败和资源不足,说白了就是--thread-concurrency开得太大,显存被吃穿。另外就是路径含中文或空格,导致内核编译产物写不进去。

解决:先把 thread-concurrency 砍半,从 8192 降到 4096 试,能正常出算力了再一档档加回去。同时确认目录路径是纯英文、无空格、不在受保护目录下。如果减半后仍然报错,把强度也降下来,排除两个参数叠加超限的可能。

5.4 跑几分钟算力突然归零,设备从列表里消失

现象:稳定跑了五到十分钟,算力曲线突然断崖式归零,界面上设备数量少了一个,过一会儿自动恢复,循环往复。

原因:Windows 的显示驱动超时检测机制在起作用。GPU 连续满载,单次内核执行时间超过了系统默认的两秒阈值,系统认为驱动卡死,强制重置,重置期间设备不可用。

解决:两条路,一是降低强度缩短单次内核执行时间,从 13 降到 11 或 10;二是调高系统的超时阈值。注册表HKLM\SYSTEM\CurrentControlSet\Control\GraphicsDrivers下新建 DWORD 值TdrDelay,十进制设成 8 到 10,重启生效。第二种方式会让真正卡死的驱动也拖更久才恢复,机器上有其他图形任务时慎用。改注册表之前先导出备份,这条没有后悔药。

5.5 文件被杀软直接删掉或隔离

现象:解压完没过多久,exe 或某个 dll 不见了,或者双击报"文件已损坏"。

原因:这个包的年代久远,打包方式与某些启发式规则高度重合,被误判是常事。老版本没有数字签名,申诉也没用。

解决:把整个目录加到杀软的排除列表,然后在解压后先核对文件完整性,记下每个文件的哈希,之后每次异常先对哈希,确认是不是文件被动过。别从二手渠道重新下载补文件,来源不明的二进制比误报危险得多。

6. 验证算力不是虚的:三点对账法与长跑采样

跑起来不等于跑对了。界面上那个算力数字是本地统计出来的,包含所有提交尝试,甚至包括最终被池子拒绝的那部分。要让这个数字可信,得做三件事:本地算力与池端算力对账、接受率与拒绝原因分类、连续采样看波动。这一章给一套可以照着做的验收流程,顺便说说我怎么判断一台机器到底能不能长期跑。

6.1 三点对账:本地、池端、时间

对账需要三个数据源:cgminer 的summary接口、矿池后台显示的算力、以及程序已运行的时长。三个数放一起看,能立刻暴露大部分问题。

本地MHS av是全程平均,池端显示的通常是滑动窗口平均,两者天然会有几分钟的相位差,所以对账要挑运行满一小时之后的时间点。正常情况下两者差距在百分之五以内算正常,超过百分之十就要查。如果本地明显高于池端,说明大量提交没被接受,去看拒绝原因;如果本地明显低于池端,多半是池端的窗口算法把早期低算力时段平滑掉了,等半小时再看。

拒绝原因是关键信息。日志里Rejected后面通常跟一句原因,最常见的是过期份额和难度不匹配。过期份额多说明网络延迟高或者scan-time设得太大,把scan-time调到 1、expiry调到 30,能明显改善。难度不匹配说明本地提交的份额低于池子当前难度,通常是强度太低导致单次计算量不够,往上加一两档强度再看。

6.2 长跑采样脚本与判读标准

单点数据没有说服力,要做的是连续采样然后看分布。下面这段在 4.2 的基础上加了统计,跑十分钟,输出均值、标准差和最大最小温度,标准差除以均值就是波动率,这个指标比均值更能说明机器跑得稳不稳。

import statistics import time from collections import defaultdict from api_poll import ask # 复用 4.2 里的 ask() def sample(minutes=10, interval=10): mhs, temps = [], defaultdict(list) t_end = time.time() + minutes * 60 while time.time() < t_end: s = ask("summary").get("SUMMARY", [{}])[0] mhs.append(float(s.get("MHS av", 0))) for i, d in enumerate(ask("devs").get("DEVS", [])): temps[i].append(float(d.get("Temperature", 0))) time.sleep(interval) avg = statistics.mean(mhs) sd = statistics.pstdev(mhs) print(f"算力均值 {avg:.2f} MHS,标准差 {sd:.2f},波动率 {sd/avg*100:.1f}%") for idx, t in temps.items(): print(f"卡{idx} 温度 最低{min(t):.0f} 最高{max(t):.0f} 均值{statistics.mean(t):.0f}") return avg, sd if __name__ == "__main__": sample()

interval设 10 秒是折中,太短会被算法的锯齿干扰,太长又抓不到瞬时过热。波动率这个指标我一般按三档判读:百分之五以内算跑得很整齐,五到十之间属于正常抖动,超过十就要查了,常见原因是worksize和卡的架构不匹配,或者有别的进程在抢显卡。温度那一行看的是最高值而不是均值,最高值逼近temp-overheat就说明散热余量不够,长时间跑必然触发降频。

6.3 一套固定的验收流程

基于上面这些,我现在验收一台机器固定走五步:第一步--ndevs确认设备枚举正常;第二步用低强度跑五分钟,看日志里有没有内核编译错误;第三步把强度加到目标值跑十分钟,用 6.2 的脚本采一轮,看波动率;第四步跑满一小时,做一次三点对账,确认拒绝率在可接受范围;第五步拔掉网络三十秒再插回,看程序能不能自己重连、算力能不能恢复。最后这一步最容易被跳过,但它是唯一能验证故障恢复能力的测试,无值守机器上这一项不过关,前面四项全白做。

有个习惯我保持了很久:每次改完参数,都把配置文件复制一份带日期后缀存起来,同时在日志目录里留一条备注说明改了什么、为什么改。这套参数调优本质上是个试错过程,两周后你只会记得"当时调过 thread-concurrency",绝不会记得是从 8192 调到 12288 还是反过来。留档这件事看起来笨,但在我第三次因为参数回滚不到位而重跑一整晚之后,就再也没省过。

希望这些能帮你少走点弯路,尤其是路径和运行时那两关,卡在那里的人最多。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进