当前位置: 首页 > article >正文

部署qwen2.5-VL-7B

简单串行执行

from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
import torch, time, threadingdef llm(model_path,prompt=None,image=None,video=None,images=None,videos=None,max_new_tokens=2048,temperature=0.6,
):"""model_path: 模型路径prompt: 文本promptimage: 单张图片路径video: 单个视频路径images: 图片路径列表videos: 视频路径列表max_new_tokens: 最大生成token数temperature: 采样温度"""old_time = time.time()gpu_memories = []# 监控代码占用显存def monitor_gpu_memory():import torchwhile not getattr(monitor_gpu_memory, 'stop', False):mem = torch.cuda.memory_allocated() / 1024 / 1024  # MBgpu_memories.append(mem)time.sleep(5)# 加载模型model = Qwen2_5_VLForConditionalGeneration.from_pretrained(model_path,torch_dtype=torch.bfloat16,attn_implementation="flash_attention_2", # "sdpa"device_map="cuda")# 启动显存监控线程monitor_gpu_memory.stop = Falsemem_thread = threading.Thread(target=monitor_gpu_memory)mem_thread.start()processor = AutoProcessor.from_pretrained(model_path, use_fast=True)# 构建messagescontents = []if prompt is not None:contents.append({"type": "text", "text": prompt})if images is not None:for img in images:contents.append({"type": "image", "image": img})elif image is not None:contents.append({"type": "image", "image": image})elif videos is not None:for vid in videos:contents.append({"type": "video", "video": vid})elif video is not None:contents.append({"type": "video", "video": video})messages = [{"role": "user", "content": contents}]# 准备输入text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)image_inputs, video_inputs = process_vision_info(messages)inputs = processor(text=[text],images=image_inputs,videos=video_inputs,padding=True,return_tensors="pt",)inputs = inputs.to(model.device)# 推理generated_ids = model.generate(**inputs,max_new_tokens=max_new_tokens,temperature=temperature)generated_ids_trimmed = [out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]output_text = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False)# 停止显存监控线程monitor_gpu_memory.stop = Truemem_thread.join()total_time = time.time() - old_timetotal_tokens = sum(len(ids) for ids in generated_ids_trimmed)speed = total_tokens / total_time if total_time > 0 else 0avg_mem = sum(gpu_memories) / len(gpu_memories) if gpu_memories else 0return {"output_text": output_text,"total_time": total_time,"total_tokens": total_tokens,"speed": speed,"avg_mem": avg_mem,}# 示例调用
if __name__ == "__main__":# https://huggingface.co/unsloth/Qwen2.5-VL-7B-Instruct-unsloth-bnb-4bitmodel_path = "/mnt/d/LLaMA-Factory/Qwen/Qwen2.5-VL-7B-Instruct-unsloth-bnb-4bit"result = llm(model_path=model_path,prompt="识别图中文字,如果有表格等特殊格式需要保留原格式。不用解释和总结,直接输出识别结果。",image=r"/mnt/c/Users/CJK/Desktop/3.png",max_new_tokens=2048,temperature=1.0)print(result["output_text"])print(f"总耗时: {result['total_time']:.2f}s, 生成token数: {result['total_tokens']}, 输出速度: {result['speed']:.2f} token/s, 平均占用显存: {result['avg_mem']:.2f} MB")result = llm(model_path=model_path,prompt="识别图中文字,如果有表格等特殊格式需要保留原格式。不用解释和总结,直接输出识别结果。",images=[r"/mnt/c/Users/CJK/Desktop/1.png", r"/mnt/c/Users/CJK/Desktop/3.png"],max_new_tokens=2048,temperature=0.6)print(result["output_text"])print(f"总耗时: {result['total_time']:.2f}s, 生成token数: {result['total_tokens']}, 输出速度: {result['speed']:.2f} token/s, 平均占用显存: {result['avg_mem']:.2f} MB")result = llm(model_path=model_path,prompt="识别图中文字,如果有表格等特殊格式需要保留原格式。不用解释和总结,直接输出识别结果。",video=r"/mnt/c/Users/CJK/Desktop/2.mp4",max_new_tokens=2048,temperature=0.6)print(result["output_text"])print(f"总耗时: {result['total_time']:.2f}s, 生成token数: {result['total_tokens']}, 输出速度: {result['speed']:.2f} token/s, 平均占用显存: {result['avg_mem']:.2f} MB")print(f"总耗时: {result['total_time']:.2f}s, 生成token数: {result['total_tokens']}, 输出速度: {result['speed']:.2f} token/s, 平均占用显存: {result['avg_mem']:.2f} MB")result = llm(model_path=model_path,prompt="识别图中文字,如果有表格等特殊格式需要保留原格式。不用解释和总结,直接输出识别结果。",videos=[r"/mnt/c/Users/CJK/Desktop/1.mp4", r"/mnt/c/Users/CJK/Desktop/2.mp4"],max_new_tokens=2048,temperature=0.6)print(result["output_text"])print(f"总耗时: {result['total_time']:.2f}s, 生成token数: {result['total_tokens']}, 输出速度: {result['speed']:.2f} token/s, 平均占用显存: {result['avg_mem']:.2f} MB")

异步/并行执行

from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
import torch, time, threading
import concurrent.futures
from typing import List, Dict, Union, Optional, Any# 全局变量用于存储加载的模型和处理器
_MODEL = None
_PROCESSOR = None
_MODEL_LOCK = threading.Lock()def load_model_and_processor(model_path):"""加载模型和处理器,如果已经加载则返回缓存的实例"""global _MODEL, _PROCESSORwith _MODEL_LOCK:if _MODEL is None or _PROCESSOR is None:# 加载模型_MODEL = Qwen2_5_VLForConditionalGeneration.from_pretrained(model_path,torch_dtype=torch.bfloat16,attn_implementation="flash_attention_2",  # "sdpa"device_map="cuda")# 加载处理器_PROCESSOR = AutoProcessor.from_pretrained(model_path, use_fast=True)return _MODEL, _PROCESSORdef llm(model_path,prompt=None,image=None,video=None,images=None,videos=None,max_new_tokens=2048,temperature=0.6,parallel=False,max_workers=4,
):"""model_path: 模型路径prompt: 文本promptimage: 单张图片路径video: 单个视频路径images: 图片路径列表videos: 视频路径列表max_new_tokens: 最大生成token数temperature: 采样温度parallel: 是否并行处理多个图片/视频max_workers: 并行处理的最大工作线程数"""# 如果启用并行处理且有多个图片或视频if parallel and ((images and len(images) > 1) or (videos and len(videos) > 1)):return parallel_process(model_path=model_path,prompt=prompt,images=images,videos=videos,max_new_tokens=max_new_tokens,temperature=temperature,max_workers=max_workers)old_time = time.time()gpu_memories = []# 监控代码占用显存def monitor_gpu_memory():import torchwhile not getattr(monitor_gpu_memory, 'stop', False):mem = torch.cuda.memory_allocated() / 1024 / 1024  # MBgpu_memories.append(mem)time.sleep(5)# 加载模型和处理器model, processor = load_model_and_processor(model_path)# 启动显存监控线程monitor_gpu_memory.stop = Falsemem_thread = threading.Thread(target=monitor_gpu_memory)mem_thread.start()# 构建messagescontents = []if prompt is not None:contents.append({"type": "text", "text": prompt})if images is not None:for img in images:contents.append({"type": "image", "image": img})elif image is not None:contents.append({"type": "image", "image": image})elif videos is not None:for vid in videos:contents.append({"type": "video", "video": vid})elif video is not None:contents.append({"type": "video", "video": video})messages = [{"role": "user", "content": contents}]# 准备输入text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)image_inputs, video_inputs = process_vision_info(messages)inputs = processor(text=[text],images=image_inputs,videos=video_inputs,padding=True,return_tensors="pt",)inputs = inputs.to(model.device)# 推理generated_ids = model.generate(**inputs,max_new_tokens=max_new_tokens,temperature=temperature)generated_ids_trimmed = [out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]output_text = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False)# 停止显存监控线程monitor_gpu_memory.stop = Truemem_thread.join()total_time = time.time() - old_timetotal_tokens = sum(len(ids) for ids in generated_ids_trimmed)speed = total_tokens / total_time if total_time > 0 else 0avg_mem = sum(gpu_memories) / len(gpu_memories) if gpu_memories else 0return {"output_text": output_text,"total_time": total_time,"total_tokens": total_tokens,"speed": speed,"avg_mem": avg_mem,}def process_single_item(model_path: str,prompt: Optional[str],item_path: str,is_video: bool = False,max_new_tokens: int = 2048,temperature: float = 0.6,
) -> Dict[str, Any]:"""处理单个图片或视频"""# 确保模型已加载model, processor = load_model_and_processor(model_path)old_time = time.time()gpu_memories = []# 监控代码占用显存def monitor_gpu_memory():import torchwhile not getattr(monitor_gpu_memory, 'stop', False):mem = torch.cuda.memory_allocated() / 1024 / 1024  # MBgpu_memories.append(mem)time.sleep(5)# 启动显存监控线程monitor_gpu_memory.stop = Falsemem_thread = threading.Thread(target=monitor_gpu_memory)mem_thread.start()# 构建messagescontents = []if prompt is not None:contents.append({"type": "text", "text": prompt})if is_video:contents.append({"type": "video", "video": item_path})else:contents.append({"type": "image", "image": item_path})messages = [{"role": "user", "content": contents}]# 准备输入text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)image_inputs, video_inputs = process_vision_info(messages)# 使用处理器处理输入inputs = processor(text=[text],images=image_inputs,videos=video_inputs,padding=True,return_tensors="pt",)inputs = inputs.to(model.device)# 推理with _MODEL_LOCK:  # 在生成时加锁,确保一次只有一个线程使用模型generated_ids = model.generate(**inputs,max_new_tokens=max_new_tokens,temperature=temperature)generated_ids_trimmed = [out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]output_text = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False)# 停止显存监控线程monitor_gpu_memory.stop = Truemem_thread.join()total_time = time.time() - old_timetotal_tokens = sum(len(ids) for ids in generated_ids_trimmed)speed = total_tokens / total_time if total_time > 0 else 0avg_mem = sum(gpu_memories) / len(gpu_memories) if gpu_memories else 0return {"output_text": output_text,"total_time": total_time,"total_tokens": total_tokens,"speed": speed,"avg_mem": avg_mem,}def parallel_process(model_path: str,prompt: Optional[str] = None,images: Optional[List[str]] = None,videos: Optional[List[str]] = None,max_new_tokens: int = 2048,temperature: float = 0.6,max_workers: int = 4,
) -> Dict[str, Any]:"""并行处理多个图片或视频"""start_time = time.time()results = []# 预先加载模型,确保所有线程共享同一个模型实例load_model_and_processor(model_path)# 确定要处理的项目列表items = []is_video_flags = []if images:items.extend(images)is_video_flags.extend([False] * len(images))if videos:items.extend(videos)is_video_flags.extend([True] * len(videos))# 使用线程池并行处理with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:future_to_item = {executor.submit(process_single_item, model_path, prompt, item, is_video, max_new_tokens, temperature): (item, is_video) for item, is_video in zip(items, is_video_flags)}for future in concurrent.futures.as_completed(future_to_item):item, is_video = future_to_item[future]try:result = future.result()results.append(result)except Exception as e:print(f"处理 {'视频' if is_video else '图片'} {item} 时出错: {e}")# 合并结果all_output_texts = [result["output_text"] for result in results]total_time = time.time() - start_timetotal_tokens = sum(result["total_tokens"] for result in results)avg_speed = total_tokens / total_time if total_time > 0 else 0avg_mem = sum(result.get("avg_mem", 0) for result in results) / len(results) if results else 0return {"output_text": all_output_texts,"total_time": total_time,"total_tokens": total_tokens,"speed": avg_speed,"avg_mem": avg_mem,"individual_results": results}# 示例调用
if __name__ == "__main__":# https://huggingface.co/unsloth/Qwen2.5-VL-7B-Instruct-unsloth-bnb-4bitmodel_path = "/mnt/d/LLaMA-Factory/Qwen/Qwen2.5-VL-7B-Instruct-unsloth-bnb-4bit"# 多图片并行处理示例result = llm(model_path=model_path,prompt="识别图中文字,如果有表格等特殊格式需要保留原格式。不用解释和总结,直接输出识别结果。",images=[r"/mnt/c/Users/CJK/Desktop/1.png", r"/mnt/c/Users/CJK/Desktop/2.png", r"/mnt/c/Users/CJK/Desktop/3.png", r"/mnt/c/Users/CJK/Desktop/4.png", r"/mnt/c/Users/CJK/Desktop/5.png", r"/mnt/c/Users/CJK/Desktop/6.png", r"/mnt/c/Users/CJK/Desktop/7.png", r"/mnt/c/Users/CJK/Desktop/8.png"],max_new_tokens=2048,temperature=0.6,parallel=True,max_workers=8)print("并行处理结果:")for i, text in enumerate(result["output_text"]):print(f"图片 {i+1} 结果: {text}")print(f"总耗时: {result['total_time']:.2f}s, 生成token数: {result['total_tokens']}, 平均输出速度: {result['speed']:.2f} token/s, 平均占用显存: {result['avg_mem']:.2f} MB")

相关文章:

部署qwen2.5-VL-7B

简单串行执行 from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor from qwen_vl_utils import process_vision_info import torch, time, threadingdef llm(model_path,promptNone,imageNone,videoNone,imagesNone,videosNone,max_new_tokens2048,t…...

记录jdk8->jdk17 遇到的坑和解决方案

最近项目在升级jdk8->jdk17 springboot2->springboot3 顺序先升级业务服务,后升级组件服务。跟随迭代开发一起验证功能。 1. 使用parent pom 版本管理 spring相关组件的版本。 组件依赖低版本parent不变。 业务服务依赖高版本parent。 2. 修改maven jdk…...

vue3 uniapp vite 配置之定义指令

动态引入指令 // src/directives/index.js import trim from ./trim;const directives {trim, };export default {install(app) {console.log([✔] 自定义指令插件 install 触发了!);Object.entries(directives).forEach(([key, directive]) > {app.directive(…...

杰弗里·辛顿:深度学习教父

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 杰弗里辛顿:当坚持遇见突破,AI迎来新纪元 一、人物简介 杰弗…...

STM32蓝牙连接Android实现云端数据通信(电机控制-开源)

引言 基于 STM32F103C8T6 最小系统板完成电机控制。这个小项目采用 HAL 库方法实现,通过 CubeMAX 配置相关引脚,步进电机使用 28BYJ-48 (四相五线式步进电机),程序通过蓝牙连接手机 APP 端进行数据收发, OL…...

第一个Qt开发的OpenCV程序

OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 下载安装Qt:https://download.qt.io/archive/qt/5.14/5.14.2/qt-opensource-windows-x86-5.14.2.exe 下载安装OpenCV:https://opencv.org/releases/ 下载安装CMake:Downl…...

如何编写爬取网络上的视频文件

网络爬虫程序,可以爬取某些网站上的视频,音频,图片或其它文件,然后保存到本地电脑上; 有时在工作中非常有用,那在技术上如何进行爬取文件和保存到本地呢?下面以python语言为例,讲解p…...

TCP 如何在网络 “江湖” 立威建交?

一、特点: (一)面向连接 在进行数据传输之前,TCP 需要在发送方和接收方之间建立一条逻辑连接。这一过程类似于打电话,双方在通话前需要先拨号建立连接。建立连接的过程通过三次握手来完成,确保通信双方都…...

【小白训练日记——2025/4/15】

变化检测常用的性能指标 变化检测(Change Detection)的性能评估依赖于多种指标,每种指标从不同角度衡量模型的准确性。以下是常用的性能指标及其含义: 1. 混淆矩阵(Confusion Matrix) 定义:统…...

交叉熵在机器学习中的应用解析

文章目录 核心概念香农信息量(自信息)熵(Entropy)KL散度(Kullback-Leibler Divergence)交叉熵 在机器学习中的应用作为损失函数对于二分类(Binary Classification):对于多…...

ARM Cortex汇编指令

在ARM架构的MCU开发中,汇编指令集是底层编程的核心。以下是针对Cortex-M系列(如M0/M3/M4/M7/M85)的指令集体系、分类及查询方法的详细说明: 一、指令集体系与核心差异 1. 架构版本与指令集特性 处理器架构指令集特点典型应用场…...

数据结构——二叉树(中)

接上一篇,上一篇主要讲解了关于二叉树的基本知识,也是为了接下来讲解关于堆结构和链式二叉树结构打基础,其实无论是堆结构还是链式二叉树结构,都是二叉树的存储结构,那么今天这一篇主要讲解关于堆结构的实现与应用 堆…...

InnoDB的MVCC实现原理?MVCC如何实现不同事务隔离级别?MVCC优缺点?

概念 InnoDB的MVCC(Multi-Version Concurrency Control)即多版本并发控制,是一种用于处理并发事务的机制。它通过保存数据在不同时间点的多个版本,让不同事务在同一时刻可以看到不同版本的数据,以此来减少锁竞争&…...

UDP目标IP不存在时的发送行为分析

当网络程序使用UDP协议发送数据时,如果目标IP不存在,发送程序的行为取决于网络环境和操作系统的处理机制。以下是详细分析: 1. UDP的无连接特性 UDP是无连接的传输协议,发送方不会预先建立连接,也不会收到对方是否存在…...

WHAT - 动态导入模块遇到版本更新解决方案

文章目录 一、动态导入模块二、常见原因与解决方案1. 模块 URL 错误2. 开发人员发版用户停留在旧页面问题背景解决方案思路1. 监听错误,提示用户刷新2. 使用缓存控制策略:强制刷新3. 动态模块加载失败时兜底4. 使用 import.meta.glob() 或 webpack 的 __…...

02-MySQL 面试题-mk

文章目录 1.mysql 有哪些存储引擎、区别是什么?1.如何定位慢查询?2.SQL语句执行很慢,如何分析?3.索引概念以及索引底层的数据结构4.什么是聚簇索引什么是非聚簇索引?5.知道什么叫覆盖索引嘛 ?6.索引创建原则有哪些?7.什么情况下索引会失效 ?8.谈一谈你对sql的优化的经验…...

#include<bits/stdc++.h>

#include<bits/stdc.h> 是 C 中一个特殊的头文件&#xff0c;其作用如下&#xff1a; 核心作用 ​​包含所有标准库头文件​​ 该头文件会自动引入 C 标准库中的几乎全部头文件&#xff08;如 <iostream>、<vector>、<algorithm> 等&#xff09;&…...

PostgreSQL:逻辑复制与物理复制

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探…...

在企业级部署中如何优化NVIDIA GPU和容器环境配置:最佳实践与常见误区20250414

在企业级部署中如何优化NVIDIA GPU和容器环境配置&#xff1a;最佳实践与常见误区 引言 随着AI和深度学习技术的迅速发展&#xff0c;企业对GPU加速计算的需求愈加迫切。在此过程中&#xff0c;如何高效地配置宿主机与容器化环境&#xff0c;特别是利用NVIDIA GPU和相关工具&…...

iphone各个机型尺寸

以下是苹果&#xff08;Apple&#xff09;历代 iPhone 机型 的屏幕尺寸、分辨率及其他关键参数汇总&#xff08;截至 2023年10月&#xff0c;数据基于官方发布信息&#xff09;&#xff1a; 一、标准屏 iPhone&#xff08;非Pro系列&#xff09; 机型屏幕尺寸&#xff08;英寸…...

栈的学习笔记

使用数组实现一个栈 #include <stdio.h>#define MAX_SIZE 101int A[MAX_SIZE]; int top -1; //栈顶指针&#xff0c;初始为-1&#xff0c;表示栈为空 void push(int x) {if (top MAX_SIZE - 1){printf("栈已满&#xff0c;无法入栈\n");return;}A[top] x;…...

Spring Boot 项目三种打印日志的方法详解。Logger,log,logger 解读。

目录 一. 打印日志的常见三种方法&#xff1f; 1.1 手动创建 Logger 对象&#xff08;基于SLF4J API&#xff09; 1.2 使用 Lombok 插件的 Slf4j 注解 1.3 使用 Spring 的 Log 接口&#xff08;使用频率较低&#xff09; 二. 常见的 Logger&#xff0c;logger&#xff0c;…...

按键精灵安卓/ios脚本辅助工具开发教程:如何把界面配置保存到服务器

在使用按键精灵工具辅助的时候&#xff0c;多配置的情况下&#xff0c;如果保存现有的配置&#xff0c;并且读取&#xff0c;尤其是游戏中多种任务并行情况下&#xff0c;更是需要界面进行保存&#xff0c;简单分享来自紫猫插件的配置保存服务器写法。 界面例子&#xff1a; …...

[react]Next.js之自适应布局和高清屏幕适配解决方案

序言 阅读前首先了解即将要用到的两个包的作用 1.postcss-pxtorem 自动将 CSS 中的 px 单位转换为 rem 单位按照设计稿尺寸直接写 px 值&#xff0c;由插件自动计算 rem 值 2.amfe-flexible 动态设置根元素的 font-size&#xff08;即 1rem 的值&#xff09;根据设备屏幕宽度和…...

STM32H503CB升级BootLoader

首先&#xff0c;使用SWD接口&#xff0c;ST-LINK连接电脑和板子。 安装SetupSTM32CubeProgrammer_win64 版本2.19。 以下是接线和软件操作截图。...

在Apple Silicon上部署Spark-TTS:四大核心库的技术魔法解析!!!

在Apple Silicon上部署Spark-TTS&#xff1a;四大核心库的技术魔法解析 &#x1f680; &#xff08;M2芯片实测&#xff5c;Python 3.12.9PyTorch 2.6.0全流程解析&#xff09; 一、核心库功能全景图 &#x1f50d; 在Spark-TTS的部署过程中&#xff0c;pip install numpy li…...

VMWare 16 PRO 安装 Rocky8 并部署 MySQL8

VMWare 16 PRO 安装 Rocky8 并部署 MySQL8 一.Rocky OS 下载1.官网二.配置 Rocky1.创建新的虚拟机2.稍后安装系统3.选择系统模板4.设置名字和位置5.设置大小6.自定义硬件设置核心、运存和系统镜像7.完成三.启动安装1.上下键直接选择安装2.回车安装3.设置分区(默认即可)和 roo…...

cursor如何回退一键回退多个文件的修改

当我们使用 Cursor 写代码时&#xff0c;起初可能操作得很顺利&#xff0c;但某次更改或许会让代码变得面目全非。这时候如果没有使用 Git 该怎么办呢&#xff1f;别担心&#xff0c;Cursor 已经为我们考虑到了。 具体的操作如下&#xff1a; 当我们要取消某次操作时&#xf…...

基于RV1126开发板的口罩识别算法开发

1. 口罩识别简介 口罩识别是一种基于深度学习的判断人员有没有戴口罩的分类算法&#xff0c;能广泛的用于安防、生产安全等多种场景。本算法先基于人脸检测和人脸标准化获取的标准人脸&#xff0c;然后输入到口罩识别分类算法进行识别。 本人脸检测算法在数据集表现如下所示&am…...

PyCharm显示主菜单和工具栏

显示主菜单 新版 PyCharm 是不显示主菜单的&#xff0c;要想显示主菜单和工具栏&#xff0c;则通过 “视图” → “外观” &#xff0c;勾选 “在单独的工具栏中显示主菜单” 和 “工具栏” 即可。 设置工具栏 此时工具栏里并没有什么工具&#xff0c;因此我们需要自定义工具…...