# 拷机样机设备说明文档

> 飞腾 **D3000 + 昇腾 310P** 高低温拷机样机  

> 系统:麒麟 UKUI · 内核 `5.4.18-87.76-generic` · 用户 `kylin`  

> 工程路径:`/home/kylin/hdd3/main/QTracker_booster_G4040`

---

## 1. 硬件配置

| 部件 | 型号/规格 | 说明 |

|------|-----------|------|

| CPU | 飞腾 D3000(8 核) | `nproc` = 8,频率约 2100 MHz |

| NPU | 昇腾 310P | 卡号多为 **16**,芯片 ID **0**;`npu-smi` 表内 AICore 常为 NA |

| 系统盘 | 梵想 S790 4TB NVMe | `/dev/nvme0n1`,序列号示例 FXS790233120414 |

| 数据/克隆盘 | 梵想 S790 4TB NVMe | `/dev/nvme1n1`,序列号示例 FXS790253820192 |

| 机箱风扇 PWM | 飞腾 X100 PCI `1db7:dc2f` | 驱动 `pwm-phytium-pci.ko`,见 [PWM风扇驱动说明.md](./PWM风扇驱动说明.md) |

| 远程 | ToDesk 4.8.5.1 / SSH | 无显示器时优先 SSH;ToDesk 临时密码需桌面或固定安全密码 |

### 关键 PCIe(摘录)

```text

04:00.6  NPU Controller [1db7:dc24]

0f:00.7  PWM Controller [1db7:dc2f]

07:00.0  nvme0 (系统)

08:00.0  nvme1 (第二块 NVMe)

```

---

## 2. 软件与目录

| 路径 | 用途 |

|------|------|

| `/home/kylin/1.sh` | 终端 2 监控屏(温度/功耗/NPU/CPU/拷机状态) |

| `/home/kylin/2.sh` | 终端 1 拷机主程序(FP16+NPU 显存+CPU 内存+CPU 算力) |

| `/home/kylin/npu_stress_mem_worker.py` | NPU 显存 / CPU 内存 / CPU 算力 worker |

| `/home/kylin/npu_stress_suite/` | 拷机报告输出根目录(按时间戳子目录) |

| `/home/kylin/check_nvme_health.sh` | NVMe SMART 健康 + 顺序读/写带宽测试 |

| `/home/kylin/pwm-phytium-pci.ko` | PCI PWM 风扇驱动 |

| `docs/高低温测试操作手册.md` | 拷机操作步骤(简版) |

| `docs/PWM风扇驱动说明.md` | PWM 安装与占空比 |

| `scripts/install_pwm_phytium_pci.sh` | PWM 开机自启安装 |

| `scripts/apply_pwm_x100.sh` | PWM 高/低转速占空比 |

### 业务程序(拷机前需停止)

```bash

sudo pkill -9 -f QTracker_booster

```

**禁止** `sudo` 运行 `2.sh` / `1.sh`(会找不到 CANN / ascend-dmi 环境)。

---

## 3. 高低温拷机流程(标准两步)

详细步骤见 [高低温测试操作手册.md](./高低温测试操作手册.md)。

### 3.1 准备(每次)

```bash

/home/kylin/1.sh fixtime          # 系统时间异常(2092 年)时必做

sudo pkill -9 -f QTracker_booster

npu-smi info                      # 确认 NPU 在线

```

### 3.2 推荐顺序

| 顺序 | 终端 | 命令 | 作用 |

|------|------|------|------|

| 1 | 终端 2 | `/home/kylin/1.sh` | 开监控(会清理上次拷机残留) |

| 2 | 终端 1 | `/home/kylin/2.sh` 或 `burn 86400 0` | 开始拷机 |

> `1.sh` 启动时会停掉上次 `2.sh` 进程并清空 `npu_stress_suite` 旧报告。  

> **不要**在终端 1 已跑 `2.sh` 时再重启终端 2 的 `1.sh`。

### 3.3 拷机时长

| 时长 | 命令 |

|------|------|

| 1 小时(默认) | `/home/kylin/2.sh` |

| 8 小时 | `/home/kylin/2.sh burn 28800 0` |

| 12 小时 | `/home/kylin/2.sh burn 43200 0` |

| 24 小时 | `/home/kylin/2.sh burn 86400 0` |

| 快速试跑 ~5 分钟 | `/home/kylin/2.sh quick` |

### 3.4 跑满判定(看 1.sh 或 npu-smi)

| 指标 | 正常拷机 |

|------|----------|

| NPU 即时功耗 | 经常 **≥ 70W**(满负荷约 80~90W) |

| NPU 显存 | **~90%+** |

| CPU 内存(采集) | **~90%+** |

| CPU 占用(采集) | **~80~100%**(需 `stress-ng`) |

| 1.sh 状态行 | **● 拷机进行中** |

仅跑 `1.sh` **不会**加压;`○ 未检测到 2.sh` 且 NPU ~30W 表示没在拷机。

### 3.5 报告位置

```text

/home/kylin/npu_stress_suite/YYYYMMDD_HHMMSS/

  report/summary.txt

  report/report.html

  csv/metrics.csv

```

---

## 4. NVMe 硬盘

### 4.1 健康与带宽一键体检

```bash

sudo /home/kylin/check_nvme_health.sh          # SMART + 顺序读带宽

sudo /home/kylin/check_nvme_health.sh all      # 再加顺序写(写到已挂载分区临时文件)

sudo /home/kylin/check_nvme_health.sh health   # 仅 SMART

```

输出类似 CrystalDiskInfo 中文摘要 + fio 测速。

### 4.2 整盘克隆(示例)

```bash

sudo dd if=/dev/nvme0n1 of=/dev/nvme1n1 bs=1M conv=fsync status=progress

```

监控克隆速度(**写扇区看第 10 列**):

```bash

DEV=nvme1n1

prev=$(awk -v d="$DEV" '$3==d{print $10}' /proc/diskstats)

tprev=$(date +%s)

while true; do

  sleep 2

  read cur tcur <<< "$(awk -v d="$DEV" -v t=$(date +%s) '$3==d{print $10, t}' /proc/diskstats)"

  awk -v p="$prev" -v c="$cur" -v tp="$tprev" -v tc="$tcur" 'BEGIN{

    dt=tc-tp; if(dt<=0) exit

    printf "已写 %.2f GiB | 当前 %.1f MiB/s\n", c*512/1024/1024/1024, (c-p)*512/1024/1024/dt

  }'

  prev=$cur; tprev=$tcur

done

```

### 4.3 新旧盘区分(经验)

| 盘 | 典型特征 |

|----|----------|

| **nvme0n1** | 有系统分区,SMART 读写各约 1TB 级,Host 读写命令均衡 |

| **nvme1n1** | 克隆目标;dd 前读取极少;克隆中写入量快速上升 |

以 **序列号** 贴机箱标签最可靠。

---

## 5. 机箱风扇 PWM(可选)

```bash

# 驱动开机自启

sudo /home/kylin/hdd3/main/QTracker_booster_G4040/scripts/install_pwm_phytium_pci.sh

# 高转速 90%(拷机/高温)

sudo /home/kylin/hdd3/main/QTracker_booster_G4040/scripts/apply_pwm_x100.sh high

# 开机自动高转速(可选)

sudo cp .../scripts/pwm-x100-fan.service /etc/systemd/system/

sudo systemctl enable --now pwm-x100-fan.service

```

厂商参数:`period=50000ns`,高占空 `duty=45000`,低占空 `duty=5000`。详见 [PWM风扇驱动说明.md](./PWM风扇驱动说明.md)。

---

## 6. 远程与维护

| 方式 | 说明 |

|------|------|

| **SSH** | 拷机维护首选;`kylin@<IP>` |

| **ToDesk** | 设备代码见 `/opt/todesk/config/config.ini` 的 `clientid`;临时密码需 GUI 或预设安全密码 |

| **Cursor 远程** | 会占用一定 CPU,与拷机无直接关系 |

### 系统时间

屏顶出现 **2092 年** 必须先校时,否则报告目录时间混乱:

```bash

/home/kylin/1.sh fixtime

```

---

## 7. 常见问题

### CPU 很高但没在拷机

多为 **`kylin-software-properties-service`** 卡死,不是 `1.sh`:

```bash

ps aux --sort=-%cpu | head -8

sudo systemctl stop kylin-software-properties.service

sudo kill -9 <PID>

```

### 1.sh 显示满格但 NPU 只有 ~30W

- 在读 **旧 csv** 或 **2092 年僵尸目录**;以 **NPU 功耗(即时)** 为准。

- 先 `fixtime`,必要时:`rm -rf /home/kylin/npu_stress_suite/2092*`

### 监控误用

| 错误 | 正确 |

|------|------|

| `1.sh 86400` 当拷机时长 | 刷新间隔默认 2s;24h 拷机用 `2.sh burn 86400 0` |

| `sudo ./1.sh` | `/home/kylin/1.sh` |

| 只跑 1.sh 期望满负载 | 终端 1 必须跑 `2.sh` |

### NPU 相关

- 310P **不支持** `ascend-dmi -p TDP`(0x1e);拷机用 **FP16 循环**。

- 命令末尾 `0` 是 **芯片 ID**,不是 CPU 核数。

- 建议:`sudo apt install -y stress-ng`

---

## 8. 迁移到其他同配置机器

压缩包示例:`/home/kylin/npu_stress_package_YYYYMMDD.tar.gz`

```bash

tar -xzvf npu_stress_package_*.tar.gz -C /home/kylin/

cp /home/kylin/npu_stress_package/scripts/* /home/kylin/

chmod +x /home/kylin/1.sh /home/kylin/2.sh /home/kylin/check_nvme_health.sh

mkdir -p /home/kylin/npu_stress_suite

```

最少拷贝:`2.sh`、`1.sh`、`npu_stress_mem_worker.py`。目标机需:`npu-smi`、CANN、ascend-dmi、python3(acl)、建议 `stress-ng` / `fio` / `smartmontools`。

---

## 9. 文档索引

| 文档 | 内容 |

|------|------|

| [设备说明文档.md](./设备说明文档.md) | 本文:整机与脚本总览 |

| [高低温测试操作手册.md](./高低温测试操作手册.md) | 拷机步骤、判据、故障 |

| [PWM风扇驱动说明.md](./PWM风扇驱动说明.md) | PWM 驱动与占空比 |

---

## 10. 记录表(现场填写)

| 项目 | 内容 |

|------|------|

| 样机编号 | |

| 设备名 / ToDesk ID | |

| nvme0 序列号 | |

| nvme1 序列号 | |

| 温箱温度 ℃ | |

| 拷机时长 | 1h / 8h / 12h / 24h |

| 开始 / 结束时间 | |

| 结论 | `summary.txt` PASS/FAIL |

| 备注 | |

---

*文档随现场脚本更新;若路径与压缩包不一致,以 `/home/kylin/` 下实际文件为准。*

#!/bin/bash
# NVMe 一键体检:SMART 健康摘要 + 顺序读/写带宽(类似 CrystalDiskInfo + 测速)
#
# 用法:
#   sudo ./check_nvme_health.sh              健康 + 顺序读带宽(默认)
#   sudo ./check_nvme_health.sh all            健康 + 读 + 写(写到各盘分区临时文件)
#   sudo ./check_nvme_health.sh health         仅 SMART 健康
#   sudo ./check_nvme_health.sh bw             仅带宽
#   sudo ./check_nvme_health.sh /dev/nvme0n1   指定盘
#
# 环境变量:
#   BW_SEC=10        每项 fio 测速秒数
#   BW_SIZE=2G       写测试文件大小上限(在分区可用空间内自动缩小)

set -u

MODE=health+bw
BW_SEC="${BW_SEC:-10}"
BW_SIZE="${BW_SIZE:-2G}"
DISKS=()

usage() {
  cat <<EOF
用法: sudo $0 [health|bw|all|help] [设备...]

  (无参数)     SMART 健康 + 顺序读带宽
  all          健康 + 顺序读 + 顺序写(写到该盘已挂载分区的临时文件,安全)
  health       仅 SMART
  bw           仅带宽测试

示例:
  sudo $0
  sudo $0 all /dev/nvme0n1 /dev/nvme1n1
  BW_SEC=15 sudo $0 bw

依赖: smartmontools, fio(读/写带宽);无 fio 时读测速回退 dd。
EOF
}

need_root() {
  if [[ "$(id -u)" -ne 0 ]]; then
    echo "请使用 root: sudo $0 $*" >&2
    exit 1
  fi
}

run_smartctl() {
  smartctl -H -A "$1" 2>/dev/null
}

health_zh() {
  case "$1" in
    PASSED) echo "良好" ;;
    FAILED) echo "故障" ;;
    *)      echo "${1:-未知}" ;;
  esac
}

warn_zh() {
  local w="$1"
  if [[ "$w" == "0x00" || "$w" == "0" ]]; then echo "无"
  elif [[ -z "$w" ]]; then echo "未知"
  else echo "有告警 ($w)"; fi
}

parse_smart_nvme() {
  local _out="$1"
  health="" cw="" temp="" spare="" used=""
  rd="" rd_u="" wr="" wr_u=""
  poh="" pc="" us="" mde="" err="" t1="" t2=""
  while IFS='=' read -r k v; do
    v="${v# }"
    case "$k" in
      health) health="$v" ;; cw) cw="$v" ;; temp) temp="$v" ;;
      spare) spare="$v" ;; used) used="$v" ;;
      rd) rd="$v" ;; rd_u) rd_u="$v" ;; wr) wr="$v" ;; wr_u) wr_u="$v" ;;
      poh) poh="$v" ;; pc) pc="$v" ;; us) us="$v" ;;
      mde) mde="$v" ;; err) err="$v" ;; t1) t1="$v" ;; t2) t2="$v" ;;
    esac
  done < <(echo "$_out" | awk '
    /^SMART overall-health/ { sub(/.*: /,""); health=$0 }
    /^Critical Warning:/              { cw=$NF }
    /^Temperature:/ && !/Sensor/      { temp=$(NF-1) }
    /^Available Spare:/ && !/Threshold/ { spare=$NF; gsub(/%/,"",spare) }
    /^Percentage Used:/               { used=$NF; gsub(/%/,"",used) }
    /^Data Units Read:/                { rd=$(NF-2); rd_u=$(NF-1)" "$NF; gsub(/[\[\]]/,"",rd_u) }
    /^Data Units Written:/             { wr=$(NF-2); wr_u=$(NF-1)" "$NF; gsub(/[\[\]]/,"",wr_u) }
    /^Power On Hours:/                 { poh=$NF }
    /^Power Cycles:/                   { pc=$NF }
    /^Unsafe Shutdowns:/               { us=$NF }
    /^Media and Data Integrity Errors:/ { mde=$NF }
    /^Error Information Log Entries:/   { err=$NF }
    /^Temperature Sensor 1:/           { t1=$(NF-1) }
    /^Temperature Sensor 2:/           { t2=$(NF-1) }
    END {
      print "health=" health; print "cw=" cw; print "temp=" temp
      print "spare=" spare; print "used=" used
      print "rd=" rd; print "rd_u=" rd_u; print "wr=" wr; print "wr_u=" wr_u
      print "poh=" poh; print "pc=" pc; print "us=" us
      print "mde=" mde; print "err=" err; print "t1=" t1; print "t2=" t2
    }')
}

# 从 fio 输出提取 BW=xxxMiB/s
fio_parse_bw() {
  grep -oE 'BW=[0-9.]+[KMG]?i?B/s' | tail -1 | sed 's/BW=//'
}

# 从 dd 输出提取速度
dd_parse_bw() {
  awk '/copied/ {
    for (i=1;i<=NF;i++)
      if ($(i+1) ~ /\/s$/) { print $i" "$(i+1); exit }
  }'
}

# 找该整盘下空间最大的已挂载分区,用于写测试
find_write_target() {
  local dev=$1 base mp avail best_mp="" best_kb=0 kb
  base=$(basename "$dev")
  while read -r mp; do
    [[ -n "$mp" && "$mp" != "[SWAP]" ]] || continue
    avail=$(df -k "$mp" 2>/dev/null | awk 'NR==2{print $4}')
    [[ "$avail" =~ ^[0-9]+$ ]] || continue
    if (( avail > best_kb )); then best_kb=$avail; best_mp=$mp; fi
  done < <(lsblk -ln -o MOUNTPOINT "/dev/${base}"* 2>/dev/null | sort -u)
  if [[ -n "$best_mp" ]]; then
    echo "${best_mp}/.nvme_bw_test_$$.bin"
  fi
}

run_fio_read() {
  local dev=$1
  local out bw
  if ! command -v fio &>/dev/null; then
    echo "  顺序读(dd): 测试中 (${BW_SEC}s 量级)..."
    out=$(dd if="$dev" of=/dev/null bs=1M count=$(( BW_SEC * 512 )) iflag=direct 2>&1) || true
    bw=$(echo "$out" | dd_parse_bw)
    echo "  顺序读: ${bw:-解析失败}"
    return
  fi
  echo "  顺序读(fio): ${BW_SEC}s direct 1M ..."
  out=$(fio --name=seqread --filename="$dev" --rw=read --bs=1M --iodepth=16 \
    --direct=1 --ioengine=libaio --runtime="$BW_SEC" --time_based --group_reporting \
    --output-format=normal 2>&1) || true
  bw=$(echo "$out" | fio_parse_bw)
  [[ -z "$bw" ]] && bw=$(echo "$out" | grep -i 'read.*iops' | tail -1)
  echo "  顺序读: ${bw:-见下方 fio 摘要}"
  echo "$out" | grep -E 'READ:|lat |iops|bw' | tail -3 | sed 's/^/    /'
}

run_fio_write_file() {
  local dev=$1 target size_human out bw
  target=$(find_write_target "$dev")
  if [[ -z "$target" ]]; then
    echo "  顺序写: 跳过(${dev} 无已挂载分区,避免破坏整盘数据)"
    return
  fi
  size_human="$BW_SIZE"
  if ! command -v fio &>/dev/null; then
    echo "  顺序写(dd): -> $target"
    out=$(dd if=/dev/zero of="$target" bs=1M count=512 oflag=direct conv=fsync 2>&1) || true
    rm -f "$target"
    bw=$(echo "$out" | dd_parse_bw)
    echo "  顺序写: ${bw:-解析失败}"
    return
  fi
  echo "  顺序写(fio): ${BW_SEC}s -> $target"
  out=$(fio --name=seqwrite --filename="$target" --rw=write --bs=1M --iodepth=8 \
    --direct=1 --ioengine=libaio --runtime="$BW_SEC" --time_based --group_reporting \
    --size="$size_human" --output-format=normal 2>&1) || true
  rm -f "$target"
  bw=$(echo "$out" | fio_parse_bw)
  echo "  顺序写: ${bw:-见下方 fio 摘要}"
  echo "$out" | grep -E 'WRITE:|lat |iops|bw' | tail -3 | sed 's/^/    /'
}

section_health() {
  local dev out model serial fw hzh wzh mark tip
  need_root
  if ! command -v smartctl &>/dev/null; then
    echo "未安装 smartctl: apt install -y smartmontools" >&2
    return 1
  fi
  local now_str
  now_str=$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "未知")
  echo "════════════════════════════════════════════════════════"
  echo "  NVMe SMART 健康摘要  $now_str"
  if [[ "$now_str" =~ ^209[0-9] ]]; then
    echo "  ⚠ 系统时间异常,请先: /home/kylin/1.sh fixtime"
  fi
  echo "════════════════════════════════════════════════════════"

  for dev in "${DISKS[@]}"; do
    [[ -b "$dev" ]] || { echo ""; echo "[跳过] $dev"; continue; }
    out=$(run_smartctl "$dev") || { echo ""; echo "【$dev】SMART 读取失败"; continue; }
    if ! echo "$out" | grep -q 'SMART/Health Information'; then
      echo ""; echo "【$dev】非 NVMe SMART"; continue
    fi
    parse_smart_nvme "$out"
    model=$(cat "/sys/block/$(basename "$dev")/device/model" 2>/dev/null | xargs || echo "?")
    serial=$(cat "/sys/block/$(basename "$dev")/device/serial" 2>/dev/null | xargs || echo "?")
    fw=$(cat "/sys/block/$(basename "$dev")/device/firmware_rev" 2>/dev/null | xargs || echo "?")
    hzh=$(health_zh "$health")
    wzh=$(warn_zh "$cw")
    if [[ "$health" == "PASSED" && "${mde:-}" == "0" && "${err:-}" == "0" ]]; then mark="✓"; else mark="!"; fi
    echo ""
    echo "┌─ $dev  $mark"
    echo "│  型号/序列 : $model  ($serial)"
    echo "│  固件      : $fw"
    echo "│  健康状态  : $hzh  (SMART: ${health:-?})"
    echo "│  关键告警  : $wzh"
    echo "│  温度      : ${temp:-?}°C  (传感器 ${t1:-?} / ${t2:-?}°C)"
    echo "│  备用块    : ${spare:-?}%   已磨损: ${used:-?}%"
    echo "│  累计读/写 : ${rd:-?} ${rd_u:-}  |  ${wr:-?} ${wr_u:-}"
    echo "│  通电/上电 : ${poh:-?}h / ${pc:-?}次   异常断电: ${us:-?}次"
    echo "│  介质错误  : ${mde:-?}   错误日志: ${err:-?}条"
    echo "└────────────────────────────────────────"
    if [[ "$health" == "PASSED" ]]; then
      tip="状态正常。"
    elif [[ -n "$health" ]]; then
      tip="建议备份并更换。"
    else
      tip="SMART 解析失败。"
    fi
    [[ -n "${us:-}" && "$us" -gt 5 ]] && tip="${tip} 异常断电偏多。"
    echo "   → $tip"
  done
  echo ""
}

section_bandwidth() {
  need_root
  echo "════════════════════════════════════════════════════════"
  echo "  NVMe 顺序带宽测试  (每盘约 ${BW_SEC}s,direct I/O)"
  echo "════════════════════════════════════════════════════════"
  local do_write=0
  [[ "$MODE" == *all* || "$MODE" == *write* ]] && do_write=1

  for dev in "${DISKS[@]}"; do
    [[ -b "$dev" ]] || continue
    echo ""
    echo "【$dev】 $(cat /sys/block/$(basename "$dev")/device/model 2>/dev/null | xargs)"
    run_fio_read "$dev"
    if (( do_write )); then
      run_fio_write_file "$dev"
    fi
  done

  if (( ! do_write )); then
    echo ""
    echo "  提示: 加写带宽测试请用: sudo $0 all"
  fi
  echo ""
  echo "════════════════════════════════════════════════════════"
}

# ── 参数解析 ──
args=()
while [[ $# -gt 0 ]]; do
  case "$1" in
    help|-h|--help) usage; exit 0 ;;
    health) MODE=health; shift ;;
    bw|bandwidth) MODE=bw; shift ;;
    all) MODE=all; shift ;;
    /dev/*) DISKS+=("$1"); shift ;;
    *) echo "未知参数: $1 (用 help)" >&2; exit 1 ;;
  esac
done

if [[ ${#DISKS[@]} -eq 0 ]]; then
  mapfile -t DISKS < <(ls -1 /dev/nvme*n1 2>/dev/null | sort -V)
fi
[[ ${#DISKS[@]} -gt 0 ]] || { echo "未找到 /dev/nvme*n1" >&2; exit 1; }

case "$MODE" in
  health)   section_health ;;
  bw)       section_bandwidth ;;
  all)      section_health; section_bandwidth ;;
  health+bw|*) section_health; section_bandwidth ;;
esac

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐