關心Qwen/QwQ-32B 性能測試結果可以參考下
https://zhuanlan.zhihu.com/p/28600079208https://zhuanlan.zhihu.com/p/28600079208
官方宣傳上是該模型性能比肩滿血版?DeepSeek-R1(671B)!
我們實現一個 使用Qwen/QwQ-32B 自動生成 PowerPoint 演示文稿的需求,基于 Qwen/QwQ-32B 的自動化 PPT 生成 Agent,帶有一個簡單的用戶界面。以下是一個使用 Python、Gradio(用于界面)和 python-pptx(用于生成 PPT)的實現方案,先利用提示詞工程實現一個簡單的ppt生成
-
前提條件
- 你已經下載了 Qwen/QwQ-32B 權重到 ./qwq_32b 目錄。
- 確保有足夠的 GPU 內存(建議至少 24GB VRAM)來加載 32B 模型。
-
系統提示:
- 設計一個系統提示,指導模型如何生成內容。該提示應包含結構化的內容格式,例如:
- 第一張幻燈片為標題幻燈片(包含標題和副標題)。
- 后續幻燈片為內容幻燈片(包含標題和要點)。
- 設計一個系統提示,指導模型如何生成內容。該提示應包含結構化的內容格式,例如:
-
內容生成流程
-
消息構建:
- 將系統提示與用戶提示結合,形成完整的輸入消息列表。
-
文本生成:
- 使用分詞器將消息轉換為模型輸入格式,然后調用模型生成文本。
- 設置生成參數(如最大新令牌數、溫度等)來控制生成內容的質量和多樣性。
PPT內容處理
-
解析生成的內容:
- 將模型生成的文本分割為不同的幻燈片,識別標題幻燈片和內容幻燈片。
-
創建PPT文件:
- 使用
python-pptx
庫創建 PowerPoint 演示文稿,根據解析的內容添加幻燈片。
- 使用
- 保存PPT文件:
- 將生成的PPT文件保存到指定的位置,方便用戶下載和使用。
def generate_ppt_content(topic, slide_count=3):system_prompt = ("You are Qwen, developed by Alibaba. You are tasked with creating content for a PowerPoint presentation. ""Generate a structured response with a title slide and subsequent slides based on the user's topic. "f"Provide content for {slide_count} slides in the following format:\n""Slide 1: Title Slide (Title, Subtitle)\n"f"Slide 2 to {slide_count}: Content Slide (Slide Title, Bullet Points)\n""Keep each slide concise and professional.")user_prompt = f"Generate a PPT about '{topic}'."messages = [{"role": "system", "content": system_prompt},{"role": "user", "content": user_prompt}]text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)model_inputs = tokenizer([text], return_tensors="pt").to("cuda") # 輸入放到 GPUgenerated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512,temperature=0.7,repetition_penalty=1.2)generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids inzip(model_inputs.input_ids, generated_ids)]response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]return response
處理流程概述:
- 定義了一個結構化的提示,用于指示模型(Qwen)預期的輸出格式。它具體說明了輸出的格式要求,包括標題幻燈片和內容幻燈片。
- 這將系統提示與請求生成指定主題的演示文稿結合在一起。
- 將提示進行標記化,意味著將文本轉換為模型可以理解的格式。這涉及將文本轉換為數字表示(標記)。
- 將標記化的輸入準備好,移動到GPU以加快處理速度。
- 模型根據輸入提示生成響應。參數max_new_tokens、temperature和repetition_penalty決定生成的
- max_new_tokens:模型可以一次生成的最大標記數(這里設置為512)。
- temperature:控制輸出的隨機性。較低的值使輸出更確定,而較高的值增加創造性。
- repetition_penalty:抑制模型重復短語或想法,促進內容的多樣性。
將生成的標記ID解碼回人類可讀的文本格式,排除模型使用的任何特殊標記。
初步運行下報錯了
torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 270.00 MiB. GPU 0 has a total capacity of 23.53 GiB of which 158.56 MiB is free. Including non-PyTorch memory, this process has 23.36 GiB memory in use. Of the allocated memory 22.99 GiB is allocated by PyTorch, and 1.24 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)
機器有兩張 NVIDIA 4090 顯卡,每張顯存 24GB,但當前模型加載時超出了單張 GPU 的顯存容量(23.53GiB 已幾乎用盡)。由于 Qwen/QwQ-32B 是一個非常大的模型(未經量化可能需要 60GB+ 顯存),單張 24GB 顯卡無法完整加載,因此需要利用多卡并行加載模型。
以下是實現多卡運行的解決方案,結合你的硬件(2 張 4090,48GB 總顯存):
首先確認你的環境支持多卡:
import torch
print("CUDA available:", torch.cuda.is_available())
print("Device count:", torch.cuda.device_count())
for i in range(torch.cuda.device_count()):print(f"GPU {i}: {torch.cuda.get_device_name(i)}, {torch.cuda.get_device_properties(i).total_memory / 1024**3:.2f} GB")
使用 accelerate 的多卡支持
transformers 和 accelerate 提供了內置的多卡支持,通過 device_map="auto" 自動將模型分層分配到多個 GPU 上。
# 1. 加載模型和分詞器
model_path = "./qwq_32b"
tokenizer = AutoTokenizer.from_pretrained(model_path)# 使用 accelerate 的多卡加載
model = AutoModelForCausalLM.from_pretrained(model_path,torch_dtype=torch.float16, # 使用 FP16 減少顯存占用device_map="auto", # 自動分配到多張 GPUlow_cpu_mem_usage=True # 優化內存使用
)
print("Model loaded successfully across multiple GPUs!")
優化顯存使用
如果仍然遇到顯存不足,可以進一步優化:
1. 使用 4-bit 量化
安裝 bitsandbytes:
pip install bitsandbytes
修改模型加載部分:
model = AutoModelForCausalLM.from_pretrained(model_path,torch_dtype=torch.float16,device_map="auto",load_in_4bit=True, # 4-bit 量化bnb_4bit_compute_dtype=torch.float16,bnb_4bit_use_double_quant=True # 雙重量化
)
- 顯存需求降至約 20-24GB,兩張 4090 完全夠用。
- 可能略微影響生成質量,但對 PPT 內容生成影響不大。
設置環境變量
錯誤信息提到 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,可以嘗試設置以優化內存分配:
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True或者代碼上添加
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
修改后代碼
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import gradio as gr
from pptx import Presentation
import os# 1. 加載模型和分詞器
model_path = "./base_model/qwq_32b"
tokenizer = AutoTokenizer.from_pretrained(model_path)# 使用 accelerate 的多卡加載
model = AutoModelForCausalLM.from_pretrained(model_path,torch_dtype=torch.float16,device_map="auto",load_in_4bit=True, # 啟用 4-bit 量化bnb_4bit_compute_dtype=torch.float16, # 計算用 FP16bnb_4bit_use_double_quant=True # 雙重量化,進一步節省顯存
)
print("Model loaded successfully across multiple GPUs!")# 2. 生成 PPT 內容的函數
def generate_ppt_content(topic, slide_count=3):system_prompt = ("You are Qwen, developed by Alibaba. You are tasked with creating content for a PowerPoint presentation. ""Generate a structured response with a title slide and subsequent slides based on the user's topic. "f"Provide content for {slide_count} slides in the following format:\n""Slide 1: Title Slide (Title, Subtitle)\n"f"Slide 2 to {slide_count}: Content Slide (Slide Title, Bullet Points)\n""Keep each slide concise and professional.")user_prompt = f"Generate a PPT about '{topic}'."messages = [{"role": "system", "content": system_prompt},{"role": "user", "content": user_prompt}]text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)model_inputs = tokenizer([text], return_tensors="pt").to("cuda") # 輸入放到 GPUgenerated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512,temperature=0.7,repetition_penalty=1.2)generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids inzip(model_inputs.input_ids, generated_ids)]response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]return response# 3. 創建 PPT 文件
def create_ppt(topic, content):prs = Presentation()slides = content.split("Slide")[1:]for slide_text in slides:slide_lines = slide_text.strip().split("\n")if not slide_lines:continueif "1:" in slide_text:slide_layout = prs.slide_layouts[0]slide = prs.slides.add_slide(slide_layout)title, subtitle = slide.shapes.title, slide.placeholders[1]title.text = slide_lines[0].replace("1: Title Slide", "").strip()subtitle.text = slide_lines[1].strip() if len(slide_lines) > 1 else " "else:slide_layout = prs.slide_layouts[1]slide = prs.slides.add_slide(slide_layout)title, body = slide.shapes.title, slide.placeholders[1]title.text = slide_lines[0].strip()tf = body.text_frametf.text = "\n".join(line.strip() for line in slide_lines[1:] if line.strip())output_file = f"{topic.replace(' ', '_')}_presentation.pptx"prs.save(output_file)return output_file# 4. 主函數
def ppt_agent(topic):content = generate_ppt_content(topic)output_file = create_ppt(topic, content)return content, output_file# 5. Gradio 界面
with gr.Blocks(title="PPT 生成 Agent") as demo:gr.Markdown("# 自動化 PPT 生成器")gr.Markdown("輸入主題,生成 PPT 文件。基于 Qwen/QwQ-32B,多卡運行。")topic_input = gr.Textbox(label="PPT 主題", placeholder="例如:人工智能簡介")output_text = gr.Textbox(label="生成的內容", lines=10, interactive=False)output_file = gr.File(label="下載 PPT 文件")submit_btn = gr.Button("生成 PPT")submit_btn.click(fn=ppt_agent, inputs=topic_input, outputs=[output_text, output_file])demo.launch(server_name="0.0.0.0", server_port=7860)
運行界面如下
點擊“生成PPT”?
但是ppt質量有點堪憂
生成的 PPT 內容確實存在以下問題:
- 內容質量低:生成的文本包含不自然的表達(如 "I don’t have the prime data regarding from common knowledge up until now"),語法錯誤較多,邏輯也不夠清晰。
- 格式問題:內容沒有很好地適配 PPT 的結構,段落過長,缺乏簡潔的要點化表達。
- 語言混雜:中英文混雜(如標題是中文,內容是英文),缺乏統一性。
為了提升 PPT 的質量,引入多個智能體的協作模式,利用聯網功能獲取更高質量的內容,并優化 PPT 的結構和格式。改進方案:
多智能體協作生成高質量 PPT
智能體角色分工
- 內容生成智能體:基于 QwQ-32B 模型,負責生成 PPT 的初步內容(已實現)。
- 內容優化智能體:聯網搜索最新信息,修正生成內容中的錯誤,優化語言表達。
- 格式設計智能體:優化 PPT 結構,確保內容簡潔、要點清晰,并適配 PPT 格式。
- 視覺美化智能體:提供 PPT 排版建議,優化字體、顏色、布局等(目前無法直接生成圖片,但可以提供設計建議)。
改進步驟
- 優化內容生成:調整提示詞,提升 QwQ-32B 的輸出質量。
- 聯網搜索補充:搜索與主題相關的最新信息,修正模型生成的內容。
- 結構化輸出:將內容轉為簡潔的要點形式,適配 PPT。
修改代碼
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import gradio as gr
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
from pptx.oxml.xmlchemy import OxmlElement
import os
import requests
from bs4 import BeautifulSoup
import time# 清理顯存
torch.cuda.empty_cache()
os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"# 1. 加載模型和分詞器
model_path = "./base_model/qwq_32b"
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(model_path,torch_dtype=torch.float16,device_map="auto",load_in_4bit=True,bnb_4bit_compute_dtype=torch.float16,bnb_4bit_use_double_quant=True
)
print("Model loaded successfully!")# 2. 聯網搜索(使用百度,優化解析邏輯)
def search_web(query, retries=3):for attempt in range(retries):try:url = f"https://www.baidu.com/s?wd={requests.utils.quote(query)}"headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"}response = requests.get(url, headers=headers, timeout=10)response.raise_for_status()soup = BeautifulSoup(response.text, "html.parser")snippets = []# 調整選擇器,適配百度搜索結果for result in soup.select(".result.c-container"):title = result.select_one("h3 a")summary = result.select_one(".c-abstract") or result.select_one(".c-span-last")if title and summary:snippets.append(f"{title.get_text()}: {summary.get_text()}")elif title:snippets.append(title.get_text())if snippets:print("Search results:", snippets) # 調試輸出return "\n".join(snippets[:3])else:return "未找到相關搜索結果"except Exception as e:print(f"Search attempt {attempt + 1} failed: {str(e)}")if attempt == retries - 1:return "搜索失敗,請檢查網絡連接或稍后再試"time.sleep(2) # 重試前等待 2 秒# 3. 生成 PPT 內容
def generate_ppt_content(topic, slide_count=3):system_prompt = ("你是一個專業的 PPT 內容生成助手,語言為中文。你的任務是根據用戶提供的主題,生成一份結構化的 PPT 內容。要求:"f"1. 生成 {slide_count} 頁幻燈片:\n"" - Slide 1: 標題頁(標題,副標題)\n"f" - Slide 2 到 {slide_count}: 內容頁(頁標題,3-5 個簡潔的要點)\n""2. 語言簡潔、專業,適合 PPT 展示,避免長句。\n""3. 確保內容邏輯清晰,信息準確,基于小米汽車的最新信息(如 2025 年目標交付 30 萬臺,SU7 和 YU7 系列,智能化布局等)。\n""4. 不要包含不準確的信息(如錯誤的自動駕駛級別,當前小米汽車未達到 L4)。")user_prompt = f"生成一份關于 '{topic}' 的 PPT,重點介紹產品亮點和市場戰略。"messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}]text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)model_inputs = tokenizer([text], return_tensors="pt").to("cuda")generated_ids = model.generate(model_inputs.input_ids,max_new_tokens=256,temperature=0.7,repetition_penalty=1.2,pad_token_id=tokenizer.eos_token_id)generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids inzip(model_inputs.input_ids, generated_ids)]response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]# 聯網搜索補充信息search_result = search_web(f"{topic} 2025")return response + "\n\n補充信息(來自百度):\n" + search_result# 4. 創建 PPT 文件(優化美觀度)
def create_ppt(topic, content):prs = Presentation()print("Generated content:\n", content)slides = content.split("Slide")[1:]for slide_index, slide_text in enumerate(slides):slide_lines = slide_text.strip().split("\n")if not slide_lines:continue# 設置背景漸變(淺灰色到白色)slide_layout = prs.slide_layouts[0 if "1:" in slide_text else 1]slide = prs.slides.add_slide(slide_layout)background = slide.backgroundfill = background.fillfill.gradient()fill.gradient_stops[0].color.rgb = RGBColor(240, 240, 240)fill.gradient_stops[1].color.rgb = RGBColor(255, 255, 255)# 添加占位符圖片框(右側)left = Inches(6)top = Inches(1.5)width = Inches(3)height = Inches(3)slide.shapes.add_picture_placeholder(left=left, top=top, width=width, height=height)if "1:" in slide_text:# 標題頁title, subtitle = slide.shapes.title, slide.placeholders[1]title.text = slide_lines[0].replace("1: Title Slide", "").strip()subtitle.text = slide_lines[1].strip() if len(slide_lines) > 1 else " "# 美化標題頁(使用小米品牌色:橙色)title.text_frame.paragraphs[0].font.size = Pt(44)title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0) # 小米橙色title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTERsubtitle.text_frame.paragraphs[0].font.size = Pt(24)subtitle.text_frame.paragraphs[0].font.color.rgb = RGBColor(89, 89, 89)subtitle.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTERelse:# 內容頁title, body = slide.shapes.title, slide.placeholders[1]title.text = slide_lines[0].strip()# 格式化內容為要點tf = body.text_frametf.clear()for line in slide_lines[1:]:if line.strip():p = tf.add_paragraph()p.text = line.strip()p.level = 0 if not line.startswith("-") else 1p.font.size = Pt(20)p.font.color.rgb = RGBColor(0, 0, 0)p.alignment = PP_ALIGN.LEFT# 美化內容頁title.text_frame.paragraphs[0].font.size = Pt(32)title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0) # 小米橙色# 在最后一頁添加補充信息if slide_index == len(slides) - 1 and "補充信息(來自百度)" in content:search_content = content.split("補充信息(來自百度):\n")[-1].strip()if search_content and search_content != "未找到相關搜索結果":left = Inches(0.5)top = Inches(5.5)width = Inches(9)height = Inches(1.5)textbox = slide.shapes.add_textbox(left, top, width, height)tf = textbox.text_frametf.text = "補充信息(來自百度):\n" + search_contentfor p in tf.paragraphs:p.font.size = Pt(14)p.font.color.rgb = RGBColor(100, 100, 100)p.alignment = PP_ALIGN.LEFToutput_file = f"{topic.replace(' ', '_')}_presentation.pptx"prs.save(output_file)return output_file# 5. 主函數
def ppt_agent(topic):content = generate_ppt_content(topic)output_file = create_ppt(topic, content)return content, output_file# 6. Gradio 界面
with gr.Blocks(title="PPT 生成 Agent") as demo:gr.Markdown("# 自動化 PPT 生成器")gr.Markdown("輸入主題,生成高質量 PPT 文件。基于 Qwen/QwQ-32B 和百度聯網優化。")topic_input = gr.Textbox(label="PPT 主題", placeholder="例如:小米汽車最新發布")output_text = gr.Textbox(label="生成的內容", lines=10, interactive=False)output_file = gr.File(label="下載 PPT 文件")submit_btn = gr.Button("生成 PPT")submit_btn.click(fn=ppt_agent, inputs=topic_input, outputs=[output_text, output_file])demo.launch(server_name="0.0.0.0", server_port=7860)
我們再嘗使用添加些樣式來豐富下ppt排班
修改代碼
def create_ppt(topic, content):prs = Presentation()print("Generated content:\n", content)slides = content.split("Slide")[1:]for slide_index, slide_text in enumerate(slides):slide_lines = slide_text.strip().split("\n")if not slide_lines:continue# 設置背景漸變(淺灰色到白色)slide_layout = prs.slide_layouts[0 if "1:" in slide_text else 1]slide = prs.slides.add_slide(slide_layout)background = slide.backgroundfill = background.fillfill.gradient()fill.gradient_stops[0].color.rgb = RGBColor(240, 240, 240)fill.gradient_stops[1].color.rgb = RGBColor(255, 255, 255)# 添加矩形框作為圖片占位符left = Inches(6)top = Inches(1.5)width = Inches(3)height = Inches(3)shape = slide.shapes.add_shape(1, left, top, width, height) # 1 表示矩形shape.fill.solid()shape.fill.fore_color.rgb = RGBColor(220, 220, 220)shape.line.dash_style = MSO_LINE_DASH_STYLE.DASH # 使用正確的虛線樣式shape.line.color.rgb = RGBColor(150, 150, 150)txBox = shape.text_frametxBox.text = "圖片占位符\n(請手動插入圖片)"p = txBox.paragraphs[0]p.font.size = Pt(12)p.font.color.rgb = RGBColor(100, 100, 100)p.alignment = PP_ALIGN.CENTERif "1:" in slide_text:# 標題頁title, subtitle = slide.shapes.title, slide.placeholders[1]title.text = slide_lines[0].replace("1: Title Slide", "").strip()subtitle.text = slide_lines[1].strip() if len(slide_lines) > 1 else " "# 美化標題頁(使用小米品牌色:橙色)title.text_frame.paragraphs[0].font.size = Pt(44)title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0)title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTERsubtitle.text_frame.paragraphs[0].font.size = Pt(24)subtitle.text_frame.paragraphs[0].font.color.rgb = RGBColor(89, 89, 89)subtitle.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTERelse:# 內容頁title, body = slide.shapes.title, slide.placeholders[1]title.text = slide_lines[0].strip()# 格式化內容為要點tf = body.text_frametf.clear()for line in slide_lines[1:]:if line.strip():p = tf.add_paragraph()p.text = line.strip()p.level = 0 if not line.startswith("-") else 1p.font.size = Pt(20)p.font.color.rgb = RGBColor(0, 0, 0)p.alignment = PP_ALIGN.LEFT# 美化內容頁title.text_frame.paragraphs[0].font.size = Pt(32)title.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 103, 0)# 在最后一頁添加補充信息和分割線if slide_index == len(slides) - 1 and "補充信息(來自百度)" in content:# 添加分割線left = Inches(0.5)top = Inches(5.3)width = Inches(9)height = Inches(0.01)line = slide.shapes.add_shape(1, left, top, width, height)line.fill.solid()line.fill.fore_color.rgb = RGBColor(200, 200, 200)line.line.color.rgb = RGBColor(200, 200, 200)# 添加補充信息search_content = content.split("補充信息(來自百度):\n")[-1].strip()if search_content and search_content != "未找到相關搜索結果":left = Inches(0.5)top = Inches(5.5)width = Inches(9)height = Inches(1.5)textbox = slide.shapes.add_textbox(left, top, width, height)tf = textbox.text_frametf.text = "補充信息(來自百度):\n" + search_contentfor p in tf.paragraphs:p.font.size = Pt(14)p.font.color.rgb = RGBColor(100, 100, 100)p.alignment = PP_ALIGN.LEFToutput_file = f"{topic.replace(' ', '_')}_presentation.pptx"prs.save(output_file)return output_file
再次運行,控制臺和新ppt