ubuntu 22.04 ray 集群部署 vllm + deepseek r1

一、集群架构

具有两个 Worker 节点的 Ray 集群。每个节点都运行 Ray 帮助程序进程,以促进分布式调度和内存管理。Head 节点运行其他控制进程(以蓝色突出显示)。

1. Ray 集群架构

  • Head node(头节点)

    • 头节点是 Ray 集群的管理节点,负责调度任务、分配资源,并协调集群中的工作。它通常部署在一个高性能的机器上。
  • Worker node(工作节点)

    • 工作节点是执行计算的实际节点。它们通过网络与头节点通信,接收任务并执行。它们参与分布式调度,以及集群内存中 Ray 对象的存储和分发。

二、部署集群

1. 环境

系统 hostname CPU 内存 GPU ip
Ubuntu 22.04 head 32 核 64GB V100 * 2 10.1.16.75
Ubuntu 22.04 work1 32 核 64GB V100 * 2 10.1.16.14

我这里使用的是 kvm 虚拟机。gpu 和虚拟机通过 PCIe 直连配置如下

 <hostdev mode='subsystem' type='pci' managed='yes'>
    <source>
        <address domain='0x0000' bus='0x8a' slot='0x00' function='0x0'/>
    </source>
    <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
</hostdev>

2. 安装 v100 相关驱动

查看 ubuntu 22.04 部署 vllm + deepseek r1

3. 基本配置

vim ~/.bashrc 编辑完 source ~/.bashrc

# 指定通信网卡
export GLOO_SOCKET_IFNAME=eth1
export TP_SOCKET_IFNAME=eth1

# 各个节点的 ip
export VLLM_HOST_IP=10.1.16.18

# NCCL配置
# 设置使用的线程数来处理每个 socket 连接的通信。
export NCCL_SOCKET_NTHREADS=10

# 指定用于 NCCL 通信的网络接口
export NCCL_SOCKET_IFNAME=eth1

# 设置 NCCL 调试的级别,帮助调试和分析通信过程中的问题
# info:提供详细的运行信息,包括每个节点和 GPU 间的通信状态,适合在调试时使用。
# warn:只显示警告信息,适合正常运行时的调试。
# version:输出 NCCL 版本信息。
export NCCL_DEBUG=info

# 指定 NCCL 使用的网络协议
# 如果你使用的是 InfiniBand 网络,并且 NCCL 能够利用该网络,可以将其设置为 NCCL_NET=IB,以利用更低延迟和更高带宽的通信
export NCCL_NET=Socket

# 控制是否禁用 InfiniBand 网络
export NCCL_IB_DISABLE=0

# 使用的 gpu。我这里每台机器有两个gpu。所以0,1
export CUDA_VISIBLE_DEVICES=0,1

4. 启动 ray 集群

关于 ray start 参数在最后面

  • head 节点

\color{red}{注意}:如果需要 ray 的 dashboard 的话需要安装 pip install ray[default]
\color{red}{注意}:安装了 pip install ray[default] 才能使用 ray list nodes 这类的命令
\color{red}{注意}:这里需要指定 --num-gpus=2。Ray 默认不会在 head 节点上分配 GPU

(vllm) root@head:~# ray start --head --node-ip-address=10.1.16.75 --num-cpus=2 --num-cpus=20

Usage stats collection is enabled. To disable this, add `--disable-usage-stats` to the command that starts the cluster, or run the following command: `ray disable-usage-stats` before starting the cluster. See https://docs.ray.io/en/master/cluster/usage-stats.html for more details.

Local node IP: 10.1.16.75

--------------------
Ray runtime started.
--------------------

Next steps
  To add another node to this Ray cluster, run
    ray start --address='10.1.16.75:6379'

  To connect to this Ray cluster:
    import ray
    ray.init(_node_ip_address='10.1.16.75')

  To submit a Ray job using the Ray Jobs CLI:
    RAY_ADDRESS='http://127.0.0.1:8265' ray job submit --working-dir . -- python my_script.py

  See https://docs.ray.io/en/latest/cluster/running-applications/job-submission/index.html
  for more information on submitting Ray jobs to the Ray cluster.

  To terminate the Ray runtime, run
    ray stop

  To view the status of the cluster, use
    ray status

  To monitor and debug Ray, view the dashboard at
    127.0.0.1:8265

  If connection to the dashboard fails, check your firewall settings and network configuration.
  • work1

--address head 节点 IP 地址
--node-ip-address 当前 work 节点 IP 地址

(vllm) root@work1:~# ray start --address='10.1.16.75:6379' --node-ip-address=10.1.16.18 --num-cpus=2 --num-cpus=20
  • 查看集群状态(哪个节点都能执行)

这里可以看到 GPU 是4个了

(vllm) root@head:~# ray status

======== Autoscaler status: 2025-02-28 09:24:23.411693 ========
Node status
---------------------------------------------------------------
Active:
 1 node_1db276a96f3e4a01201e0d78c1982c73ebd2e75ce25f24db2233d79d
 1 node_abc7f98b82a8b9e94e148ed74b945c6bca5b5e5dba0b4153a0c3cc34
Pending:
 (no pending nodes)
Recent failures:
 (no failures)

Resources
---------------------------------------------------------------
Usage:
 0.0/40.0 CPU
 4.0/4.0 GPU (4.0 used of 4.0 reserved in placement groups)
 0B/80.31GiB memory
 0B/37.07GiB object_store_memory

Demands:
 (no resource demands)
  • 查看集群节点列表(如果 head 的 dashboard 是 127.0.0.1 这个命令只能在 head 节点)
(vllm) root@head:~# ray list nodes

======== List: 2025-02-28 09:28:15.097758 ========
Stats:
------------------------------
Total: 2

Table:
------------------------------
    NODE_ID                                                   NODE_IP     IS_HEAD_NODE    STATE    STATE_MESSAGE    NODE_NAME    RESOURCES_TOTAL                  LABELS
 0  1db276a96f3e4a01201e0d78c1982c73ebd2e75ce25f24db2233d79d  10.1.16.75  True            ALIVE                     10.1.16.75   CPU: 20.0                        ray.io/node_id: 1db276a96f3e4a01201e0d78c1982c73ebd2e75ce25f24db2233d79d
                                                                                                                                 GPU: 2.0
                                                                                                                                 accelerator_type:V100: 1.0
                                                                                                                                 memory: 37.149 GiB
                                                                                                                                 node:10.1.16.75: 1.0
                                                                                                                                 node:__internal_head__: 1.0
                                                                                                                                 object_store_memory: 18.575 GiB
 1  abc7f98b82a8b9e94e148ed74b945c6bca5b5e5dba0b4153a0c3cc34  10.1.16.18  False           ALIVE                     10.1.16.18   CPU: 20.0                        ray.io/node_id: abc7f98b82a8b9e94e148ed74b945c6bca5b5e5dba0b4153a0c3cc34
                                                                                                                                 GPU: 2.0
                                                                                                                                 accelerator_type:V100: 1.0
                                                                                                                                 memory: 43.157 GiB
                                                                                                                                 node:10.1.16.18: 1.0
                                                                                                                                 object_store_memory: 18.496 GiB

5. vllm 启动模型

  • work1

\color{red}{注意}:这里 api_server 默认的端口号是 8000

(vllm) root@work1:~# python -m vllm.entrypoints.openai.api_server --model /root/deepseek-r1-qwen-1.5b --served-model-name deepseek-qwen-1.5b --dtype=half --tensor-parallel-size 2 --pipeline-parallel-size 2 --max-model-len 1000 --trust-remote-code

......
INFO 02-28 09:18:58 launcher.py:31] Route: /score, Methods: POST
INFO 02-28 09:18:58 launcher.py:31] Route: /v1/score, Methods: POST
INFO 02-28 09:18:58 launcher.py:31] Route: /v1/audio/transcriptions, Methods: POST
INFO 02-28 09:18:58 launcher.py:31] Route: /rerank, Methods: POST
INFO 02-28 09:18:58 launcher.py:31] Route: /v1/rerank, Methods: POST
INFO 02-28 09:18:58 launcher.py:31] Route: /v2/rerank, Methods: POST
INFO 02-28 09:18:58 launcher.py:31] Route: /invocations, Methods: POST
INFO:     Started server process [19143]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

用到的参数说明。具体都有什么参数在最后

参数 说明
--served-model-name deepseek-qwen-1.5b 指定正在提供服务的模型的名称。
--dtype=half 设置模型参数的数据类型为 half,也就是 16 位浮动精度(FP16)。V100 只能用 16 位
--tensor-parallel-size 2 设置张量并行的大小为 2。根据节点中 gpu 定义
--pipeline-parallel-size 2 设置流水线并行的大小为 2。根据 ray work 节点数量定义。
--max-model-len 1000 设置模型输入的最大长度为 1000。此参数定义了输入序列的最大 token 数量,超过这个长度的输入将被截断。
--trust-remote-code 启用远程代码信任。

6. 查看 gpu 监控

可以看到使用了 4 个 gpu


image.png

7. 测试

import requests


headers = {
    "Content-Type": "application/json",
}

url = "http://localhost:8000/v1/chat/completions"
prompt = {
    "model": 'deepseek-qwen-1.5b',
    "messages": [
        {'role': "system", "content":"你好"},
    ],
    "temperature": 0.6,
    "top_p": 0.8
}

resp = requests.post(url, headers=headers, json=prompt, stream=False)
rep = resp.json()
print(rep)

执行后

(vllm) root@work1:~# python op.py

{
    'id': 'chatcmpl-0856a11261a74839aa84ba118bde241c',
    'object': 'chat.completion',
    'created': 1740706585,
    'model': 'deepseek-qwen-1.5b',
    'choices': [{
        'index': 0,
        'message': {
            'role': 'assistant',
            'reasoning_content': None,
            'content': '您好!您提到的“你好”是中文里常用的问候语,如果您有其他问题或需要帮助,请随时告诉我。\n</think>\n\n你好!有什么我可以帮助你的吗?',
            'tool_calls': []
        },
        'logprobs': None,
        'finish_reason': 'stop',
        'stop_reason': None
    }],
    'usage': {
        'prompt_tokens': 5,
        'total_tokens': 42,
        'completion_tokens': 37,
        'prompt_tokens_details': None
    },
    'prompt_logprobs': None
}

三、Ray 关键组件

1. Raylets

  • 功能:Raylet 是 Ray 集群中的基本计算单元,负责分配和管理任务的资源。
  • 作用:Raylet 运行在每个工作节点上,接收来自调度器的任务请求,确保任务能够在可用资源上运行。每个 Raylet 维护着本地的工作池,负责管理本地计算资源。

2. 调度器 (Scheduler)

  • 功能:调度器负责在 Ray 集群中调度任务,确保任务能够根据依赖关系按顺序执行。
  • 作用:调度器根据任务的资源需求和节点状态,决定将任务分配到哪个节点,并处理任务之间的依赖关系。它确保计算任务的执行是高效的,并且最大化地利用了集群中的计算资源。

3. 对象存储 (Object Store)

  • 功能:Ray 提供了一个高效的对象存储,用于跨进程和跨节点共享数据。
  • 作用:Ray 使用对象存储来存放任务的输入和输出数据。这些数据可以被多个任务或工作节点共享,因此它是 Ray 中数据交换的核心组件。Ray 的对象存储支持高效的内存映射和数据传输。

4. 调度器的执行队列 (Execution Queue)

  • 功能:每个 Raylet 中都有一个执行队列,存储等待执行的任务。
  • 作用:执行队列确保任务按照合适的顺序被调度执行。当一个节点资源可用时,任务从队列中取出并执行。

5. 任务调度 (Task Scheduling)

  • 功能:任务调度机制负责将任务分配到合适的节点,并确保所有依赖的任务都已完成。
  • 作用:Ray 使用了灵活的任务调度策略,能够处理大规模并行任务和任务间的依赖关系。任务调度是 Ray 高效分布式执行的关键。

6. Ray Core API

  • 功能:Ray Core 提供了用于定义和执行任务的基础 API。
  • 作用:开发者使用 Ray Core API 来定义远程函数、对象存储、任务依赖等。通过这些 API,Ray 用户能够方便地提交计算任务,并控制任务之间的数据传递和计算逻辑。

7. 分布式执行 (Distributed Execution)

  • 功能:Ray 的核心特性之一是分布式执行,允许在多台机器或多节点上并行运行任务。
  • 作用:通过分布式执行,Ray 能够在大量工作节点上并行计算,从而提升大规模计算任务的效率。Ray 自动处理节点之间的通信和数据交换,确保分布式计算过程的顺利进行。

8. Ray Dashboard

  • 功能:Ray 提供了一个可视化的 Dashboard,用于监控集群状态、查看任务执行情况以及管理集群。
  • 作用:通过 Dashboard,用户可以查看集群中每个节点的资源使用情况、任务执行情况、失败任务等信息,有助于故障排查和性能优化。

9. Ray Tune

  • 功能:Ray Tune 是一个分布式超参数调优框架,旨在帮助用户在分布式环境中进行模型训练时优化超参数。
  • 作用:Ray Tune 可以自动化地运行超参数搜索,支持多种搜索算法(如网格搜索、随机搜索、贝叶斯优化等),并且能够与 Ray 集群的计算资源进行无缝集成。

10. Ray Serve

  • 功能:Ray Serve 是一个分布式的模型推理和服务框架。
  • 作用:Ray Serve 用于大规模的机器学习模型推理,可以将机器学习模型部署为服务,支持自动扩展、负载均衡等功能,非常适合生产环境下的实时推理任务。

11. Ray Actors

  • 功能:Ray Actor 是 Ray 中用于持久化状态和实现并行计算的基础单元。
  • 作用:Ray Actor 允许开发者创建具有状态的计算实体,每个 Actor 可以在不同的任务中维持自己的状态。它支持并行执行,可以高效地处理需要状态持久化的任务。

12. Ray RLlib

  • 功能:Ray RLlib 是一个强大的分布式强化学习库。
  • 作用:RLlib 提供了在 Ray 集群上高效训练强化学习模型的能力,支持分布式训练、自动并行化等功能。它能够处理大规模的强化学习任务,并在多个环境中同时训练多个智能体。

13. Ray Workflow

  • 功能:Ray Workflow 使得复杂任务可以按工作流进行组织和管理。
  • 作用:通过 Ray Workflow,用户可以定义和管理多个依赖关系复杂的任务,使得任务的执行变得更具可控性和可扩展性。

四、ray start 参数说明

官方文档

参数名 说明
--node-ip-address TEXT 本节点的 IP 地址
--address TEXT head 节点 IP 地址
--port INTEGER head 点 Ray 进程的端口。如果未提供,默认为 6379;如果设置为 0,将分配一个可用端口。
--object-manager-port INTEGER 启动对象管理器时使用的端口
--node-manager-port INTEGER 启动节点管理器时使用的端口
--gcs-server-port INTEGER GCS 服务器的端口号
--min-worker-port INTEGER 工作节点绑定的最小端口号。如果未设置,将随机选择端口。
--max-worker-port INTEGER 工作节点绑定的最大端口号。如果设置,必须同时设置 --min-worker-port
--worker-port-list TEXT 一个逗号分隔的端口列表,供工作节点绑定。覆盖 --min-worker-port--max-worker-port
--ray-client-server-port INTEGER Ray 客户端服务器绑定的端口号,默认为 10001,如果没有安装 ray[client] 则为 None。
--object-store-memory INTEGER 启动对象存储时使用的内存量(以字节为单位)。默认值是系统内存的 30%(由 ray_constants.DEFAULT_OBJECT_STORE_MEMORY_PROPORTION 限制)
--num-cpus INTEGER 本节点的 CPU 数量
--num-gpus INTEGER 本节点的 GPU 数量
--resources TEXT 一个 JSON 序列化的字典,将资源名称映射到资源数量。
--head 启动 head 点时提供此参数
--include-dashboard BOOLEAN 提供此参数以启动 Ray Dashboard GUI(仪表盘)
--dashboard-host TEXT 绑定 Dashboard 服务器的主机地址,默认是 127.0.0.1,也可以是 0.0.0.0(所有接口可用)。
--dashboard-port INTEGER 绑定 Dashboard 服务器的端口号,默认是 8265
--dashboard-agent-listen-port INTEGER Dashboard 代理监听 HTTP 请求的端口
--dashboard-agent-grpc-port INTEGER Dashboard 代理监听 gRPC 请求的端口
--dashboard-grpc-port INTEGER Dashboard head 点监听 gRPC 请求的端口
--runtime-env-agent-port INTEGER 运行时环境代理监听 HTTP 请求的端口
--block 提供此参数将命令阻塞执行,直到被中断
--plasma-directory TEXT 对象存储内存映射文件的目录
--autoscaling-config TEXT 包含自动扩展配置的文件
--no-redirect-output 不将非工作进程的标准输出和标准错误重定向到文件
--plasma-store-socket-name TEXT 手动指定 Plasma 存储的套接字名称
--raylet-socket-name TEXT 手动指定 Raylet 进程的套接字路径
--temp-dir TEXT 手动指定 Ray 进程的根临时目录,只有在指定 --head 时才有效
--storage TEXT 集群的持久存储 URI,实验性功能
--metrics-export-port INTEGER 用于通过 Prometheus 端点导出 Ray 指标的端口号
--ray-debugger-external 使 Ray 调试器对外部节点可用。只有在节点背后有防火墙时才能安全激活
--disable-usage-stats 如果设置为 True,将禁用使用情况统计收集
--log-style [auto|record|pretty] 日志格式。pretty 输出格式化且有颜色,record 输出无格式记录,auto 默认选择 pretty,如果标准输入不是 TTY 则禁用漂亮日志
--log-color [auto|false|true] 使用彩色日志。auto 在标准输出为 TTY 时启用彩色日志,false 禁用彩色日志,true 强制启用彩色日志
-v, --verbose 启用详细输出模式

五、vllm.entrypoints.openai.api_server 参数说明

官方文档

参数 说明 默认值
--model 要使用的 HuggingFace 模型的名称或路径。 "facebook/opt-125m"
--tokenizer 要使用的 HuggingFace 分词器的名称或路径。
--skip-tokenizer-init 跳过分词器和反向分词器的初始化。
--revision 要使用的特定模型版本,可以是分支名称、标签名称或提交 ID。如果未指定,将使用默认版本。
--code-revision 要使用的 HuggingFace Hub 上的模型代码的特定版本。可以是分支名称、标签名称或提交 ID。
--tokenizer-revision 要使用的特定分词器版本,可以是分支名称、标签名称或提交 ID。如果未指定,将使用默认版本。
--tokenizer-mode 可选值:

auto: 将使用快速分词器(如果可用)。
slow: 始终使用慢速分词器。
"auto"
--trust-remote-code 信任来自 HuggingFace 的远程代码。
--download-dir 用于下载和加载权重的目录,默认为 HuggingFace 的默认缓存目录。
--load-format 加载模型权重的格式
可选值:

auto: 将尝试加载 safetensors 格式的权重,如果没有该格式则回退到 pytorch bin 格式。
pt: 将加载 pytorch bin 格式的权重。
safetensors: 将加载 safetensors 格式的权重。
npcache: 将加载 pytorch 格式的权重并存储 numpy 缓存,以加快加载速度。
dummy: 将使用随机值初始化权重,主要用于性能分析。
tensorizer: 将使用 CoreWeave 的 tensorizer 加载权重,假设 tensorizer_uri 已设置为序列化权重的路径。
"auto"
--dtype 模型权重和激活的精度类型
可选值:

auto: 对于 FP32 和 FP16 模型将使用 FP16 精度,对于 BF16 模型将使用 BF16 精度。
half: 为 FP16。推荐用于 AWQ 量化。
float16: 与 half 相同。
bfloat16: 在精度和范围之间取得平衡。
float: 是 FP32 精度的简写。
float32: 为 FP32 精度。
"auto"
--kv-cache-dtype 可选值:auto, fp8,kv 缓存存储的数据类型。如果选择“auto”,将使用模型的数据类型。 "auto"
--quantization-param-path 包含 KV 缓存缩放因子的 JSON 文件路径。
--max-model-len 模型上下文的最大长度。如果未指定,将根据模型配置自动推导。
--guided-decoding-backend 可选值:outlines, lm-format-enforcer,默认使用哪个引擎进行引导解码(JSON schema / 正则表达式等)。 "outlines"
--worker-use-ray 是否使用 Ray 进行分布式服务,当使用超过 1 个 GPU 时将自动设置。
--pipeline-parallel-size, -pp 管道阶段的数量。 1
--tensor-parallel-size, -tp 张量并行副本的数量。 1
--max-parallel-loading-workers 按多个批次顺序加载模型,以避免使用张量并行和大模型时出现 RAM OOM 问题。
--ray-workers-use-nsight 如果指定,使用 nsight 来分析 Ray worker。
--block-size 可选值:8, 16, 32,连续的标记块的大小。 16
--enable-prefix-caching 启用自动前缀缓存。
--use-v2-block-manager 使用 BlockSpaceManagerV2。
--num-lookahead-slots 实验性调度配置,必要时用于推测解码。 0
--seed 随机种子。 0
--swap-space 每个 GPU 的 CPU swap 空间大小(GiB)。 4
--gpu-memory-utilization 用于模型执行的 GPU 内存占比,从 0 到 1 之间。例如,0.5 表示使用 50% 的 GPU 内存。 0.9
--num-gpu-blocks-override 如果指定,忽略 GPU 分析结果并使用此数量的 GPU 块。用于测试抢占。
--max-num-batched-tokens 每次迭代中最大的批次 token 数量。
--max-num-seqs 每次迭代中最大的序列数量。 256
--max-logprobs 返回最大日志概率数量。 5
--disable-log-stats 禁用日志统计。
--quantization, -q 可选值:aqlm, awq, fp8, gptq, squeezellm, gptq_marlin, marlin, None,量化权重的方法。 None
--enforce-eager 始终使用急切模式的 PyTorch。如果为 False,将使用急切模式和 CUDA 图混合以获得最大性能和灵活性。
--max-context-len-to-capture CUDA 图支持的最大上下文长度。超出此长度的序列将回退到急切模式。(已弃用,使用 `--max-seq_len-to-capture)
--max-seq_len-to-capture CUDA 图支持的最大序列长度。超出此长度的序列将回退到急切模式。 8192
--disable-custom-all-reduce 参见 ParallelConfig。
--tokenizer-pool-size 用于异步分词的分词池大小。如果为 0,将使用同步分词。 0
--tokenizer-pool-type 用于异步分词的分词池类型。如果 tokenizer_pool_size 为 0,则忽略此设置。 "ray"
--tokenizer-pool-extra-config 分词池的额外配置。应为 JSON 字符串,将解析为字典。如果 tokenizer_pool_size 为 0,则忽略此设置。
--enable-lora 如果为 True,启用 LoRA 适配器的处理。
--max-loras 单批次中 LoRA 的最大数量。 1
--max-lora-rank LoRA 的最大秩。 16
--lora-extra-vocab-size LoRA 适配器中可以存在的额外词汇的最大大小(添加到基础模型词汇中)。 256
--lora-dtype 可选值:auto, float16, bfloat16, float32,LoRA 的数据类型。如果为 auto,将默认使用基础模型的 dtype。 "auto"
--max-cpu-loras 要存储在 CPU 内存中的 LoRA 数量。必须大于或等于 max_num_seqs。默认值为 max_num_seqs。
--fully-sharded-loras 默认情况下,只有一半的 LoRA 计算与张量并行共享。启用此选项将使用完全分片的层。
--device 可选值:auto, cuda, neuron, cpu,用于 vLLM 执行的设备类型。 "auto"
--image-input-type 可选值:pixel_values, image_features,传递给 vLLM 的图像输入类型。
--image-token-id 图像 token 的输入 ID。
--image-input-shape 给定输入类型时最大的图像输入形状(对于内存占用最坏)。仅在 vLLM 的 profile_run 中使用。
--image-feature-size 图像特征在上下文维度上的大小。
--scheduler-delay-factor 在调度下一个提示之前,延迟因子乘以前一个提示的延迟。 0.0
--enable-chunked-prefill 如果设置,预填充请求可以基于 max_num_batched_tokens 进行分块。
--speculative-model 用于推测解码的草稿模型的名称。
--num-speculative-tokens 从草稿模型中采样的推测 token 数量。
--speculative-max-model-len 草稿模型支持的最大序列长度。超出此长度的序列将跳过推测。
--ngram-prompt-lookup-max 推测解码中 ngram 提示查找的最大窗口大小。
--ngram-prompt-lookup-min 推测解码中 ngram 提示查找的最小窗口大小。
--model-loader-extra-config 模型加载器的额外配置。这将传递给选择的 load_format 对应的模型加载器。应为 JSON 字符串,解析为字典。
--served-model-name 在 API 中使用的模型名称。如果提供多个名称,服务器将响应提供的任何名称。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。