一、推理镜像介绍
当前预置了两个大模型推理镜像:
·【推荐】angel-vllm:TI-ONE 团队基于开源 vllm 优化的版本,支持在线 int8、nf4、fp8 的 weight-only 量化和 LayerwiseSearchSMQ 等多种量化加速方式,对生产环境并发要求高的推荐使用此镜像;
·angel-deepspeed:TI-ONE 团队基于开源 deepspeed 优化的版本,支持了算子、通信、量化等多项优化技术;
这篇会介绍如何使用 TI-ONE 内置大模型推理镜像来启动提供大模型对话 API 的推理服务。
二、模型目录准备
首先你需要将要推理的模型放到自己的 CFS 或 GooseFS 文件系统中,模型目录结构与 huggingface 上的 Text Generation 类型的模型相同,模型格式支持 .bin 格式及 .safetensors 格式。
注意:说明:
注意:angel-vllm 镜像还支持推理使用 tilearn-llm 库进行 LayerwiseSearchSMQ 量化得到的模型,请确认模型目录中包含 smoothq_model-8bit-auto.safetensors 文件,并建议删除原始的非量化模型文件。
注意:若你希望使用 TI-ONE 提供的 LayerwiseSearchSMQ 量化加速技术,请提工单反馈。
三、模型配置文件
为了让推理镜像更好地识别到你推理用的是什么模型,推荐在模型目录中额外保存一个 ti_model_config.json 配置文件,格式如下:
{"model_id": "Baichuan2-13B-Chat", "conv_template_name": "baichuan2-chat"}
字段含义:
model_idconv_template_name
·含义:模型名称
·推荐值:若要运行的是 huggingface 上的开源模型,推荐直接指定为 huggingface 上的模型 ID,例如"Baichuan2-13B-Chat","Qwen1.5-14B-Chat",模型名称中是否包含斜杠"/"、及文本的大小写均不影响实际使用。
·其他指定方式:若你不想在模型目录中增加ti_model_config.json 文件,也可以在启动服务时通过 MODEL_ID 环境变量来指定。
·含义:对话模板名称
·推荐值:若你的 model_id 与开源模型相同,且没有在 SFT 阶段修改过模型的对话模板,你可以不添加 conv_template_name 参数,推理镜像会尝试自动匹配;若你明确模型用了哪种对话模板,建议你手动指定对话模板。
·其他指定方式:若你不想在模型目录中增加ti_model_config.json 文件,也可以在启动服务时通过 CONV_TEMPLATE 环境变量来指定。
目前常用的对话模板名称如下表所示,镜像剩余当前支持的对话模板列表详见 conversation.py。
| 对话模板名称 | 支持的模型系列 |
| generate | 非对话模型(直接生成,无对话模板) |
| shennong_chat | 腾讯行业大模型(行业大模型需要开通白名单) |
| llama-3 | llama-3-8b-instruct、llama-3-70b-instruct 模型 |
| llama-2 | llama-2-chat 系列模型 |
| qwen-7b-chat | qwen-chat 系列模型(chatml 格式) |
| baichuan2-chat | baichuan2-chat 系列模型 |
| baichuan-chat | baichuan-13b-chat 模型 |
| chatglm3 | chatglm3-6b 模型 |
| chatglm2 | chatglm2-6b 模型 |
四、启动推理服务
你可以通过 TI-ONE 平台的模型服务 > 在线服务 > 新建服务入口来启动推理服务,以下是服务实例配置的指引。
五、模型来源及运行环境
大模型推理服务模型来源请选择 CFS 或 GooseFS,路径直接填上文 模型目录准备 在 CFS 或 GooseFS 上的路径。
运行环境请选择 【内置 / LLM / angel-vllm】或【内置 / LLM / angel-deepspeed】,推荐选择 angel-vllm。
六、资源申请建议
大模型推理时需要的机器资源与模型的参数量相关,我们推荐按如下规则配置推理服务资源(CPU 和内存建议按照节点的实际可用资源按占用的卡数等比例分配,建议内存至少分配大于显卡显存,否则容易引起 OOMKilled 异常)。
| 模型参数量 | GPU 卡类型和数量 |
| 6 ~ 8B | PNV5b * 1 / A10 * 1 / A100 * 1 / V100 * 1 |
| 12 ~ 14B | PNV5b * 1 / A10 * 2 / A100 * 1 / V100 * 2 |
| 65 ~ 72B | PNV5b * 8 / A100 * 8 |
注意:说明:
注意:部分模型支持32k、128k之类的长上下文,KV cache 所需的显存会较大,若你推理时不需要支持那么长上下文,可以手动调小上下文长度;
注意:若显存不足无法启动,请尝试以下几种方式解决:
注意:1. 给服务分配更多 GPU 卡数量或更换显存更大的 GPU 机型;
注意:2. 【推荐】尝试开启 int8 weight-only 量化,可以减半模型权重所需的显存,如 QUANTIZATION=ifq;
注意:3. 【推荐】尝试调小上下文长度,可以减少 KV cache 所需的显存,如 MAX_MODEL_LEN=4096;
注意:4. 尝试调整显存预留分配比例,如 GPU_MEMORY_UTILIZATION=0.95;
注意:5. 尝试使用 eager 模式,启动参数添加 –enforce-eager;
注意:具体参数含义详见 angel-vllm 镜像高级设置
七、angel-vllm 镜像高级设置
大多数情况下,你无需做任何修改直接启动镜像即可。若你有进一步的高级设置需求,可以通过启动命令或环境变量调整容器的启动参数。
镜像默认的启动命令为
run
目前支持的启动参数和环境变量如下(下表中同一行中启动参数与环境变量的作用是等价的,若同时设置了,启动命令的参数的优先级更高):
| 启动参数 | 环境变量 | 默认值 | 含义 |
| 无 | MODEL_ID | 无 | 模型名称,详见 模型配置文件 model_id 的介绍 |
| 无 | CONV_TEMPLATE | 无 | 对话模板名称,详见 模型配置文件 conv_template_name 的介绍 |
| –model-path | MODEL_PATH | /data/model | 模型加载路径,启动服务时指定的 CFS 路径默认会自动挂载到容器中的 "/data/model" 路径,大多数情况你不需修改 |
| –num-gpus | TP | 容器分配的显卡数量 | 模型并行推理的显卡数量,用于当单张显卡显存不够时,用多张显卡来加载模型。默认为你为单个容器分配的 GPU 资源数。说明:angel-vllm(2.0) 镜像支持分布式推理,选择多机分布式部署时会自动设置为分布式推理集群的总卡数。 |
| –worker-num | WORKER_NUM | 1 | 推理 Worker 数量,影响单个容器中跑推理服务的进程数,默认为1。一般分配给容器的 GPU 卡数等于 TP * WORKER_NUM |
| –limit-worker-concurrency | MAX_CONCURRENCY | 128 | 每个推理 Worker 支持的最大并发数,正在处理的请求超过此数值时,新来的请求会进行排队 |
| –dtype | DTYPE | float16 | 模型精度类型,可选值 ["auto", "float16", "bfloat16", "float32"],若配置 "auto" 则自动根据模型训练时使用的精度来配置 |
| –seed | SEED | 无 | 每次生成的随机种子,默认不指定 |
| –max-model-len | MAX_MODEL_LEN | 模型上下文长度 | 推理服务支持的最大上下文token数,默认自动读取模型配置信息的上下文长度,若一些长上下文的模型按默认配置加载显存不足,你可以手动设置此参数来调小此数值 |
| –quantization | QUANTIZATION | none | 量化加速模式,可选值 ["none", "ifq", "ifq_nf4", "fp8", "smoothquant", "auto"]"none":表示关闭量化加速;"ifq":表示开启在线 Int8 Weight-Only 量化,可以在效果基本不损失的情况下加速推理,并减少模型权重的显存占用;"ifq_nf4": 表示开启在线 NF4 Weight-Only 量化,4bit 量化可以进一步加速推理并减少模型权重的显存占用;【angel-vllm(2.0) 镜像开始支持】"fp8":表示开启在线 FP8 Weight-Only 量化,可以进一步加速推理并减少模型权重的显存占用;【angel-vllm(2.0) 镜像开始支持】【仅 NVIDIA Hopper 系列机型支持】"smoothquant":表示开启 LayerwiseSearchSMQ 量化 ,可以在效果略微损失的情况下进一步加速推理(依赖提前准备量化后的模型文件,当前仅部分模型支持);"auto":表示自动判断量化模式,其中:若机型的显卡不支持量化,自动关闭量化;若模型目录中包含 smoothq_model-8bit-auto.safetensors 文件,会自动开启 LayerwiseSearchSMQ 量化加速;其他情况下,默认开启在线 Int8 Weight-Only 量化加速(ifq);若你对推理速度有较高要求,或显存资源紧张,推荐你开启量化加速。 |
| –use-lookahead | USE_LOOKAHEAD | 0 | Lookahead 并行解码加速,可选值["0", "1", "true", "false"],设置为"1"或"true"表示开启 Lookahead 并行解码加速。Lookahead 并行解码针对输入较长,特别是生成内容包含在输入文本中的如 RAG 场景生成加速效果较好。【angel-vllm(2.0) 镜像开始支持】 |
| –num-speculative-tokens | NUM_SPECULATIVE_TOKENS | 8 | 并行解码token数量,在开启并行解码时生效。表示一次并行解码长度,单并发推荐设置成12,BatchSize较大时可以适当降低一次解码长度,例如6或者4。【angel-vllm(2.0) 镜像开始支持】 |
| –api-keys | OPENAI_API_KEYS | 无 | API_KEY 列表,默认不设置。支持的格式为英文逗号分隔的字符串,例如"sk-xxxx,sk-yyyy,sk-zzzz",设置后表示服务的 API 接口开启 Bearer Token 鉴权,用户请求时需要带上任意一个可用的 API_KEY 作为访问令牌,例如 curl 请求时带上 -H "Authorization: Bearer sk-xxxx",或者用 openai sdk 调用时设置 OPENAI_API_KEY。开启鉴权后,请求鉴权不通过会返回 HTTP 401 错误。 请注意:开启API_KEY鉴权后将不再支持在线体验,仅支持服务调用 |
| –max-num-batched-tokens | 无 | max(模型上下文长度, 2048) | 每次迭代最大处理的 token 数,默认取2048和模型支持的上下文长度的较大值;若显存富余较多,且输入文本较长,可以适当调大此参数,以获得更好的吞吐性能。 |
| –max-num-seqs | 无 | 256 | 每次迭代最大处理的生成序列数 |
| –gpu_memory_utilization | GPU_MEMORY_UTILIZATION | 0.9 | 用于模型权重、激活及 KV cache 的预留 GPU 显存比例,值越大可支持的 KV cache 越大,但是越容易显存溢出 |
| –enforce-eager | ENFORCE_EAGER | 1 | 是否强制开启 PyTorch 的 eager 模式,默认关闭,此时会额外使用 CUDA graph 做进一步加速,但会占用额外显存,并增加一些服务启动耗时。若启动报显存不足,你可以尝试启动命令中添加–enforce-eager参数来节约显存占用,但是推理性能会稍有下降。【angel-vllm(2.0) 镜像默认打开】 |
同时,镜像也支持 vllm 原生的 entrypoint 推理服务启动方式,你可以修改启动命令为 python3 -m vllm.entrypoints.openai.api_server 开头,此时支持开源 vllm 0.4.2 版本原生支持的各类参数,详见 vllm 产品文档。
平台镜像版本在此基础上额外支持以下参数:
·–use-lookahead: 表示开启 lookahead 并行解码,针对输入较长,特别是生成内容包含在输入文本中的如 RAG 场景生成加速效果较好,需同时开启–use-v2-block-manager
·–num-speculative-tokens: 表示一次并行解码长度,单并发推荐设置成12,BatchSize 较大时可以适当降低一次解码长度,例如6或者4。
·–quantization: 可额外支持 ifq、smoothquant 量化方式,详见上文量化加速模式说明。
例如:
python3 -m vllm.entrypoints.openai.api_server –model /data/model –served-model-name model –trust-remote-code –quantization ifq –use-v2-block-manager –use-lookahead –num-speculative-tokens 6
八、angel-deepspeed 镜像高级设置
大多数情况下,你无需做任何修改直接启动镜像即可。若你有进一步的高级设置需求,可以通过启动命令或环境变量调整容器的启动参数。
镜像默认的启动命令为
run
目前支持的启动参数和环境变量如下(下表中同一行中启动参数与环境变量的作用是等价的,若同时设置了,启动命令的参数的优先级更高):
| 启动参数 | 环境变量 | 默认值 | 含义 |
| 无 | MODEL_ID | 无 | 模型名称,详见 模型配置文件 model_id 的介绍 |
| 无 | CONV_TEMPLATE | 无 | 对话模板名称,详见 模型配置文件 conv_template_name 的介绍 |
| –model-path | MODEL_PATH | /data/model | 模型加载路径,启动服务时指定的 CFS 路径默认会自动挂载到容器中的 "/data/model" 路径,大多数情况你不需修改 |
| –num-gpus | TP | 1 | 模型并行推理的显卡数量,默认为 1 |
| –worker-num | WORKER_NUM | 1 | 推理 Worker 数量,影响单个容器中跑推理服务的进程数,一般在分配给容器的 GPU 卡数大于设置的 –num-gpus 参数时才修改 |
| –limit-worker-concurrency | MAX_CONCURRENCY | 32 | 每个推理 Worker 支持的最大并发数,正在处理的请求超过此数值时,新来的请求会进行排队 |
| –dtype | DTYPE | float16 | 模型精度类型,可选值 ["float16", "bfloat16", "float32"],若配置 "auto" 则自动根据模型训练时使用的精度来配置 |
| –seed | SEED | 无 | 每次生成的随机种子,默认不指定 |
| –ds-dtype | DS_DTYPE | float16 | 表示使用 Angel-Deepspeed 加速时的精度类型,可选值 ["float16", "bfloat16", "int8"] |
| –max-batch-size | MAX_BATCH_SIZE | 16 | 动态组 Batch 时,每个批次的请求数上限 |
| –batch-wait-timeout | BATCH_WAIT_TIMEOUT | 0.1 | 动态组 Batch 时,每个批次的最长等待时间(秒) |
九、对话 API 接口文档
推理镜像容器启动后默认会监听容器的 8501 端口,你可以通过在线服务的实例列表 > 进入容器按钮直接进入容器调试。
若你需要在服务外部访问模型的对话接口,有以下几种方式:
1. 在线体验:直接使用前端在线体验页面访问或使用 快速试一试开源大模型 – 第三步:在线体验模型效果 中的接口访问,这种方式会经过内容审核,支持内置大模型和自定义大模型;
2. 服务调用:参考 在线服务调用 文档,在服务的服务调用标签页获取服务的公网或 VPC 内调用地址(使用平台内置大模型的推理服务,不支持服务调用;使用你自己模型和内置大模型镜像的推理服务,支持服务调用);你可以基于此接口二次开发你的大模型应用,若有内容审核需求需要你自行接入。若你对服务调用性能要求较高,推荐使用 VPC 内网调用方式。
十、1. 接口描述
POST /v1/chat/completions
对话接口,基本兼容 OpenAI 的 Chat 接口格式。
十一、2. 输入参数
HTTP Header 参数
| 名称 | 值 |
| Content-Type | application/json |
HTTP 请求体参数
| 参数名称 | 必填 | 类型 | 描述 |
| messages | 是 | Array of Message | 会话内容,按对话时间序排列。 |
| max_tokens | 否 | Integer | 默认为模型支持的上下文长度,表示模型可生成内容的最长 token 数量,最大不能超过模型支持的上下文长度; |
| stream | 否 | Boolean | 默认 false,表示非流式返回;设置为 true 时表示流式返回,如果对首字返回延迟敏感建议使用流式,以获得更好的体验。 |
| temperature | 否 | Float | 默认 0.7,取值区间为 [0.0, 2.0],表示采样温度,用于调整随机从生成模型中抽样的程度。 较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定。设置为 0 时,表示使用贪心采样策略,此时生成结果没有随机性。此参数会影响模型生成的回复的质量和多样性。 |
| top_p | 否 | Float | 默认 1.0,取值区间为 (0.0, 1.0],表示将可能性之和不超过 top_p 的 tokens 列入候选名单。 影响输出文本的多样性,取值越大,生成文本的多样性越强。此参数会影响模型生成的回复的质量和多样性。 |
| top_k | 否 | Integer | 默认 -1,表示禁用 top-k 过滤采样。当取值大于 0 时,会先过滤可能性最高的 top-k 个 tokens 后再使用 top_p 采样。此参数会影响模型生成的回复的质量和多样性。 |
| presence_penalty | 否 | Float | 默认 0.0,取值区间为 [-2.0, 2.0],表示存在惩罚,为正时对于新 token 在现文本中是否出现进行惩罚,增大模型讨论新话题的概率。此参数会影响模型生成的回复的质量和多样性。 |
| frequency_penalty | 否 | Float | 默认 0.0,取值区间为 [-2.0, 2.0],表示频率惩罚,为正时对于新 token 在现文本中的出现次数进行惩罚,降低模型重复同一文本的概率。此参数会影响模型生成的回复的质量和多样性。 |
| repetition_penalty | 否 | Float | 默认 1.0,取值区间为 [1.0, 2.0],表示重复惩罚系数,大于 1 时会抑制重复生成词,减少连续重复现象。此参数会影响模型生成的回复的质量和多样性。 |
| stop | 否 | Array of String | 默认 None,表示无额外停止词;可以根据实际业务需要设置1个或多个停止词,遇到停止词时自动停止生成。 |
| ignore_eos | 否 | Boolean | 默认 false,表示不忽略停止词;设置为 true 时会强制生成 max_tokens 数量的 token 后才停止生成,一般只在调试时使用(此参数仅 angel-vllm 镜像支持)。 |
| seed | 否 | Integer | 默认 None,表示不设置随机种子;设置为具体的数值后会在每次生成时使用此随机种子,一般用于复现特定生成结果使用(此参数仅 angel-vllm 镜像支持)。 |
Message
| 参数名称 | 必填 | 类型 | 描述 |
| role | 是 | String | 对话消息对应的角色,当前支持以下取值:system:表示系统提示(仅支持最开始出现一次)user:表示用户assistant:表示对话助手在 message 中必须是 user 与 assistant 交替出现(一问一答) |
| content | 是 | String | 对话消息的内容 |
十二、3. 输出参数
非流式情况,会返回 content-type: application/json 数据,格式如下:
| 参数名称 | 类型 | 描述 |
| id | String | 对话的唯一标识符 |
| choices | Array of Choice | 生成结果 |
| created | Integer | 对话开始的 Unix 时间戳 (秒级) |
| model | String | 对话使用的模型名称 |
| object | String | 本对象的类型,固定为 chat.completion |
| usage | Usage | token 数量统计信息:包含生成结果中的 token 数量 completion_tokens;提示词中的 token 数量 prompt_tokens;请求中的总 token 数量(提示词和生成结果)total_tokens; |
流式输出,会以 SSE 形式返回 content-type: text/event-stream; charset=utf-8 数据
流式输出每行返回的数据为 data: 分片,分片数据格式如下:
| 参数名称 | 类型 | 描述 |
| id | String | 对话的唯一标识符 |
| choices | Array of StreamChoice | 生成结果 |
| created | Integer | 对话开始的 Unix 时间戳 (秒级) |
| model | String | 对话使用的模型名称 |
| object | String | 本对象的类型,固定为 chat.completion.chunk |
流式输出会以 data: [DONE] 作为结尾,详见 示例。
十三、示例1 非流式请求
输入示例
curl -H "content-type: application/json" http://localhost:8501/v1/chat/completions -d '{"messages":[{"role": "user", "content": "你好"}], "temperature": 0.0}'
输出示例
{"id":"chatcmpl-4aeRgYwnaYe4RzmmcyKtYs","object":"chat.completion","created":1698291242,"model":"baichuan-13b-chat","choices":[{"index":0,"message":{"role":"assistant","content":"你好!有什么我能帮到你的吗?"},"finish_reason":"stop"}],"usage":{"prompt_tokens":4,"total_tokens":16,"completion_tokens":12}}
十四、示例2 流式请求
输入示例
curl -H "content-type: application/json" http://localhost:8501/v1/chat/completions -d '{"messages":[{"role": "user", "content": "你好"}], "temperature": 0.0, "stream": true}'
输出示例
data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": null}]}data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {"content": "你"}, "finish_reason": null}]}data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {"content": "好!"}, "finish_reason": null}]}data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {"content": "有什么我"}, "finish_reason": null}]}data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {"content": "能帮"}, "finish_reason": null}]}data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {"content": "到你的"}, "finish_reason": null}]}data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {"content": "吗?"}, "finish_reason": null}]}data: {"id": "chatcmpl-3fYW8fqN3YYMJiebiZgpzZ", "object": "chat.completion.chunk", "created": 1698290857, "model": "baichuan-13b-chat", "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]}data: [DONE]
十五、Python SDK
可以直接使用 openai 的 SDK 来调用本推理服务,示例如下:
import osfrom openai import OpenAI# 示例1:容器本地访问地址os.environ["OPENAI_BASE_URL"] = "http://127.0.0.1:8501/v1"# 示例2:外网访问地址,可以通过服务调用页面获取(使用openai sdk时,地址后面加上v1)os.environ["OPENAI_BASE_URL"] = "https://service-********.sh.tencentapigw.com:443/tione/v1"# 若服务启动设置了 –api-keys 参数或 OPENAI_API_KEYS 环境变量开启了鉴权,OPENAI_API_KEY 需要设置为任意一个可用的 api_key;否则可以任意填写os.environ["OPENAI_API_KEY"] = "EMPTY"
client = OpenAI()# 非流式请求示例:print("—– standard request —–")completion = client.chat.completions.create( model="model", messages=[ { "role": "user", "content": "你好", }, ], temperature=0.7, top_p=1.0, max_tokens=128,)print(completion.choices[0].message.content)
# 流式请求示例:print("—– streaming request —–")stream = client.chat.completions.create( model="model", messages=[ { "role": "user", "content": "你好", }, ], temperature=0.7, top_p=1.0, max_tokens=128, stream=True,)for chunk in stream: if not chunk.choices or not chunk.choices[0].delta.content: continue print(chunk.choices[0].delta.content, end="")print()
十六、命令行对话 Demo
你也可以使用 python 常用的 requests 库来请求对话接口,下面是一个命令行中与大模型推理服务进行对话交互的 Demo 示例:
import argparseimport requestsimport jsondef chat(messages): data = { "messages": messages, "temperature": args.temperature, "max_tokens": args.max_tokens, "top_p": args.top_p, "stream": True, # 开启流式输出 } header = { "Content-Type": "application/json", } if args.token: header["Authorization"] = f"Bearer {args.token}" response = requests.post(f"{args.server}/v1/chat/completions", json=data, headers=header, stream=True) # 设置 stream=True 参数获取实时数据流 if response.status_code != 200: print(response.json()) exit() result = "" print("Assistant: ", end = "", flush = True) for part in response.iter_lines(): if part: if "content" in part.decode("utf-8"): content = json.loads(part.decode("utf-8")[5:])["choices"][0]["delta"]["content"] # 字符串过滤 data: 之后转成 json 格式再取文本 result += content print(content, end = "", flush = True) print("\\n") return resultif __name__ == "__main__": parser = argparse.ArgumentParser(description="Chat CLI Demo.") parser.add_argument("–server", type=str, default="http://127.0.0.1:8501") parser.add_argument("–max-tokens", type=int, default=512) parser.add_argument("–temperature", type=float, default=0.7) parser.add_argument("–top_p", type=float, default=1.0) parser.add_argument("–system", type=str, default=None) parser.add_argument("–token", type=str, default=None) args = parser.parse_args() messages = [] if args.system: messages.append({"role": "system", "content": args.system}) while True: user_input = input("User: ") messages.append({"role": "user", "content": user_input}) response = chat(messages) messages.append({"role": "assistant", "content": response})