Docker torchserve 部署模型流程——以WSL部署YOLO-FaceV2为例

Docker torchserve 部署模型流程——以WSL部署YOLO-FaceV2为例

Docker torchserve 模型部署

    • 一、配置WSL安装docker
    • 二、配置docker环境
      • 1,拉取官方镜像
      • 2,启动docker容器,将本地路径映射到docker
      • 3,查看docker镜像
      • 4,进入docker容器
      • 5,在docker容器中配置模型需要的Python依赖包
      • 6,如果修改过docker容器配置,需要将自定义的容器保存为镜像
      • 7,第一次配置docker的完整执行步骤
      • 8,完整配置并保存docker镜像后,重新启动、进入docker容器的执行步骤
    • 三、编写handler文件,生成.mar文件
      • 生成.mar文件指令,注意指令格式
      • 1,handler文件initialize函数修改
      • 2,模型文件很多,需要加载的文件很多,--extra-files很多肿么办?服用zip压缩包可以救命!
      • 3,handler文件preprocess函数修改,这里以图片为例
    • 四、.mar文件生成之后,重启测试

一、配置WSL安装docker

WSL官方教程
1,https://learn.microsoft.com/zh-cn/windows/wsl/?source=recommendations
2,https://learn.microsoft.com/zh-cn/windows/wsl/setup/environment?source=recommendations

docker安装,这里注意选择合适的docker版本
https://docs.docker.com/
在这里插入图片描述

二、配置docker环境

1,拉取官方镜像

地址https://hub.docker.com/r/pytorch/torchserve/tags

docker pull pytorch/torchserve:0.7.0-gpu

**左侧为docker镜像,右侧为拉取命令**
左侧为docker镜像,右侧为拉取命令

docker镜像对应的dockerfile:
https://hub.docker.com/layers/pytorch/torchserve/0.7.0-gpu/images/sha256a8a5fb048b20fb71fed43d47caf370e5f4e15f27c219234734d8bb7d7870c158?context=explore

在这里插入图片描述

2,启动docker容器,将本地路径映射到docker

YOLO-FaceV2 Windows本地路径
在这里插入图片描述
YOLO-FaceV2 WSL路径
在这里插入图片描述

# pytorch/torchserve:0.7.0-gpu     docker启动指令
docker run --rm -it --gpus all -p 8080:8080 -p 8081:8081 -v /mnt/c/data/CrowdCounting/serve/YOLO-FaceV2-master:/home/model-server/extra-files -v /mnt/c/data/CrowdCounting/serve/YOLO-FaceV2-master/model-store:/home/model-server/model-store pytorch/torchserve:0.7.0-gpu
# docker路径映射指令,根据自己的需要增加映射指令
-v /mnt/c/data/CrowdCounting/serve/YOLO-FaceV2-master:/home/model-server/extra-files 

在这里插入图片描述

3,查看docker镜像

docker启动之后,查看镜像、容器以及进入容器需要打开新的terminal
在这里插入图片描述

# docker启动之后,查看镜像、容器以及进入容器需要打开新的terminal
# 查看所有镜像
docker images
# 查看已启动的镜像容器,一个镜像可以启动多个容器
docker ps

在这里插入图片描述

4,进入docker容器

# 把cc4313027126修改为自己的容器ID
docker exec -it cc4313027126 /bin/bash

5,在docker容器中配置模型需要的Python依赖包

torch官网地址https://pytorch.org/get-started/previous-versions/

# 从官网安装torch
pip install torch==1.10.1+cu111 torchvision==0.11.2+cu111 torchaudio==0.10.1 -f https://download.pytorch.org/whl/cu111/torch_stable.html
# 从清华源安装其他依赖
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

6,如果修改过docker容器配置,需要将自定义的容器保存为镜像

# 50e43f173778为容器ID,yoloface:cu111为新镜像名字
docker commit 50e43f173778 yoloface:cu111

7,第一次配置docker的完整执行步骤

第一次配置docker,
特别是需要修改docker配置、需要根据requirements.txt文件安装python包的,
建议严格按照1~6的顺序,依次完成步骤,中间不要跳步!!!
否则可能无法成功启动docker,届时需要删掉已配置的docker容器,从头再来!!!

8,完整配置并保存docker镜像后,重新启动、进入docker容器的执行步骤

完整配置并保存docker镜像后,
不再需要修改docker配置、不再安装python包的,
按照2~4的步骤顺序,重新启动、进入docker容器,不再执行步骤1、5、6

注意,此时重新启动的docker应该为刚刚保存的yoloface:cu111,不再是最开始的pytorch/torchserve:0.7.0-gpu,因此将步骤2的启动指令更新为下面的指令

# yoloface:cu111     docker启动指令
docker run --rm -it --gpus all -p 8080:8080 -p 8081:8081 -v /mnt/c/data/CrowdCounting/serve/YOLO-FaceV2-master:/home/model-server/extra-files -v /mnt/c/data/CrowdCounting/serve/YOLO-FaceV2-master/model-store:/home/model-server/model-store yoloface:cu111

三、编写handler文件,生成.mar文件

生成.mar文件指令,注意指令格式

# 生成.mar文件指令示例,参数根据自己的情况重新设置
# 注意:正确设置路径,否则会产生一系列错误!!!!!
# 不要问我是怎么知道的,mar不相信眼泪
torch-model-archiver --model-name yolofacev2 --version 1.0 --model-file experimental.py --serialized-file best.pt --handler handler.py --extra-files "models.zip, utils.zip"# 注意--extra-files "models.zip, utils.zip",引号中zip文件间的空格,如果有空格且出现“找不到zip文件”的错误,那就去掉空格

注意指令参数与文件相对路径对应
在这里插入图片描述
神经病啊,没有handler文件怎么生成mar?
在这里插入图片描述
这不就来了嘛!

1,handler文件initialize函数修改

接下来的修改过程:
请注意:正确设置路径,否则会产生一系列错误!!!!!
请注意:正确设置路径,否则会产生一系列错误!!!!!
请注意:正确设置路径,否则会产生一系列错误!!!!!

    def initialize(self, context):properties = context.system_propertieslogger.info(f"Cuda available: {torch.cuda.is_available()}")logger.info(f"GPU available: {torch.cuda.device_count()}")use_cuda = torch.cuda.is_available() and torch.cuda.device_count() > 0self.map_location = 'cuda' if use_cuda else 'cpu'self.device = torch.device(self.map_location + ':' +str(properties.get('gpu_id')) if use_cuda else 'cpu')self.manifest = context.manifestmodel_dir = properties.get('model_dir')logger.info("==================model_dir==================="" %s loaded successfully", model_dir)self.model_pt_path = Noneif "serializedFile" in self.manifest["model"]:serialized_file = self.manifest["model"]["serializedFile"]self.model_pt_path = os.path.join(model_dir, serialized_file)model_file = self.manifest['model']['modelFile']logger.info("Model file %s loaded successfully", self.model_pt_path)

上面的代码不要随意修改,它来自BaseHandler文件的initialize函数,它的作用主要是加载model_dir,model_file,与serialized_file,眼熟吗?看这里:

torch-model-archiver 
--model-file experimental.py 
--serialized-file best.pt 
--model-name yolofacev2 
--version 1.0 
--handler handler.py 
--extra-files "models.zip, utils.zip"

model_file,与serialized_file在mar文件生成的指令中出现过,它们负责加载模型文件与模型权重
model_dir是在handler文件执行过程中docker产生的临时路径,嘶~,它长这个亚子:

model_dir:/home/model-server/tmp/models/59324bc14e6c48d5821e157886545f1b

关键在model_dir里存放了“mar文件生成的指令”传入的所有文件
在这里插入图片描述
So,你可以自己找到临时路径,查看你想传入的文件是不是正确传入到model_dir,如果没有?你懂得!

突然感觉docker变得透明了
在这里插入图片描述

2,模型文件很多,需要加载的文件很多,–extra-files很多肿么办?服用zip压缩包可以救命!

“mar文件生成的指令”中–extra-files可以传入压缩包,像这样:

torch-model-archiver 
--extra-files "models.zip, utils.zip"

但需要在initialize导入压缩包并解压,解压后存放的位置就是model_dir,下图包含了解压后的结果
在这里插入图片描述

注意:压缩包里是这个样子的,zip里《必须》有完整的文件夹,解压之后才会有上面的效果!
在这里插入图片描述

导入压缩包并解压程序如下,放在handler文件initialize函数中

with zipfile.ZipFile(model_dir + '/models.zip', 'r') as zip_ref:zip_ref.extractall(model_dir)
with zipfile.ZipFile(model_dir + '/utils.zip', 'r') as zip_ref:zip_ref.extractall(model_dir)
self.load_yoloface_model()

注意看:这个load_yoloface_model函数叫小帅???重来!
在这里插入图片描述
在zip解压之后,才可以在load_yoloface_model函数导入之前被压缩的文件,比如模型文件、config文件等,注意导入文件的时机,否则?你懂得!

   def load_yoloface_model(self):from experimental import attempt_loadfrom utils.datasets import letterboxfrom utils.general import check_img_size, non_max_suppression, scale_coords, xyxy2xywhself.letterbox = letterboxself.check_img_size = check_img_sizeself.non_max_suppression = non_max_suppressionself.scale_coords = scale_coordsself.xyxy2xywh = xyxy2xywh

3,handler文件preprocess函数修改,这里以图片为例

preprocess函数接收到的data是图片经过http转码的,需要转个圈圈,转回来,如下:

    def preprocess(self, data):# Initialize# stride = int(model.stride.max())  # model strideprint("debug--%d", len(data))images = []for row in data:image = row.get("data") or row.get("body")if isinstance(image, str):# if the image is a string of bytesarray.image = base64.b64decode(image)# If the image is sent as bytesarrayelif isinstance(image, (bytearray, bytes)):image = Image.open(io.BytesIO(image))image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)else:# if the image is a listimage = image.get('instances')[0]image = np.divide(torch.HalfTensor(image), 255)img0 = image

4,完整handler文件如下,仅供参考
什么?你觉得我代码写的烂,哎?,不知道为什么人家听不见,我觉得能跑就行!

# -*- coding: utf-8 -*-
import datetime
import os
import cv2
import sys
import zipfile
import numpy as np
import logging
import base64
import torch
import io
from PIL import Image
from ts.torch_handler.base_handler import BaseHandlerlogger = logging.getLogger(__name__)
start_up_time = datetime.datetime.now()
filename = ".//log_" + str(start_up_time).replace(':', '') + '.txt'
logging.basicConfig(filename=filename, level=logging.INFO,format='[%(asctime)s.%(msecs)03d] %(message)s', datefmt='%H:%M:%S')
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))class FaceDetectHandler(BaseHandler):def __init__(self):super().__init__()self.imgsz = 640self.iou_thres = 0.3self.conf_thres = 0.1self.xyxy2xywh = Noneself.scale_coords = Noneself.non_max_suppression = Noneself.check_img_size = Noneself.letterbox = Nonedef load_yoloface_model(self):from experimental import attempt_loadfrom utils.datasets import letterboxfrom utils.general import check_img_size, non_max_suppression, scale_coords, xyxy2xywhself.letterbox = letterboxself.check_img_size = check_img_sizeself.non_max_suppression = non_max_suppressionself.scale_coords = scale_coordsself.xyxy2xywh = xyxy2xywhwith torch.no_grad():self.model = attempt_load(self.model_pt_path, map_location=self.device)  # load FP32 modeldef initialize(self, context):properties = context.system_propertieslogger.info(f"Cuda available: {torch.cuda.is_available()}")logger.info(f"GPU available: {torch.cuda.device_count()}")use_cuda = torch.cuda.is_available() and torch.cuda.device_count() > 0self.map_location = 'cuda' if use_cuda else 'cpu'self.device = torch.device(self.map_location + ':' +str(properties.get('gpu_id')) if use_cuda else 'cpu')self.manifest = context.manifestmodel_dir = properties.get('model_dir')logger.info("==================model_dir==========================="" %s loaded successfully", model_dir)self.model_pt_path = Noneif "serializedFile" in self.manifest["model"]:serialized_file = self.manifest["model"]["serializedFile"]self.model_pt_path = os.path.join(model_dir, serialized_file)model_file = self.manifest['model']['modelFile']logger.info("Model file %s loaded successfully", self.model_pt_path)with zipfile.ZipFile(model_dir + '/models.zip', 'r') as zip_ref:zip_ref.extractall(model_dir)with zipfile.ZipFile(model_dir + '/utils.zip', 'r') as zip_ref:zip_ref.extractall(model_dir)self.load_yoloface_model()def dynamic_resize(self, shape, stride=64):max_size = max(shape[0], shape[1])if max_size % stride != 0:max_size = (int(max_size / stride) + 1) * stridereturn max_sizedef preprocess(self, data):print("debug--%d", len(data))images = []for row in data:image = row.get("data") or row.get("body")if isinstance(image, str):# if the image is a string of bytesarray.image = base64.b64decode(image)# If the image is sent as bytesarrayelif isinstance(image, (bytearray, bytes)):image = Image.open(io.BytesIO(image))image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)else:# if the image is a listimage = image.get('instances')[0]image = np.divide(torch.HalfTensor(image), 255)img0 = imageimgsz = self.imgszif imgsz <= 0:  # original sizeimgsz = self.dynamic_resize(image.shape)imgsz = self.check_img_size(imgsz, s=64)  # check img_size# yolov5的resize,使用比例填充# (683, 1024, 3) -> (448, 640, 3)img = self.letterbox(image, imgsz)[0]# Convert# (448, 640, 3) -> (3, 448, 640)img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416img = np.ascontiguousarray(img)img = torch.from_numpy(img).to(self.device)img = img.float()  # uint8 to fp16/32img /= 255.0  # 0 - 255 to 0.0 - 1.0if img.ndimension() == 3:img = img.unsqueeze(0)images.append([img, img0])return imagesdef inference(self, data, *args, **kwargs):imgsz = self.imgszmodel = self.model# Run inference# img(1,3,448,640)且归一化bbox_sets = []for img_4t, img0_3c in data:pred = model(img_4t)[0]bbox_sets.append([img_4t, img0_3c, pred])return bbox_setsdef postprocess(self, data):boxes = [[] for _ in range(len(data))]for i, bbox_sets in enumerate(data):img_4t, img0_3c, pred = bbox_sets[0], bbox_sets[1], bbox_sets[2]# Apply NMSpred = self.non_max_suppression(pred, self.conf_thres, self.iou_thres)[0]h, w, c = img0_3c.shapeif pred is not None:pred[:, :4] = self.scale_coords(img_4t.shape[2:], pred[:, :4], img0_3c.shape).round()for j in range(pred.size()[0]):*xyxy, conf, cls = pred[j]xyxy = torch.Tensor(xyxy).to(self.device)# xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1)  # normalized xywhxywh = (self.xyxy2xywh(torch.as_tensor(xyxy).view(1, 4)) / 1.0).view(-1)xywh = xywh.data.cpu().numpy()conf = pred[j, 4].cpu().numpy()# landmarks = (pred[j, 5:15].view(1, 10) / gn_lks).view(-1).tolist()# class_num = pred[j, 15].cpu().numpy()x1 = int(xywh[0] - 0.5 * xywh[2])y1 = int(xywh[1] - 0.5 * xywh[3])x2 = int(xywh[0] + 0.5 * xywh[2])y2 = int(xywh[1] + 0.5 * xywh[3])# boxes.append([x1, y1, x2 - x1, y2 - y1, conf])boxes[i].append({"x1": x1,"y1": y1,"x2": x2,"y2": y2,"confidence": conf.item()})return boxes

四、.mar文件生成之后,重启测试

根据mar文件存放地址重启torchserve,mar文件最好放在model-store里,为什么我忘了,后面找到了再补充
在这里插入图片描述

torchserve --stop
torchserve --start --ncs --model-store model-store --models yolofacev2.mar

重启torchserve后,打开新的terminal,可能需要再次进入docker容器(实际操作中,进不进容器需要尝试,有些WSL必须进容器才能进行连接测试,有些WSL必须不进入容器才能进行连接测试,根据实际情况做判断,怎么不报错,怎么来)

# 连接模型
curl http://localhost:8081/models/yolofacev2
# 测试命令
curl http://127.0.0.1:8080/predictions/yolofacev2 -T data/images/zidane.jpg
curl http://127.0.0.1:8080/predictions/yolofacev2 -T data/images/bus.jpg

样例测试结果如下:
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/3772.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis入门到实战教程(基础篇)笔记

教学来源&#xff1a; Redis课程介绍导学_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1cr4y1671t?p1一、Redis 入门 1.认识NoSQL 2.Redis在虚拟机中的安装和开机自启 Redis在虚拟机中安装和配置开机自启-CSDN博客https://blog.csdn.net/qq_69183322/article/deta…

项目管理中常用的三个工具:甘特图、看板、燃尽图

在日常项目管理的实践中&#xff0c;为了更有效地追踪项目进度、优化资源配置和提高团队协作效率&#xff0c;管理者常常会借助一些工具来辅助工作。这些工具的本质在于将抽象复杂的项目管理任务具象化、简单化&#xff0c;以更直观、方便的方式呈现出来。 以下介绍项目管理中…

2024.4.28 机器学习周报

目录 引言 Abstract 文献阅读 1、题目 2、引言 3、创新点 4、总体流程 5、网络结构 5.1、损失函数 5.2、Confidence Maps 5.3、Part Affinity Fields(PAFs) 5.4、多人的PAFs 6、实验 7、结论 深度学习 yolov8实现目标检测和人体姿态估计 Yolov8网络结构 yaml…

el-input-number 只能输入整数,最小值1,最大值5

<el-form-item label"排序" prop"name" > <el-input-number v-model"form.sort" placeholder"请输入唯一排序" :min1 :max"5" :precision"0" class"custom-input-number" /> </el-form-…

uniapp中vue写微信小程序的生命周期差别

根据uniapp官网里的生命周期&#xff0c;感觉不太对劲&#xff0c;就自己测试了几个&#xff0c;发现有所差别。 红字数字 为 实际测试生命周期顺序。 因为需要页面传参 后再 初始化数据&#xff0c;而onLoad(option)接收参数后&#xff0c;就已经过了create()了&#xff0c;所…

02_c/c++开源库ZeroMQ

1.安装 C库 libzmq sudo apt install libzmq3-dev 实例: https://zeromq.org/get-started/?languagec&librarylibzmq# 编译依赖: pkg-config --cflags --libs libzmq or cat /usr/lib/x86_64-linux-gnu/pkgconfig/libzmq.pc -isystem /usr/include/mit-krb5 -I/usr/in…

DSNeRF复现流程

创建虚拟环境安装依赖 conda create -n DSNeRF python3.7pip install -r requirements.txt下载LLFF数据放在创建的data文件下 https://drive.google.com/file/d/1RjhfcbsywOvw0ts1AFSri91mKANvEVOa/view?uspsharing 下载预先训练好的模型 bash download_models.sh渲染视频…

【Linux】进程间通信(共享内存、消息队列、信号量)

一、System V —— 共享内存&#xff08;详解&#xff09; 共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到内核&#xff0c;换句话说&#xff0c;就是进程不再通过执行进入内核的系统调用来传递彼此的数…

C# WinForm —— 10 单选按钮与复选框的介绍与使用

单选按钮 RadioButton 一组单选按钮中&#xff0c;只能选择一个&#xff0c;互相排斥 常用属性、事件&#xff1a; 属性用途(Name)单选按钮的ID&#xff0c;在代码里引用的时候会用到,一般以 rb开头Text单选按钮旁边显示的 文本信息Checked单选按钮的勾选状态Appearance控制单…

【JavaScript】内置对象 ④ ( Math 内置对象常用方法 | 取绝对值 | 向下取整 | 向上取整 | 四舍五入取整 | 取随机数 )

文章目录 一、Math 内置对象常用方法1、计算绝对值 - Math.abs2、取整计算 - Math.floor 向下取整 / Math.ceil 向上取整 / Math.round 四舍五入3、随机数 - Math.random4、代码示例 - 猜随机数 一、Math 内置对象常用方法 1、计算绝对值 - Math.abs 向 Math.abs() 方法中 传入…

报错:测试报错postman(测试接口)

报错如下 c.e.exception.GlobalExceptionHandler : 异常信息&#xff1a; Content type multipart/form-data;boundary--------------------------952399813172082093419475;charsetUTF-8 not supported 解决&#xff1a; 异常信息 Content type multipart/form-data;boundary…

力扣-1832.判断句子是否全为字母句

思路: 首先&#xff0c;我们初始化了一个长度为 26 的布尔值列表 exist&#xff0c;所有值都为 False&#xff0c;表示所有字母初始都未出现过。然后&#xff0c;我们遍历输入的字符串 sentence 中的每个字符。对于每个字符&#xff0c;我们通过计算其 ASCII 码值减去字母 a 的…

深度学习从入门到精通—Transformer

1.绪论介绍 1.1 传统的RNN网络 传统的RNN&#xff08;递归神经网络&#xff09;主要存在以下几个问题&#xff1a; 梯度消失和梯度爆炸&#xff1a;这是RNN最主要的问题。由于序列的长距离依赖&#xff0c;当错误通过层传播时&#xff0c;梯度可以变得非常小&#xff08;消失…

【产品经理修炼之道】- 需求挖掘之手机话费充值

画原型图从来就不是面试考察的重点&#xff0c;分析、解决问题才是产品经理的核心能力。那么该如何积累产品经验呢&#xff1f;本文以以「手机话费充值」为案例&#xff0c;分享整个设计流程和思路&#xff0c;希望对你有所启发。 不少产品新人有个误区&#xff1a;产品经理的日…

C#带引导窗体的窗体设计方法:创建特殊窗体

目录 1.设计操作流程 2.实例 &#xff08;1&#xff09;Resources.Designer.cs &#xff08;2&#xff09;Frm_Main.Designer.cs &#xff08;3&#xff09;Frm_Main.cs &#xff08;4&#xff09;Frm_Start.Designer.cs &#xff08;5&#xff09;Frm_Start.cs &#…

调用另一个程序的数据 IMPORT EXPORT MEMORY ID

*字段介绍&#xff1a; *1.SELNAME 屏幕字段名 *2.KIND 参数类型 P PARAMETER S SELECT-OPTION *3.其他的字段和SELECT-OPTION 一样 SUBMIT xxx VIA SELECTION-SCREEN "要不要显示选择屏幕 WITH SELECTION-TABLE xxx "选择屏幕的值 AND RETURN . 利用SUBMIT XXX …

模拟信号的离散化

本文介绍模拟信号的离散化。 1.采样定理 定义&#xff1a;若想重建输入的模拟信号&#xff0c;采样频率必须大于等于输入模拟信号最高频率的2倍&#xff0c;即&#xff1a; 其中&#xff0c;为采样频率&#xff0c;为输入模拟信号最高频率 否则&#xff0c;信号会发生混叠 2…

电脑本地搭建privateGPT流程

文章目录 前言效果所需文件流程1,python版本2,工程文件安装2.1服务端搭建2.2客户端搭建 3,模型下载 前言 当我的电脑主机在本地运行privateGPT的时候我听到了cpu风扇在呼啸, 至于privateGPT是干什么的, 其实就相当于一个文档分析软件,只不过内置了一个gpt,你把文件丟给他,你可…

Oracle delete删除数据是否为逻辑删除、新插入数据占用的数据块位置实验

假设一&#xff1a;数据库delete删除为直接删除 假设二&#xff1a;数据库delete删除为逻辑删除&#xff0c;在数据块标记出来&#xff0c;但是实际并没有删除。 方式一&#xff1a;通过dump数据块的方式来实现 我们先用小数据量&#xff0c;通过dump数据块的方式来实现 -- 数…

虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本

虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本 情况描述原因分析解决方法 情况描述 公司开发机上打开虚拟机报错&#xff08;w10ent64d.vmx&#xff09;&#xff08;这虚拟机是我在家里开发机上创建的&#xff09;&#xff0c;报错截图如下&#xff1a; 报错信息…