mirror of
https://github.com/datawhalechina/self-llm.git
synced 2026-08-29 07:31:13 +08:00
1. 简化requirements.txt内容,仅保留了几个必需手动安装的库,删除了其他依赖库的版本。
2. 修改md教程,简化了安装流程。去掉了conda的部分内容,将环境和制作的镜像替换为pytorch2.3版本。 3. 代码文件添加了较为详细的注释,没有给每个函数的参数添加文档注释,但是做了尽量详细的行注释。
This commit is contained in:
@@ -15,19 +15,20 @@ from transformers import (
|
||||
GenerationConfig,
|
||||
)
|
||||
from transformers.utils import is_flash_attn_2_available
|
||||
from transformers.modeling_utils import get_first_parameter_dtype
|
||||
from accelerate import init_empty_weights
|
||||
from accelerate.utils import calculate_maximum_sizes, convert_bytes
|
||||
from accelerate.commands.estimate import create_ascii_table
|
||||
|
||||
# copy from qwen_vl_utils.process_vision_info
|
||||
MIN_PIXELS = 4 * 28 * 28
|
||||
MAX_PIXELS = 16384 * 28 * 28
|
||||
VIDEO_MIN_PIXELS = 128 * 28 * 28
|
||||
VIDEO_MAX_PIXELS = 768 * 28 * 28
|
||||
VIDEO_TOTAL_PIXELS = 24576 * 28 * 28
|
||||
MIN_PIXELS = 4 * 28 * 28 # 一张图最小占4个token
|
||||
MAX_PIXELS = 16384 * 28 * 28 # 一张图最大占16384个token
|
||||
VIDEO_MIN_PIXELS = 128 * 28 * 28 # 一个视频里一帧最小占128个token
|
||||
VIDEO_MAX_PIXELS = 768 * 28 * 28 # 一个视频里一帧最大占768个token
|
||||
VIDEO_TOTAL_PIXELS = 24576 * 28 * 28 # 一个视频里所有帧总共占最多24576个token
|
||||
|
||||
# default
|
||||
DEFAULT_CKPT_PATH = "./Qwen2-VL-2B-Instruct"
|
||||
DEFAULT_CKPT_PATH = "path/to/Qwen2-VL-2B-Instruct"
|
||||
VIDEO_EXTENSIONS = [
|
||||
".mp4",
|
||||
".avi",
|
||||
@@ -39,38 +40,47 @@ VIDEO_EXTENSIONS = [
|
||||
".mpeg",
|
||||
]
|
||||
IMAGE_EXTENSIONS = [".png", ".jpg"]
|
||||
|
||||
print(f"单张图片最大/最小token长度限制:{MAX_PIXELS//28*28}/{MIN_PIXELS//28*28}")
|
||||
# end default
|
||||
print("*" * 60)
|
||||
print("*Qwen2-vl 图片视频模态token限制如下:")
|
||||
print(f"*单张图片最大/最小token长度限制:{MAX_PIXELS//(28*28)}/{MIN_PIXELS//(28*28)}")
|
||||
print(
|
||||
f"单个视频最大/最小/总token长度限制:{VIDEO_MAX_PIXELS//28*28}/{VIDEO_MIN_PIXELS//28*28}/{VIDEO_TOTAL_PIXELS//28*28}"
|
||||
f"*单个视频最大/最小/总token长度限制:{VIDEO_MAX_PIXELS//(28*28)}/{VIDEO_MIN_PIXELS//(28*28)}/{VIDEO_TOTAL_PIXELS//(28*28)}"
|
||||
)
|
||||
print("*" * 60, end="\n\n")
|
||||
|
||||
|
||||
# modify from https://github.com/huggingface/accelerate/blob/c0552c9012a9bae7f125e1df89cf9ee0b0d250fd/src/accelerate/commands/estimate.py#L285
|
||||
def cal_model_size(args):
|
||||
"""计算模型在各种数据类型下的存储占用"""
|
||||
# modify from https://github.com/huggingface/accelerate/blob/c0552c9012a9bae7f125e1df89cf9ee0b0d250fd/src/accelerate/commands/estimate.py#L285
|
||||
"""计算模型在各种数据类型下的存储占用
|
||||
主要计算方法是
|
||||
借助calculate_maximum_sizes函数计算所有参数数量在特定下的存储->float32,float16,int8,int4分别进行进一步乘除即可.
|
||||
convert_bytes: 将计算结果转为不超过1024的TB/GB/MB/KB等单位下的结果表示.
|
||||
"""
|
||||
model_name = Path(args.model_path).name
|
||||
model_path = Path(args.model_path).as_posix()
|
||||
# 空加载模型, 可以几乎免去对存储空间的占用, 只记录每层有几个参数, 而不实际去申请内存初始化这些参数, 在加载大模型时有很多好处, 比如这里用来计算模型存储空间的占用, 毕竟加载一次大模型还是挺费时间的~
|
||||
with init_empty_weights():
|
||||
model = Qwen2VLForConditionalGeneration.from_pretrained(
|
||||
model_path, torch_dtype="auto"
|
||||
)
|
||||
) # 这里auto时会加载bfloat16格式,占用和float16一致
|
||||
total_size, largest_layer = calculate_maximum_sizes(model)
|
||||
data = []
|
||||
|
||||
for dtype in ["float32", "float16", "int8", "int4"]:
|
||||
dtype_total_size = total_size
|
||||
dtype_largest_layer = largest_layer[0]
|
||||
# dtype_training_size = estimate_training_usage(dtype_total_size, dtype) # buxuyao
|
||||
if dtype == "float16":
|
||||
if dtype == "float32":
|
||||
dtype_total_size *= 2
|
||||
dtype_largest_layer *= 2
|
||||
elif dtype == "float16":
|
||||
pass
|
||||
elif dtype == "int8":
|
||||
dtype_total_size /= 2
|
||||
dtype_largest_layer /= 2
|
||||
elif dtype == "int8":
|
||||
elif dtype == "int4":
|
||||
dtype_total_size /= 4
|
||||
dtype_largest_layer /= 4
|
||||
elif dtype == "int4":
|
||||
dtype_total_size /= 8
|
||||
dtype_largest_layer /= 8
|
||||
row = [dtype, dtype_largest_layer, dtype_total_size]
|
||||
for i, item in enumerate(row):
|
||||
if isinstance(item, (int, float)):
|
||||
@@ -89,6 +99,7 @@ def cal_model_size(args):
|
||||
|
||||
|
||||
def _get_args() -> Namespace:
|
||||
"""命令行参数解析为命名空间(可以看作可以用.来访问的字典)"""
|
||||
parser = ArgumentParser()
|
||||
|
||||
parser.add_argument(
|
||||
@@ -130,7 +141,12 @@ def _get_args() -> Namespace:
|
||||
|
||||
|
||||
class LazyModelLoader:
|
||||
"""延迟加载模型以达到快速显示页面的目的"""
|
||||
"""延迟加载模型以达到快速显示页面的目的
|
||||
|
||||
延迟加载需要用到多线程,主线程执行web页面的时候,用子线程去加载模型,只需要记录好模型的引用对象即可.
|
||||
(利用延迟加载,主线程中不加载而是放到子线程中,这样而等到页面渲染好,
|
||||
用户输入完提问后,取出模型做推理时,子线程已经加载好模型.)
|
||||
"""
|
||||
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
@@ -145,6 +161,9 @@ class LazyModelLoader:
|
||||
print(f"Loading model: {self.args.model_path}")
|
||||
try:
|
||||
model, proc = self._load_model_processor()
|
||||
# model不一定是存有dtype变量的nn.Module类,
|
||||
# 因此可以用这个函数来快速获取里面第一个参数的dtype。
|
||||
dtype = get_first_parameter_dtype(model)
|
||||
except Exception:
|
||||
self.lock.release()
|
||||
import traceback
|
||||
@@ -154,11 +173,17 @@ class LazyModelLoader:
|
||||
self.model = model
|
||||
self.proc = proc
|
||||
print(f"Model {self.args.model_path} loaded")
|
||||
print(f"{model.device=}")
|
||||
print(f"{model.device=} model.dtype={dtype}")
|
||||
|
||||
def _load_model_processor(
|
||||
self,
|
||||
) -> tuple[Qwen2VLForConditionalGeneration, Qwen2VLProcessor]:
|
||||
"""Qwen2-vl 加载模型时需要加载两个东西:
|
||||
1. 模型, 对应Qwen2VLForConditionalGeneration类
|
||||
2. processor(一个对图片和文本进行处理,转换为模型输入的预处理工具),对应AutoProcessor类
|
||||
|
||||
借助from_pretrained方法,我们可以在加载模型,预处理器时自动处理某些步骤(比如一般加载模型的流程是:初始化->从文件中加载权重并复制到初始化后的类中)而直接返回结果.
|
||||
"""
|
||||
args = self.args
|
||||
device_map = "cpu" if args.cpu else "auto"
|
||||
use_fa2 = (
|
||||
@@ -208,7 +233,33 @@ def _transform_messages(
|
||||
user_tag="user",
|
||||
assistant_tag="assistant",
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""gradio的messages格式与qwen2的conversation不一致,需要转换"""
|
||||
"""gradio的messages格式与qwen2的conversation不一致,需要转换
|
||||
|
||||
模型的问答是按轮次来划分的:
|
||||
第一轮: <提问>-><回答>-> 第二轮: <提问>-><回答>-> ...
|
||||
(即便是加入文件,也是放在提问里面.)
|
||||
具体来说:
|
||||
1. gradio目前有多种`对话`的处理格式, 本代码中采用的格式为:
|
||||
[
|
||||
[(<文件1>,<文件2>, ...), None], # 如果传入文件,那么没有对应回答,如果这一行是文件,那么下一行跟用户提问
|
||||
[<提问>, <回答>], # 注意,和上面的区别是提问是一个字符串,而上一行同样位置是一个存储文件的tuple.
|
||||
[("xxx1.jpg","xxx2.jpg"), None],
|
||||
["描述下这两张图片", "这张图片xxx"],
|
||||
...
|
||||
]
|
||||
2. Qwen中的格式采用:
|
||||
[
|
||||
# 这里角色可以包括: system, user, assistant, 内容则是对应角色的提问或回答.
|
||||
{"role":<角色>, "content":<内容>},
|
||||
# 针对图片和视频的传输, Qwen2-vl 在 user 的 <内容> 部分会进一步处理, 因此我们可以将这两类文件放到其 <内容> 中:
|
||||
{"role":"user", "content":"你是谁?"}, # 纯文字
|
||||
{"role":"user", "content":[{"type":"image", "image": "xxx.jpg"}, {"type":"text", "text": "这张图里有什么?"}]}, # 图片+文字
|
||||
{"role":"user", "content":[{"type":"video", "video": "xxx.mp4"}, {"type":"text", "text": "这个视频讲了什么?"}]}, # 视频+文字
|
||||
...
|
||||
]
|
||||
(值得注意的是, 对视频或图片的token限制也可以加在content里面. 可以参考下面的处理)
|
||||
3. 发现了吗,上面两种对话格式不统一,因此送入模型的预处理器前还需要做一次处理,将gradio格式转为qwen预处理支持的格式.而gradio中文件和提问是放在多个列表里的,对话轮次的切换仅通过回答是否是None来判断.
|
||||
"""
|
||||
transformed_messages = [{"role": user_tag, "content": []}]
|
||||
for message in messages:
|
||||
q = message[0]
|
||||
@@ -237,7 +288,7 @@ def _transform_messages(
|
||||
else:
|
||||
transformed_messages[-1]["content"] = q
|
||||
|
||||
if message[1]:
|
||||
if message[1]: # 如果回答里有值,说明当前轮对话完成,接下来做下一轮对话的处理。
|
||||
transformed_messages.extend(
|
||||
[
|
||||
{"role": assistant_tag, "content": message[1]},
|
||||
@@ -255,7 +306,7 @@ def _gc():
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
|
||||
# modify from https://github.com/gradio-app/gradio/blob/4e1f7dbcb2ea2a0cc29bb76faf5758a9f4afcd6d/demo/chatbot_examples/run.py#L1
|
||||
# modify from https://github.com/gradio-app/gradio/blob/4e1f7dbcb2ea2a0cc29bb76faf5758a9f4afcd6d/demo/chatbot_examples/run.py#L1, 参考这里可以看到gradio给出的带文件传输的chatbot实现
|
||||
def print_like_dislike(x: gr.LikeData) -> None:
|
||||
print(f"{x.index=} {x.value=}{x.liked=}")
|
||||
|
||||
@@ -263,10 +314,17 @@ def print_like_dislike(x: gr.LikeData) -> None:
|
||||
def add_message(
|
||||
history: List[List[str | Tuple[str, ...]]], message: Dict
|
||||
) -> tuple[List[List[str | Tuple[str, ...]]], gr.MultimodalTextbox]:
|
||||
"""
|
||||
Params:
|
||||
history: gradio的一种对话格式, 可以参考 `_transform_messages` 的文档注释.
|
||||
message: gr.MultimodalTextbox类, 可以当作字典访问,里面有file和text,分别表示提供的文件和提问.
|
||||
"""
|
||||
for x in message["files"]:
|
||||
history.append(((x,), None))
|
||||
if message["text"] is not None:
|
||||
history.append((message["text"], None))
|
||||
history.append(
|
||||
(message["text"], None)
|
||||
) # 这里填空是因为还需要把history数据转换后给模型进行回复,然后才能赋值到这里。
|
||||
return history, gr.MultimodalTextbox(value=None, interactive=False)
|
||||
|
||||
|
||||
@@ -278,12 +336,19 @@ def _pred(
|
||||
processor: Qwen2VLProcessor,
|
||||
model: Qwen2VLForConditionalGeneration,
|
||||
):
|
||||
"""模型对话的主要逻辑, 这段代码参考了Qwen2-vl官方的 web demo的一部分流程.
|
||||
先转换出qwen2-vl需要的格式
|
||||
然后将文本和图像/视频分别送入预处理器(在此之前,图像/视频要借助官方提供的process_vision_info函数resize为28*28的倍数)
|
||||
然后送入模型进行推理,模型推理的结果作为回答."""
|
||||
messages = _transform_messages(messages)
|
||||
|
||||
# 这里首先把messages对话格式转为纯文本的特殊格式
|
||||
text = processor.apply_chat_template(
|
||||
messages, tokenize=False, add_generation_prompt=True
|
||||
)
|
||||
# 这里对图片/视频做resize处理,主要是模型内视觉层对图片的宽高有特定限制。
|
||||
image_inputs, video_inputs = process_vision_info(messages)
|
||||
# 开始通过预处理器, 将文本和图片/视频作为输入, 处理出模型需要的数据: token_id列表 和 特定形状的一堆像素点
|
||||
inputs = processor(
|
||||
text=[text],
|
||||
images=image_inputs,
|
||||
@@ -291,18 +356,25 @@ def _pred(
|
||||
padding=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
inputs = inputs.to(model.device)
|
||||
inputs = inputs.to(model.device) # 结果送入模型所在的设备(CPU或某GPU卡)
|
||||
|
||||
streamer = TextIteratorStreamer(
|
||||
processor.tokenizer, timeout=20.0, skip_prompt=True, skip_special_tokens=True
|
||||
)
|
||||
) # 借助TextIteratorStreamer可以提供一个流式的接口,是的模型每生成一个token就返回这个token对应的文本。
|
||||
|
||||
# 模型在生成token前有一个后处理,这里简单介绍贪心解码和采样解码:
|
||||
# 当使用贪心解码时,设置do_sample = False, 对于下一个token,模型总会选择预测的概率最大的那个。
|
||||
# 当使用采样时,字如其名,就是随机的选择。首先会对输出的下一个token的概率分布做一些简单变换(比如temperature越大,可以让概率分布越平均, topK和topP则减小待采样的词表),然后对剩余的词表进行加权的随机选择(因为加权,所以概率大的还是有大的机率被选中,但是如果temperature设置过大,反而把剩余所有词表的概率平均化了,这样大家的权重都接近1:1)
|
||||
# 因此也可以说,temperature控制模型的创造性,越大,模型采样到不同词的可能越大,模型的回答便越发散。
|
||||
_gen_kwargs = (
|
||||
dict(temperature=temperature, top_p=topp, top_k=topk)
|
||||
if temperature
|
||||
else dict(do_sample=False)
|
||||
)
|
||||
# max_new_tokens主要限制模型回答的最大token长度,当超过这个token就会停止。
|
||||
gen_config = GenerationConfig(max_new_tokens=512, **_gen_kwargs)
|
||||
|
||||
# 使用子线程启动模型的推理,结果会自动添加到streamer接口中。
|
||||
thread = Thread(
|
||||
target=model.generate,
|
||||
kwargs=dict(
|
||||
@@ -322,10 +394,12 @@ def bot(
|
||||
topk: int,
|
||||
topp: float,
|
||||
) -> Generator[List[List[str | Tuple[str, ...]]], Any, None]:
|
||||
_gc()
|
||||
"""这里是输入提问并点击提交后触发回答的逻辑"""
|
||||
_gc() # 可以清除一下上一次回答的存储碎片
|
||||
# 然后将提问与之前轮次的对话送入_pred让模型针对这些上文进行推理
|
||||
model, proc = loader.get_model(), loader.get_processor()
|
||||
# 这里会返回一个流式的接口,通过for循环即可获取接口里新添加进去的回答,然后拼接到history里流式的返回给gradio即可.
|
||||
stream = _pred(history, temperature, topk, topp, processor=proc, model=model)
|
||||
# stream = "Test"
|
||||
history[-1][1] = ""
|
||||
for it in stream:
|
||||
history[-1][1] += it
|
||||
@@ -333,6 +407,7 @@ def bot(
|
||||
|
||||
|
||||
def web_demo(args: Namespace):
|
||||
"""创建gradio应用程序"""
|
||||
with gr.Blocks(fill_height=True) as demo:
|
||||
with gr.Column(scale=6):
|
||||
chatbot = gr.Chatbot(
|
||||
@@ -350,9 +425,11 @@ def web_demo(args: Namespace):
|
||||
)
|
||||
with gr.Column(scale=1):
|
||||
with gr.Accordion("Gen Config", open=False):
|
||||
# 一个隐藏的选项,可以控制 Temperature、top p、top k
|
||||
temperature = gr.Slider(0.0, 1.0, step=0.01, label="Temperature")
|
||||
topk = gr.Slider(-1, 1000, step=2, label="Top K") # need?
|
||||
topp = gr.Slider(0.0, 1.0, step=0.01, label="Top P") # need?
|
||||
# 多模态的输入会先调用 add_message,然后调用 bot,最后清除输入框中的内容(因为已经显示在chatbot里了)
|
||||
chat_msg = chat_input.submit(
|
||||
add_message, [chatbot, chat_input], [chatbot, chat_input]
|
||||
)
|
||||
@@ -361,19 +438,22 @@ def web_demo(args: Namespace):
|
||||
)
|
||||
bot_msg.then(lambda: gr.MultimodalTextbox(interactive=True), None, [chat_input])
|
||||
|
||||
# 这里主要是给chatbot的每个回答绑定一个用户偏好反馈的结果打印
|
||||
chatbot.like(print_like_dislike, None, None)
|
||||
demo.launch(max_threads=2, server_name=args.host, server_port=args.port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = _get_args()
|
||||
# 在这里检测flash-attn是否安装和启用
|
||||
print("flash-attn 已安装" if is_flash_attn_2_available() else "flash-attn 未安装")
|
||||
print(
|
||||
"flash-attn 已安装。"
|
||||
"flash-attn 已启用。"
|
||||
if args.flash_attn2 and is_flash_attn_2_available()
|
||||
else "flash-attn 未安装。"
|
||||
else "flash-attn 未启用。"
|
||||
)
|
||||
cal_model_size(args)
|
||||
cal_model_size(args) # 在每次启动模型时会先显示模型占用
|
||||
if args.cal_size is False:
|
||||
loader = LazyModelLoader(args)
|
||||
loader.get_model()
|
||||
web_demo(args)
|
||||
loader.get_model() # 在这里手动提前触发一下模型加载
|
||||
web_demo(args) # 运行web demo
|
||||
|
||||
@@ -1,84 +1,10 @@
|
||||
# # requirements.txt
|
||||
accelerate==1.1.0
|
||||
aiofiles==23.2.1
|
||||
annotated-types==0.7.0
|
||||
anyio==4.6.2.post1
|
||||
av==13.1.0
|
||||
certifi==2024.8.30
|
||||
charset-normalizer==3.4.0
|
||||
click==8.1.7
|
||||
exceptiongroup==1.2.2
|
||||
fastapi==0.115.5
|
||||
ffmpy==0.4.0
|
||||
filelock==3.16.1
|
||||
fsspec==2024.10.0
|
||||
gradio==5.5.0
|
||||
gradio_client==1.4.2
|
||||
h11==0.14.0
|
||||
httpcore==1.0.6
|
||||
httpx==0.27.2
|
||||
huggingface-hub==0.26.2
|
||||
idna==3.10
|
||||
Jinja2==3.1.4
|
||||
markdown-it-py==3.0.0
|
||||
MarkupSafe==2.1.5
|
||||
mdurl==0.1.2
|
||||
mpmath==1.3.0
|
||||
networkx==3.4.2
|
||||
numpy==2.1.3
|
||||
nvidia-cublas-cu12==12.4.5.8
|
||||
nvidia-cuda-cupti-cu12==12.4.127
|
||||
nvidia-cuda-nvrtc-cu12==12.4.127
|
||||
nvidia-cuda-runtime-cu12==12.4.127
|
||||
nvidia-cudnn-cu12==9.1.0.70
|
||||
nvidia-cufft-cu12==11.2.1.3
|
||||
nvidia-curand-cu12==10.3.5.147
|
||||
nvidia-cusolver-cu12==11.6.1.9
|
||||
nvidia-cusparse-cu12==12.3.1.170
|
||||
nvidia-nccl-cu12==2.21.5
|
||||
nvidia-nvjitlink-cu12==12.4.127
|
||||
nvidia-nvtx-cu12==12.4.127
|
||||
orjson==3.10.11
|
||||
packaging==24.2
|
||||
pandas==2.2.3
|
||||
pillow==11.0.0
|
||||
psutil==6.1.0
|
||||
pydantic==2.9.2
|
||||
pydantic_core==2.23.4
|
||||
pydub==0.25.1
|
||||
Pygments==2.18.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-multipart==0.0.12
|
||||
pytz==2024.2
|
||||
PyYAML==6.0.2
|
||||
qwen-vl-utils==0.0.8
|
||||
regex==2024.11.6
|
||||
requests==2.32.3
|
||||
rich==13.9.4
|
||||
ruff==0.7.3
|
||||
safehttpx==0.1.1
|
||||
safetensors==0.4.5
|
||||
semantic-version==2.10.0
|
||||
shellingham==1.5.4
|
||||
six==1.16.0
|
||||
sniffio==1.3.1
|
||||
starlette==0.41.2
|
||||
sympy==1.13.1
|
||||
tokenizers==0.20.3
|
||||
tomlkit==0.12.0
|
||||
torch==2.5.1
|
||||
torchvision==0.20.1
|
||||
tqdm==4.67.0
|
||||
# requirements.txt
|
||||
qwen_vl_utils==0.0.8
|
||||
transformers==4.46.2
|
||||
triton==3.1.0
|
||||
typer==0.13.0
|
||||
typing_extensions==4.12.2
|
||||
tzdata==2024.2
|
||||
urllib3==2.2.3
|
||||
uvicorn==0.32.0
|
||||
websockets==12.0
|
||||
accelerate==1.1.1
|
||||
gradio==5.5.0
|
||||
torchvision==0.19.0
|
||||
modelscope==1.20.0
|
||||
# # 如果安装了flash-attn,则会多出这两个库
|
||||
# einops==0.8.0
|
||||
# flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.0.post2/flash_attn-2.7.0.post2+cu11torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl
|
||||
# # 本底导出会显示下面这个样式,实际上要确认对应python、pytorch、cuda版本后下载下载上面这个
|
||||
# # flash-attn @ file:///root/flash_attn-2.7.0.post2%2Bcu11torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl#sha256=7ccce59f987f422d8210587383914057cf7db219988427ef71287e89153ad2fa
|
||||
# flash-attn==2.7.0
|
||||
@@ -8,40 +8,35 @@
|
||||
ubuntu 22.04
|
||||
python 3.10
|
||||
cuda 11.8
|
||||
pytorch 2.3.0
|
||||
----------------
|
||||
```
|
||||
|
||||
# 环境安装
|
||||
|
||||
```python
|
||||
# 创建环境
|
||||
# conda init bash
|
||||
conda create -n qwen2vl_wb python=3.10
|
||||
conda activate qwen2vl_wb
|
||||
|
||||
# 换源
|
||||
python -m pip install --upgrade pip
|
||||
pip config set global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
||||
|
||||
# 需要安装的库
|
||||
# 安装transformers时会依赖最新的torch版本, 目前为 torch2.5.1
|
||||
pip install qwen_vl_utils==0.0.8 transformers==4.46.2 accelerate==1.1.0 gradio==5.5.0
|
||||
pip install torchvision # 会匹配对应torch版本的依赖
|
||||
# torchvision需要安装匹配对应torch的版本
|
||||
pip install qwen_vl_utils==0.0.8 transformers==4.46.2 accelerate==1.1.0 gradio==5.5.0 torchvision==0.18.0 av==13.1.0
|
||||
|
||||
# 如需使用魔搭(国内推荐)下载模型
|
||||
# 如需使用魔搭(国内推荐)下载模型, 需安装这个库
|
||||
pip install modelscope==1.20.0
|
||||
|
||||
# 安装flash-attn
|
||||
# 安装flash-attn(可选)
|
||||
# 如显卡支持flash-attn,在确认对应python、pytorch、cuda版本后, 下载对应的release版本.
|
||||
wegt https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.0.post2/flash_attn-2.7.0.post2+cu11torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl
|
||||
wegt https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.0.post2/flash_attn-2.7.0.post2+cu12torch2.3cxx11abiFALSE-cp312-cp312-linux_x86_64.whl
|
||||
# 镜像加速链接:
|
||||
# wget https://github.moeyy.xyz/https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.0.post2/flash_attn-2.7.0.post2+cu11torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl
|
||||
pip install flash_attn-2.7.0.post2+cu11torch2.5cxx11abiFALSE-cp310-cp310-linux_x86_64.whl
|
||||
# wget https://github.moeyy.xyz/https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.0.post2/flash_attn-2.7.0.post2+cu12torch2.3cxx11abiFALSE-cp312-cp312-linux_x86_64.whl
|
||||
pip install flash_attn-2.7.0.post2+cu12torch2.3cxx11abiFALSE-cp312-cp312-linux_x86_64.whl
|
||||
```
|
||||
> 完整的pip列表(包含依赖)请参考[02-Qwen2-VL-2B-Instruct Web Demo 参考代码/requirements.txt](./02-Qwen2-VL-2B-Instruct%20Web%20Demo%20参考代码/requirements.txt)
|
||||
|
||||
# 下载模型
|
||||
## 借助 modelscope 下载
|
||||
# 下载模型(两种下载方法二选一即可~)
|
||||
## 1. 借助 modelscope 下载
|
||||
使用 `modelscope` 中的 `snapshot_download` 函数下载模型,第一个参数为模型名称,参数 `cache_dir` 为模型的下载路径。
|
||||
|
||||
新建 `model_download.py` 文件输入以下代码,并运行 `python model_download.py` 执行下载。
|
||||
@@ -56,9 +51,9 @@ model_dir = snapshot_download('Qwen/Qwen2-VL-2B-Instruct', cache_dir='/root/auto
|
||||
|
||||
> 注意:请记得修改 `cache_dir` 为你自己的模型下载路径 ~
|
||||
|
||||
## lfs 下载
|
||||
## 2. 借助 git lfs 下载
|
||||
```python
|
||||
# 进入autodl-tmp/
|
||||
# 进入autodl-tmp/ 或者你要保存的路径
|
||||
cd autodl-tmp/
|
||||
|
||||
# 首先安装lfs,便于通过git直接下载模型。
|
||||
@@ -74,7 +69,7 @@ MODEL=Qwen2-VL-2B-Instruct
|
||||
# URL="https://huggingface.co/Qwen/"
|
||||
# git clone "${URL}/${MODEL}"
|
||||
|
||||
# 魔搭下载
|
||||
# 魔搭下载(国内推荐)
|
||||
URL="https://www.modelscope.cn/Qwen"
|
||||
git clone "${URL}/${MODEL}.git"
|
||||
|
||||
@@ -85,14 +80,17 @@ cd ..
|
||||
# 运行Demo
|
||||
|
||||
```python
|
||||
# Ampere/Ada/Hopper架构显卡可以启用flash attn2加速推理,autodl要通过6006端口对外访问。
|
||||
# 可以使用 python mm_qwen2vl.py -h 或查看代码来查看命令帮助
|
||||
# Ampere/Ada/Hopper架构显卡可以启用flash attn2加速推理,autodl要通过6006端口对外访问。(没安装flash-attn库的忽略)
|
||||
# python mm_qwen2vl.py --flash-attn2 --model-path ./autodl-tmp/Qwen2-VL-2B-Instruct --host 0.0.0.0 --port 6006
|
||||
python mm_qwen2vl.py --model-path ./autodl-tmp/Qwen2-VL-2B-Instruct --host 0.0.0.0 --port 6006
|
||||
```
|
||||
> 完整代码请参考[mm_qwen2vl.py](./02-Qwen2-VL-2B-Instruct%20Web%20Demo%20参考代码/mm_qwen2vl.py)
|
||||
> 完整代码及详细注释请参考[mm_qwen2vl.py](./02-Qwen2-VL-2B-Instruct%20Web%20Demo%20参考代码/mm_qwen2vl.py)
|
||||
|
||||
# 测试效果
|
||||
|
||||
## 图片
|
||||

|
||||
## 视频
|
||||

|
||||
|
||||
> 如果觉得2B理解能力较差, 建议用7B以上模型.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 289 KiB |
Reference in New Issue
Block a user