InternLM / lmdeploy

LMDeploy is a toolkit for compressing, deploying, and serving LLMs.
https://lmdeploy.readthedocs.io/en/latest/
Apache License 2.0
4.63k stars 426 forks source link

[Bug] 部署的多模态模型,多轮对话时输出结果异常 #1612

Open wssywh opened 6 months ago

wssywh commented 6 months ago

Checklist

Describe the bug

使用llava-v1.6-34b模型部署OPENAI 服务后,多轮对话过程中模型有一定的概率会返回错误的结果

[{'type': 'text', 'text': '图片中有一辆红色的踏板车,它的车身和车座都有明显的磨损和污垢,车座上甚至有一些灰尘。踏板车的后部有一个黑色的篮子,里面可能装了一些东西。'}]

Reproduction

服务运行命令:

CUDA_VISIBLE_DEVICES=0,3,4,5 NCCL_SHM_DISABLE=1 lmdeploy serve api_server /data/workspace/models/llava-v1.6-34b --server-port 23333 --tp 4 --cache-max-entry-count 0.5 --chat-template template.json

template.json文件内容:

{
    "model_name": "llava-chatml",
    "system": "<|im_start|>system\n",
    "meta_instruction": "You are a robot developed by LMDeploy.",
    "eosys": "<|im_end|>\n",
    "user": "<|im_start|>user\n",
    "eoh": "<|im_end|>\n",
    "assistant": "<|im_start|>assistant\n",
    "eoa": "<|im_end|>",
    "separator": "\n",
    "capability": "chat",
    "stop_words": ["<|im_end|>"]
}

程序运行代码:

from openai import OpenAI
import sys
from qwen_test import prompts

client = OpenAI(api_key='YOUR_API_KEY', base_url='http://0.0.0.0:23333/v1')
model_name = client.models.list().data[0].id
# print(model_name)
stream = True
messages=[
    {
        'role': 'user',
        'content': 
            [ 
                {
                    'type': 'image_url',
                    'image_url': {'url': 'https://smartfire.jxgh.vip:9009/000000-zg119/upload/20240412/31de9aff2d9a01b0dfaaf5a6b9ad22de.jpeg',},
                },
                {
                    'type': 'text',
                    'text': '描述一下这张图片',
                },
            ],
    },
    {
        'role': 'assistant',
        'content': 
            [ 
                {
                    'type': 'text', 
                    'text': """
这张图片显示的是一个室内空间,看起来像是一个仓库或储藏室。在地面中间,有一辆红色的踏板车,它的车座和车身都有明显的磨损和污垢,车座上甚至还有一些灰尘。踏板车的后部有一个黑色的篮子,里面可能
装了一些东西。\n\n在踏板车的周围堆放着各种物品,包括包装材料、箱子、工具、还有一些建筑材料,比如梯子、管道等。这些物品看起来有些杂乱,被随意地堆放在地面上。\n\n在踏板车前方,有一个红色的圆环状物体,可能是某种工具或设备
的一部分。\n\n整个空间的背景是白色的墙壁,上面有一些架子,上面也堆放着一些物品。在左上角,有一根黄色的电缆,可能是用于某种电器设备。在右下角,有一块透明的塑料布覆盖着某些物品,可能是为了保护它们免受灰尘或污垢的侵害。\n\n总的来说,这张图片给人一种凌乱、拥挤的感觉,表明这个空间可能是用来存放一些不常使用的物品或工具的。
"""
                }
            ],
    },
    {
        'role': 'user',
        'content': 
            [ 
                {
                    'type': 'text',
                    'text': "图片中有什么车辆,车辆的颜色是什么",
                },
            ],
    }
]

try:
    responses = client.chat.completions.create(
        model=model_name,
        messages=messages,
        # temperature=0.1,
        stream=stream,
        # top_p=0.8
        )
    if stream:
        for response in responses:
            # print(response)
            # print(response.choices[0].delta.content)
            result = response.choices[0].delta.content
            # print(result)
            sys.stdout.write(result)
            sys.stdout.flush()

    else:
        print(responses)
        # print(responses.choices[0].message.content)
except Exception as e:
        print(e)

Environment

sys.platform: linux
Python: 3.10.14 (main, May  6 2024, 19:42:50) [GCC 11.2.0]
CUDA available: True
MUSA available: False
numpy_random_seed: 2147483648
GPU 0,1,2,3,4,5: NVIDIA A100-PCIE-40GB
CUDA_HOME: /usr/local/cuda
NVCC: Cuda compilation tools, release 12.2, V12.2.140
GCC: gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
PyTorch: 2.2.1+cu121
PyTorch compiling details: PyTorch built with:
  - GCC 9.3
  - C++ Version: 201703
  - Intel(R) oneAPI Math Kernel Library Version 2022.2-Product Build 20220804 for Intel(R) 64 architecture applications
  - Intel(R) MKL-DNN v3.3.2 (Git Hash 2dc95a2ad0841e29db8b22fbccaf3e5da7992b01)
  - OpenMP 201511 (a.k.a. OpenMP 4.5)
  - LAPACK is enabled (usually provided by MKL)
  - NNPACK is enabled
  - CPU capability usage: AVX512
  - CUDA Runtime 12.1
  - NVCC architecture flags: -gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_80,code=sm_80;-gencode;arch=compute_86,code=sm_86;-gencode;arch=compute_90,code=sm_90
  - CuDNN 8.9.2
  - Magma 2.6.1
  - Build settings: BLAS_INFO=mkl, BUILD_TYPE=Release, CUDA_VERSION=12.1, CUDNN_VERSION=8.9.2, CXX_COMPILER=/opt/rh/devtoolset-9/root/usr/bin/c++, CXX_FLAGS= -D_GLIBCXX_USE_CXX11_ABI=0 -fabi-version=11 -fvisibility-inlines-hidden -DUSE_PTHREADPOOL -DNDEBUG -DUSE_KINETO -DLIBKINETO_NOROCTRACER -DUSE_FBGEMM -DUSE_QNNPACK -DUSE_PYTORCH_QNNPACK -DUSE_XNNPACK -DSYMBOLICATE_MOBILE_DEBUG_HANDLE -O2 -fPIC -Wall -Wextra -Werror=return-type -Werror=non-virtual-dtor -Werror=bool-operation -Wnarrowing -Wno-missing-field-initializers -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas -Wno-unused-parameter -Wno-unused-function -Wno-unused-result -Wno-strict-overflow -Wno-strict-aliasing -Wno-stringop-overflow -Wsuggest-override -Wno-psabi -Wno-error=pedantic -Wno-error=old-style-cast -Wno-missing-braces -fdiagnostics-color=always -faligned-new -Wno-unused-but-set-variable -Wno-maybe-uninitialized -fno-math-errno -fno-trapping-math -Werror=format -Wno-stringop-overflow, LAPACK_INFO=mkl, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, TORCH_VERSION=2.2.1, USE_CUDA=ON, USE_CUDNN=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=1, USE_NNPACK=ON, USE_OPENMP=ON, USE_ROCM=OFF, USE_ROCM_KERNEL_ASSERT=OFF, 

TorchVision: 0.17.1+cu121
LMDeploy: 0.4.1+398c2aa
transformers: 4.40.2
gradio: 3.50.2
fastapi: 0.111.0
pydantic: 2.7.1
triton: 2.2.0

Error traceback

No response

GuiQuQu commented 5 months ago

我最近再用这个库做qwen-vl的推理,想要做few-shot,之前不用库的时候是直接str拼接few shot example的,然后用了这个库,我发现我怎么样也没法直接在prompt上直接拼多模态的例子,然后也实验了一下使用多轮对话代替,输出结果也是异常输出 多轮的结果,还有对应的期待答案

Response(text='         \n         19\n        ', 
generate_token_len=8, 
input_token_len=3645, 
session_id=0, 
finish_reason='stop', 
token_ids=[688, 198, 260, 220, 16, 24, 198, 260], 
logprobs=None)

{"p": "[0|5349]", 
"input_ids_length": 3645, 
"question": "What is the ‘actual’ value per 1000, during the year 1975?", 
"response": "         \n         19\n        ", 
"answers": ["0.28"]}

zero-shot(也就是单轮的结果)

Response(text='8.22', 
generate_token_len=5, 
input_token_len=543, 
session_id=0, 
finish_reason='stop', 
token_ids=[23, 13, 17, 17], 
logprobs=None)
{"p": "[0|5349]", 
"input_ids_length": 543, 
"question": "What is the ‘actual’ value per 1000, during the year 1975?", 
"response": "8.22", 
"answers": ["0.28"]}
irexyc commented 5 months ago

@wssywh 我跑llava的cli对比了一下,拼的prompt有点差别。https://github.com/InternLM/lmdeploy/pull/1620 这个PR修了一下。

你指的输出结果异常是不是说回答跟问题不是很匹配,有一些多余的东西?

跑了10次你给的例子(使用你给的第一轮问答作为历史)。结果如下,llava这边结果整体确实跟简洁一些。

# run with llava.cli
图片中有一辆红色的踏板车。
图片中有一辆踏板车,它的颜色是红色的。
图片中有一辆红色踏板车。
图片中有一辆红色的踏板车,也就是我们通常所说的“电瓶车”或“电动自行车”。
图片中有一辆踏板车,它的颜色是红色。
图片中有一辆红色的踏板车,它是一种小型、轻便的交通工具,通常用于短途出行或城市内行驶。
图片中有一辆红色的小型踏板车。
图片中有一辆红色的踏板车。
图片中有一辆红色的踏板车,也就是一种小型电动摩托车。
图片中有一辆红色的踏板车。

# run with lmdeploy
图片中有一辆红色的踏板车,它是一种小型摩托车,通常用于短距离的通勤或娱乐
图片中有一辆红色的踏板车,也就是通常所说的“小电驴”或“电瓶车”
图片中有一辆红色的小型摩托车,通常称为踏板车或电动踏板车。
图片中有一辆红色的踏板车,它的颜色为红色。
图片中有一辆红色的小型车辆,看起来像是一辆踏板车或小型摩托车。
图片中有一辆红色的踏板车,这是一种小型、轻便的交通工具,通常用于短距离的出行。
图片中有一辆红色的踏板车。
图片中有一辆红色踏板车。
图片中有一辆踏板车,它的颜色是红色。
图片中有一辆红色的踏板车,它是一种小型两轮车辆,通常用于短距离的交通工具。
irexyc commented 5 months ago

@GuiQuQu

你是说用lmdeploy拼不出来你想要的prompt么?可以给一些例子说明一下,我目前不是很明白你的问题。

GuiQuQu commented 5 months ago

就比如说我想要的prompt是这样

<im_start>system
SYSTEM_MESSAGE
<im_end>
<im_start>user
"关于问题的相关说明.... 
<img>表示图像1</img> 
问题:aaa
答案:bbb
关于问题的相关说明.... 
<img>表示图像2</img> 
问题:aaa
答案:bbb
关于问题的相关说明.... 
<img>表示图像3</img> 
问题:aaa
答案:bbb
关于问题的相关说明.... 
<img>表示图像4</img> 
问题:aaa
答案:"
<im_end>
<im_start>assistant:

我前三个是few-shot的例子,最后是是我对应的问题,但是因为图像必须额外传递参数,不能和文本prompt混在一起传递,所以我想要的文字和图像前后顺序怎么都无法拼成这个样子。我看了你们qwen-vl-chat的templateWarpper的源码,如果我单独传递图像参数的话,图像的prompt会变成类似于"Picture 0:"这样的内容拼接在文本prompt的最前端。

然后我觉得这样搞不成了之后,我发现你们支持gpt4v的那种prompt格式,然后我就试了下面这种prompt传递的参数

[
                {
                    'role': 'user',
                    'content': [{'type':'text','text':text1},{'type':'image_url','image_url':{'url':image_path1}}]
                },
                {
                    'role': 'assistant',
                    'content': answer1
                },
                {
                    'role': 'user',
                    'content': [{'type':'text','text':text2},{'type':'image_url','image_url':{'url':image_path2}}]
                },
                {
                    'role': 'assistant',
                    'content': answer2
                },
                {
                    'role': 'user',
                    'content': [{'type':'text','text':text3},{'type':'image_url','image_url':{'url':image_path3}}]
                },
                {
                    'role': 'assistant',
                    'content': answer3
                },
                {
                    'role': 'user',
                    'content': [{'type':'text','text':text4},{'type':'image_url','image_url':{'url':image_path4}}]
                }
 ]

构造出来的prompt相当于我的每一个问题都被user: 和 包裹了,然后前面例子的答案被assistant和包裹,我在这么去构造了我的prompt之后,然后我就发现了输出全都是混乱的字符

wssywh commented 5 months ago

@irexyc 结果不对是指输出了额外的json格式数据:

[{'type': 'text', 'text': '图片中有一辆红色的踏板车,它的车身和车座都有明显的磨损和污垢,车座上甚至有一些灰尘。踏板车的后部有一个黑色的篮子,里面可能装了一些东西。'}]

正确的输出应该是纯文本:

图片中有一辆红色的踏板车,它的车身和车座都有明显的磨损和污垢,车座上甚至有一些灰尘。踏板车的后部有一个黑色的篮子,里面可能装了一些东西。

这些都是多余的输出:[{'type': 'text', 'text':

wssywh commented 5 months ago

@irexyc 多轮对话输出文本格式异常示例 输出异常

irexyc commented 5 months ago

@wssywh

明白你的意思了。

之前的逻辑对于assistant/system这种角色,支持的格式应该是下面这种。

{
    'role': 'assistant',
    'content': 'some str' # not list
}

而你的第一轮回答给的是下面这种,所以拼接的时候会把content直接拼过去(list not text),造成第二轮之后的回话格式有问题。这个PR也对针对这种输入进行了修复。可以再试一下。

    {
        'role': 'assistant',
        'content': 
            [ 
                {
                    'type': 'text', 
                    'text': """ answer ...  """
                }
            ],
    },
irexyc commented 5 months ago

@GuiQuQu

明白你的意思了。这个问题我之前有想过,有些模型的prompt并不是固定的,<image> token可以出现在user prompt 中的任何地方。

我加一下这部分的逻辑。

irexyc commented 5 months ago

@GuiQuQu

可以看下 https://github.com/InternLM/lmdeploy/pull/1627 这个能不能解决你的问题。

GuiQuQu commented 5 months ago

我看了你提交的pr,我目前还有两个问题

  1. 我看到了你提交的pr的代码改动,但是在main分支我没看到对应的修改,然后那个pr显示"Merging is blocked"了
  2. 按照你这个修改逻辑,多张图像之间顺序就是按照传入的图像list的顺序吗,也就是说传入的第一个图像放到第一个IMAGE_TOKEN那里,第二个图像放到第二个IMAGE_TOKEN那里,这样
irexyc commented 5 months ago

@GuiQuQu

PR需要review,需要一段时间才能合入。现在用的话,需要改安装目录的代码。

image list 是按照顺序插入到 user prompt 当中。

以你之前的例子来说 (如果不改 system, user , assistant的话),那么输入大概长这样?

"关于问题的相关说明.... <IMAGE_TOKEN>问题:aaa\n答案:bbb
关于问题的相关说明.... <IMAGE_TOKEN>问题:aaa\n答案:bbb
关于问题的相关说明.... <IMAGE_TOKEN>问题:aaa\n答案:bbb
关于问题的相关说明.... <IMAGE_TOKEN>问题:aaa\n答案:"

你创建server或者pipeline的时候,可以把log_level设置成INFO,这样应该会打印拼好的prompt,可以对比一下看看拼的对不对。