ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

使用 SkyPilot Pools 与 vLLM 实现零冷启动的批量文本分类推理

2026/9/16 19:54:42 拓冰建站 浏览量
使用 SkyPilot Pools 与 vLLM 实现零冷启动的批量文本分类推理 使用 SkyPilot Pools 与 vLLM 实现零冷启动的批量文本分类推理【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot本指南讲解 SkyPilotPools工作池这一核心特性它预先拉起一组常驻 Worker 并缓存好模型与依赖使后续提交的离线批量推理作业无需冷启动即可立即执行。你将掌握从创建池、提交 10 个并行分类作业、分区处理 IMDB 电影评论数据集、监控进度到清理资源的完整实战链路并深入理解SKYPILOT_JOB_RANK分区机制与 vLLM Python SDK 的调用细节。Pools 是什么为无冷启动的批量作业而生SkyPilot Pools 是一组预先配置好的常驻 Worker专门用于高效处理多个独立作业offline batch inference。与普通作业每次启动都要重新拉镜像、装依赖、下载模型不同池中的 Worker 在创建时就把运行环境全部准备完毕因此每个后续作业提交后可以直接在空闲 Worker 上执行。从源码看Pools 复用并扩展了 SkyPilot 的 Managed Jobs / SkyServe 调度体系任务在提交时通过task.service.pool标记为池任务见 CLI 校验逻辑sky jobs utils.py中维护了作业 → Worker 集群的分配映射job_id_on_pool_cluster、get_pool_submit_info等从而支持按池名取消、按池过滤查询等运维操作。Pools 的三大核心收益作业执行快Worker 在池创建阶段即完成预配置模型已加载/缓存作业提交后跳过一切初始化开销零设置开销每个作业直接复用 Worker 上已安装的依赖与已下载的模型文件无需重复安装或下载简单并行一条命令即可通过--num-jobs提交多个作业SkyPilot 自动将并发数限制在池内 Worker 数量以内超出部分进入队列排队由调度器按 Worker 空余情况逐个放行。示例全景三个文件完成一个端到端案例本示例用openai/gpt-oss-20b运行在 vLLM 上对 IMDB 影评做情感分类包含三个文件文件作用pool.yaml池配置声明 2 个 Worker、H100 GPU、vLLM 安装与模型预下载classify.yaml作业定义在池 Worker 上运行分类脚本classify.py分类脚本基于作业 rank 切分数据集并调用 vLLM SDK 推理整体流程为创建含 2 个 Worker 的池 → 提交 10 个分类作业并行处理 IMDB 数据集 → 每个作业按SKYPILOT_JOB_RANK处理数据集的一个分片 → 结果写入云存储桶。Step 1创建池 —— pool.yaml 逐段解读创建命令# 设置你的全局唯一桶名桶名必须全局唯一 export BUCKET_NAMEbatch-inference-results-${USER} # 创建名为 text-classify 的池 sky jobs pool apply --env BUCKET_NAME -p text-classify pool.yaml该命令将完成拉起 2 个带 H100 GPU 的 Worker、在每个 Worker 上安装 vLLM 与依赖、把云存储桶挂载到/results、预下载并缓存gpt-oss-20b模型。sky jobs pool apply是 Pools 的入口命令定义在 command.py当指定pool_yaml时创建/更新池配置若池已运行则把新配置应用到现有池。注意 YAML 中必须包含pool段否则 CLI 会直接报错The YAML file needs a pool section。pool.yaml 配置拆解# Pool configuration for batch text classification workdir: . envs: MODEL_NAME: openai/gpt-oss-20b BUCKET_NAME: # 设置你的全局唯一桶名必须全局唯一 # 云存储桶用于存放结果可选 # 若无云存储桶权限 # 1. 注释掉整个 file_mounts 段 # 2. 将 classify.yaml 中 --output-dir 改为本地路径如 ~/sky_workdir file_mounts: /results: name: ${BUCKET_NAME} mode: MOUNT resources: accelerators: H100:1 disk_size: 100 setup: | # 创建虚拟环境 uv venv --python 3.10 --seed source .venv/bin/activate # 安装 vLLM 与依赖 uv pip install vllm0.11.0 transformers4.57.1 # 安装分类脚本依赖 uv pip install datasets2.18.0 tqdm4.66.1 # 预下载模型缓存供所有作业复用 echo Downloading model ${MODEL_NAME}... huggingface-cli download ${MODEL_NAME} --local-dir /tmp/model echo Pool worker setup complete! pool: # 池内 Worker 数量 workers: 2关键配置点envs声明了池级环境变量。BUCKET_NAME在此留空由命令行--env BUCKET_NAME注入MODEL_NAME供 setup 脚本下载模型使用。file_mounts将云存储桶以MOUNT模式挂载到每个 Worker 的/results。桶名通过${BUCKET_NAME}引用环境变量因此必须全局唯一。resources.accelerators: H100:1每个 Worker 申请 1 张 H100 GPU示例输出中实际由 Kubernetes 后端调度到 H200属于不同集群的硬件差异。setupWorker 初始化脚本是消除冷启动的关键——把虚拟环境、vLLM 0.11.0、transformers、datasets 全部装好并用huggingface-cli download把模型下载到/tmp/model缓存。后续 10 个作业直接复用这份环境不需要再下载模型。pool.workers: 2池的 Worker 数量。SkyPilot 据此并发调度作业队列中等待的作业会在 Worker 空闲后自动放行。查看池状态sky jobs pool status text-classifysky jobs pool status命令定义在 command.py。示例输出Pools NAME VERSION UPTIME STATUS WORKERS text-classify 1 5m 39s READY 2/2 Pool Workers POOL_NAME ID VERSION LAUNCHED INFRA RESOURCES STATUS USED_BY text-classify 1 1 6 mins ago Kubernetes 1x(gpusH200:1, cpus4, mem16, ...) READY - text-classify 2 1 6 mins ago Kubernetes 1x(gpusH200:1, cpus4, mem16, ...) READY -输出分为两部分Pool 总览名称、配置版本、存活时间、状态、READY 2/2表示两个 Worker 全部就绪与 Worker 明细每个 Worker 的 ID、基础设施、资源配置与当前占用者USED_BY。状态变为READY后即可开始提交作业。Step 2提交批量分类作业 —— --num-jobs 与作业分区提交命令sky jobs launch -p text-classify --num-jobs 10 classify.yaml该命令会向池text-classify提交 10 个作业每个作业获得唯一的$SKYPILOT_JOB_RANK09每个作业基于该 rank 处理数据集的一个分区结果写入挂载的云存储桶。--num-jobs的值可以按需调整——作业越多并行度越高最多等于 Worker 数超出部分自动排队。CLI 在提交时支持--pool参数指定目标池相关解析逻辑见 command.py。classify.yaml作业如何落到 Worker 上resources: accelerators: H100:1 name: batch-classify run: | echo Starting classification job (rank ${SKYPILOT_JOB_RANK} of ${SKYPILOT_NUM_JOBS}) source .venv/bin/activate # 运行分类脚本 # 注意若没有云存储桶把 --output-dir 改为 ~/sky_workdir python classify.py \ --job-rank ${SKYPILOT_JOB_RANK} \ --num-jobs ${SKYPILOT_NUM_JOBS} \ --output-dir /results \ --model-path /tmp/model echo Classification complete! Results saved to S3 bucket mounted at /results/results_rank_${SKYPILOT_JOB_RANK}.jsonl作业定义非常轻量没有setup段依赖已由池准备好run只负责激活虚拟环境并执行脚本。两个关键环境变量由 SkyPilot 自动注入SKYPILOT_JOB_RANK当前作业在整批作业中的序号0 起始用于数据集分片与输出文件命名SKYPILOT_NUM_JOBS本批作业总数即--num-jobs的值。这两个变量在 SkyPilot 作业调度核心sky/jobs/controller.py、sky/jobs/server/core.py中按批次生成并注入每个作业是批量作业实现数据分治的契约。分区算法每个作业处理哪一段数据classify.py的calculate_partition函数classify.py负责把整个数据集均分到 N 个作业def calculate_partition(total_items, job_rank, num_jobs): items_per_job total_items // num_jobs remainder total_items % num_jobs # 把余数均摊到前几个作业 start_idx job_rank * items_per_job min(job_rank, remainder) end_idx start_idx items_per_job (1 if job_rank remainder else 0) return start_idx, end_idx每个作业拿到total_items // num_jobs条数据无法整除的余数部分分配给前remainder个作业保证每条评论恰好被处理一次、不重不漏。以默认--dataset-size 5000、10 个作业为例每个作业处理 500 条rank 04 各自多分到 1 条若总数无法整除。这正是多作业离线批处理的经典数据并行模式。Step 3监控作业进度查看作业队列sky jobs queue输出示例Fetching managed job statuses... Managed jobs In progress tasks: 4 PENDING, 2 RUNNING ID TASK NAME REQUESTED SUBMITTED TOT. DURATION JOB DURATION #RECOVERIES STATUS POOL 10 - batch-classify 1x[H200:1] 2 mins ago 2m 35s - 0 PENDING text-classify 9 - batch-classify 1x[H200:1] 2 mins ago 2m 35s - 0 PENDING text-classify 8 - batch-classify 1x[H200:1] 2 mins ago 2m 37s - 0 PENDING text-classify 7 - batch-classify 1x[H200:1] 2 mins ago 2m 10s 49s 0 SUCCEEDED text-classify 6 - batch-classify 1x[H200:1] 2 mins ago 2m 37s 17s 0 RUNNING text-classify (worker1) 5 - batch-classify 1x[H200:1] 2 mins ago 2m 42s 14s 0 RUNNING text-classify (worker2) 4 - batch-classify 1x[H200:1] 2 mins ago 2m 17s 49s 0 SUCCEEDED text-classify 3 - batch-classify 1x[H200:1] 2 mins ago 2m 45s - 0 PENDING text-classify 2 - batch-classify 1x[H200:1] 2 mins ago 1m 19s 1m 18s 0 SUCCEEDED text-classify 1 - batch-classify 1x[H200:1] 2 mins ago 1m 20s 1m 19s 0 SUCCEEDED text-classify这份输出清晰地展示了 Pools 的调度行为并发上限 Worker 数同一时刻只有 2 个作业处于RUNNING且恰好分别运行在text-classify (worker1)和text-classify (worker2)上其余作业保持PENDING排队完成一个放行一个POOL列显示作业归属的池名及实际分配的 Worker 编号。该列由sky/jobs/utils.py中的作业→Worker 映射表_get_job_id_to_worker_map生成排队的作业因尚未分配 Worker 而只有池名。查看日志与仪表盘# 查看指定作业的日志 sky jobs logs job-id # 启动 SkyPilot 仪表盘Web UI sky dashboard仪表盘同样提供队列、运行状态与日志的图形化视图适合批量作业的集中巡检。按池取消作业批量提交后若需要整体中止可以直接按池取消实现见 sky/jobs/utils.py 的cancel_jobs_by_poolsky jobs cancel --pool text-classifyStep 4查看分类结果作业完成后每个作业会在挂载桶中生成两个文件results_rank_N.jsonl逐条预测明细JSONL每行一条记录summary_rank_N.json该作业的准确率与吞吐统计。用云厂商 CLI 查看把BUCKET_NAME替换成你的桶名BUCKET_NAMEbatch-inference-results-${USER} # 替换成你的桶名 # AWS aws s3 ls s3://${BUCKET_NAME}/ aws s3 cp s3://${BUCKET_NAME}/summary_rank_0.json - # GCP gsutil ls gs://${BUCKET_NAME}/ gsutil cat gs://${BUCKET_NAME}/summary_rank_0.json结果文件的生成逻辑classify.py在推理完成后写两类文件classify.py明细文件results_rank_N.jsonl每条记录包含评论在数据集中的原始index、截断到 200 字符的评论文本、真实标签true_label、模型预测predicted_label以及correct布尔值{index: 12, text: This movie was a masterpiece..., true_label: positive, predicted_label: positive, correct: true}汇总文件summary_rank_N.json记录作业元信息与统计指标{ job_rank: 0, num_jobs: 10, start_idx: 0, end_idx: 500, total_processed: 500, correct_predictions: 468, accuracy: 93.6, elapsed_time_seconds: 42.1, throughput_reviews_per_sec: 11.88 }把 10 个作业的total_processed相加即得到整批处理量由于分区算法保证数据不重不漏全部作业的准确率加权平均即为整体准确率。Step 5清理资源sky jobs pool down text-classify该命令停止池中所有 Worker 并清理云资源避免持续计费。注意挂载桶中的结果文件会被保留不受池销毁影响。若不再需要全部池与作业sky jobs pool down -a可以一次性清理所有池CLI 在sky down --purge等全局操作时也会检查并提醒先终止在跑的池。附录无云存储桶的本地运行方式没有云存储桶权限时可以改用 Worker 本地存储在 pool.yaml 中注释掉整个file_mounts段不再挂载/results在 classify.yaml 中把--output-dir从/results改为~/sky_workdir。这样结果会落在各 Worker 本地需要自行用sky jobs logs job-id或进入 Worker 收集结果适用于快速验证或没有对象存储权限的环境。深入classify.py 的推理实现要点分类提示词与输出约束脚本为每条评论构造严格受限的提示词classify.py要求模型只输出一个标签You are a sentiment classifier. Classify the following movie review as either positive, negative, or neutral. Output ONLY the label, nothing else. Review: {text} Classification:超过 1000 字符的评论文本会被截断避免构造过长的提示词max_model_len2048的限制。vLLM 采样参数为分类任务做确定性推理sampling_params SamplingParams( temperature0.0, # 分类任务要求确定性输出 top_p1.0, max_tokens10, # 标签很短只需少量 token ) llm LLM( modelargs.model_path, dtypeauto, max_model_len2048, )temperature0.0使解码近乎贪婪保证分类结果可复现max_tokens10限制输出长度标签生成完成后 vLLM 自动停止模型直接加载/tmp/model池 setup 阶段预下载的缓存路径Worker 上无需联网再下载。批量推理与标签归一化llm.generate(prompts, sampling_params)一次性把该分区的所有提示词交给 vLLM由引擎内部做连续批处理continuous batching脚本侧无需手动分块——--batch-size参数在 vLLM 内部批处理下主要起兜底作用。推理完成后parse_classification把模型输出小写化并归一化为positive/negative/neutral三值无法识别的输出截断到前 20 字符保留原始信息避免异常输出污染统计。运行统计脚本统计每个作业的处理量、准确率correct / total * 100、耗时与吞吐reviews/sec并打印到日志同时写入summary_rank_N.json便于整批作业结束后统一汇总评估。更多池运维能力Pools 的 CLI 还提供下列运维手段定义于 command.py滚动/蓝绿更新sky jobs pool apply支持--mode rolling或--mode blue_green在池已运行时更新配置而无需整体重建在线扩缩容sky jobs pool apply -p text-classify --workers 5可在不重建池的情况下调整 Worker 数量从而控制并发度与成本查看池日志sky jobs pool logs pool-name查看池级日志。这些能力与--num-jobs配合可以让批量推理系统的并发吞吐按业务节奏弹性伸缩。小结本文完整演示了 SkyPilot Pools 的离线批量推理范式用 pool.yaml 一次性地把 GPU 资源、依赖环境、模型缓存和结果桶准备到位再通过--num-jobs与SKYPILOT_JOB_RANK实现数据分区并行配合队列监控、按池取消与一键清理构建了一个零冷启动、可扩展、可观测的文本分类流水线。该模式可以平移到翻译、摘要、代码生成、向量化等任何同模型、多数据的离线批处理场景。【免费下载链接】skypilotThe AI Compute Platform for frontier teams. SkyPilot turns fragmented AI compute into one AI supercomputer, so frontier AI teams build custom intelligence faster.项目地址: https://gitcode.com/GitHub_Trending/sk/skypilot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考