coglet 深度解析:Cog 机器学习预测服务器的纯 Rust 核心运行时
【免费下载链接】cogContainers for machine learning项目地址: https://gitcode.com/GitHub_Trending/co/cog
coglet 是 Cog 项目(Containers for machine learning)中负责预测服务执行的 Rust 核心库,它以"纯 Rust、零 Python 依赖"的方式实现了子进程隔离、并发槽位管理与高性能 IPC,是 Cog 预测服务器(coglet-python 的底层引擎)的心脏。阅读本文后,你将完整掌握 coglet 的父/子进程双通道架构、PredictionService/Orchestrator/Worker三大核心组件的工作原理、PermitPool槽位并发控制与桥接协议细节,以及健康状态机、取消与优雅关闭的完整行为语义。
coglet 在 Cog 体系中的定位
coglet 的核心定位写在其自身文档首行:它是 coglet 预测服务器的核心 Rust 库(Core Rust library for the coglet prediction server),纯 Rust 实现、不依赖任何 Python 库,Python 绑定则独立存在于 coglet-python 中。这种分层意味着:
- 预测服务器的全部核心逻辑(进程编排、并发控制、IPC 协议、HTTP 服务)都可以用 Rust 独立测试与演进;
- Python 侧只保留薄薄的 PyO3 绑定层,通过 lib.rs 暴露
serve()、active()、_run_worker()等入口; - 整个 crates 工作区(见 crates/README.md)由
coglet(核心库)与coglet-python(PyO3 绑定)两个 crate 组成,统一由 Cargo.toml 工作区清单管理。
从整体架构上看,coglet 实现了 Cog 的子进程隔离模型:HTTP 请求进入父进程,父进程把预测任务下发给子进程(worker),子进程内运行 Python 预测器(load()/setup()/predict())。这种模型带来三个核心收益:崩溃隔离(worker 崩溃可重启而父进程存活)、内存隔离(GPU 内存泄漏不会累积)、以及可按需 SIGKILL 的干净关闭能力。
总体架构:父进程编排 + 双通道 IPC
coglet 的核心架构可以浓缩为下面这张取自 crates/coglet/README.md 的模块图:
coglet ┌─────────────────────────────────────────────────────────────────┐ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ transport/http │ │ │ │ ┌──────────────┐ ┌─────────────────────────────────┐ │ │ │ │ │ server.rs │ │ routes.rs │ │ │ │ │ │ Axum setup │ │ /health, /predictions, /cancel │ │ │ │ │ └──────────────┘ └─────────────────────────────────┘ │ │ │ └───────────────────────────────┬─────────────────────────┘ │ │ │ │ │ ┌───────────────────────────────▼─────────────────────────┐ │ │ │ service.rs │ │ │ │ PredictionService: health, permits, state, webhooks │ │ │ └───────────────────────────────┬─────────────────────────┘ │ │ │ │ │ ┌────────────────────────┼────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌────────────────────┐ ┌──────────┐ │ │ │ permit/ │ │ orchestrator.rs │ │webhook.rs│ │ │ │ PermitPool │ │ Parent-side: │ │ Sender │ │ │ │ Slot alloc │ │ spawn, route │ │ Retry │ │ │ └─────────────┘ └─────────┬──────────┘ └──────────┘ │ │ │ │ │ ┌────────────────────────────▼────────────────────────────┐ │ │ │ bridge/ │ │ │ │ ┌──────────────┐ ┌─────────────┐ ┌────────────────┐ │ │ │ │ │ protocol.rs │ │ codec.rs │ │ transport.rs │ │ │ │ │ │ Message types│ │ JSON lines │ │ Unix sockets │ │ │ │ │ └──────────────┘ └─────────────┘ └────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ worker.rs │ │ │ │ Child-side: PredictHandler trait, run_worker loop │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘各层职责清晰可辨:
| 层次 | 模块 | 职责 |
|---|---|---|
| HTTP 传输层 | transport/http/server.rs、routes.rs | Axum 服务装配与路由处理:/health、/predictions、/cancel等 |
| 服务层 | service.rs | PredictionService:健康状态、许可池、预测状态、webhook 的统一所有者 |
| 编排层 | orchestrator.rs | 父进程侧:spawn 子进程、事件循环、消息路由 |
| 并发控制 | permit/pool.rs | PermitPool:槽位许可管理与分配 |
| Webhook | webhook.rs | WebhookSender:节流、重试 |
| IPC 桥 | bridge/protocol.rs、codec.rs、transport.rs | 消息类型、JSON lines 编解码、Unix socket 传输 |
| 子进程侧 | worker.rs | PredictHandlertrait、run_worker事件循环 |
双通道 IPC:控制通道与槽位套接字
父进程与 worker 之间并非单一通道,而是精心设计的两套通道(对应 protocol.rs 的注释说明):
- 控制通道(stdin/stdout,JSON lines):承载生命周期消息——
Init、Cancel、Shutdown、Healthcheck(父 → 子),以及Ready、Idle、Failed、Fatal、ShuttingDown(子 → 父)。一行一条 JSON 消息。 - 槽位套接字(Unix domain socket,每个 slot 一条):承载预测数据——
SlotRequest::Predict下发放,LogLine、OutputChunk、Metric、Done、Failed、Cancelled回传。每个槽位独立 socket,避免头端阻塞(head-of-line blocking)。
从源码实现看,slot socket 在父进程侧通过 NamedSocketTransport::create 创建,路径格式为{temp_dir}/coglet-{pid}/slot-{n}.sock;在 Linux 上还支持抽象命名空间(AbstractSocketTransport,无文件系统残留、自动清理)。worker 子进程侧则通过connect反连回这些 socket,随后父进程调用accept_connections完成握手。
目录结构:一个 crate,一套清晰分层
目录树 展示了源码组织的完整脉络(以下为节选并标注核心注释):
coglet/ └── src/ ├── lib.rs # Public API exports │ │ # Core Types ├── health.rs # Health, SetupStatus, SetupResult ├── prediction.rs # Prediction state machine ├── predictor.rs # PredictionResult, PredictionError, PredictionOutput ├── version.rs # VersionInfo │ │ # Service Layer ├── service.rs # PredictionService - lifecycle, state, webhooks ├── webhook.rs # WebhookSender, webhook types │ │ # Orchestrator (Parent Process) ├── orchestrator.rs # spawn_worker, OrchestratorHandle, event loop │ │ # Worker (Child Process) ├── worker.rs # run_worker, PredictHandler trait, SetupError │ │ # Concurrency Control ├── permit/ │ ├── pool.rs # PermitPool - slot permit management │ └── slot.rs # PredictionSlot - permit + prediction binding │ │ # IPC Bridge ├── bridge/ │ ├── protocol.rs # ControlRequest, ControlResponse, SlotRequest, SlotResponse │ ├── codec.rs # JsonCodec - newline-delimited JSON │ └── transport.rs # Unix socket transport, ChildTransportInfo │ │ # HTTP Transport └── transport/ └── http/ ├── server.rs # ServerConfig, serve() └── routes.rs # Route handlers, request/response types公共 API 导出集中在 lib.rs,包括Health、SetupResult、Prediction、PredictionStatus、PredictionResult、PredictionService、Orchestrator、PermitPool、InputValidator、run_worker等,并提供了install_crypto_provider()用于一次性安装 rustls 的 ring TLS 加密提供程序(必须在任何reqwest::Client创建前调用,可重复调用)。
三大核心组件深入
PredictionService:预测状态的唯一所有者
service.rs 中的PredictionService是"与传输层无关的预测生命周期管理服务",它统一管理:
- 健康状态:
Unknown → Starting → Ready/SetupFailed; - PermitPool + Orchestrator 引用:两者通过
OrchestratorState原子地一起设置(保证池与编排器总是同时就绪); - 活动预测:
DashMap<String, PredictionEntry>作为预测状态的单一事实来源(single source of truth); - 取消:
CancellationToken+ 编排器委托; - Webhook:从
Prediction的 mutation 方法(set_processing、set_succeeded等)触发,不维护双份状态。
典型的组装方式(取自 README 示例):
let service = PredictionService::new_no_pool() .with_health(Health::Starting) .with_version(version); // Later, after worker is ready: service.set_orchestrator(pool, handle).await; service.set_health(Health::Ready).await;几个值得注意的源码细节:
set_health(Health::Ready)在没有编排器时会静默忽略(源码中有明确 warn 日志),保证 READY 必须先有编排器;HealthSnapshot提供is_ready()与is_busy()(READY 但可用槽位为 0)判定;strip_and_validate_input()在单次锁获取内完成"剔除未知字段 + 校验 + 为省略的可选无默认值字段注入 null"三步(strip_validate_inject),保证 predict 与 train 两条路径的排序不变量完全一致;- 输入超过
MAX_INLINE_IPC_SIZE(6 MiB,见 protocol.rs)时,build_slot_request会把输入溢出写盘到/tmp/coglet/predictions/{id}/inputs/spill_*.json,worker 端rehydrate_input读盘、反序列化后立即删除该文件; - 同步预测使用
SyncPredictionGuard:HTTP 连接断开时 axum 丢弃响应 future,从而触发 guard 的 drop,进而调用service.cancel(id)同时触发 CancellationToken 与编排器取消;disarm()可在正常结束时解除武装。
Orchestrator:父进程侧的 worker 生命周期管理
orchestrator.rs 负责 spawn 子进程并维持其生命周期,README 给出了完整的启动流程:
spawn_worker(config) │ ├─▶ Create Unix socket transport (N slots) ├─▶ Spawn: python -c "import coglet; coglet.server._run_worker()" ├─▶ Send Init message via stdin ├─▶ Wait for worker to connect sockets ├─▶ Wait for Ready message (with timeout) ├─▶ Populate PermitPool with slot writers ├─▶ Spawn event loop task └─▶ Return OrchestratorReady {pool, schema, handle}事件循环统一处理来自 worker 的各种响应:
ControlResponse::Idle—— 槽位可接收下一个预测;ControlResponse::Failed—— 槽位被毒化(poisoned),标记不可用;SlotResponse::Log/Output/Done/Failed—— 路由到对应预测;- worker 崩溃—— 失败所有进行中的预测。
补充说明:事件循环还处理ControlResponse::Fatal(worker 不可恢复错误,父进程应毒化所有槽位并失败所有在途预测)、DroppedLogs(背压丢弃日志的系统诊断),以及HealthcheckResult(用户自定义健康检查结果)。此外upload_file实现了与 Python cog 的put_file_to_signed_endpoint一致的签名上传逻辑(PUT + Content-Type、跟随重定向、从 Location 头取最终 URL 并剥离查询参数)。
Worker:子进程侧的事件循环
worker.rs 是子进程侧实现,核心是PredictHandlertrait 与run_worker循环:
run_worker(handler, config) │ ├─▶ Connect to slot sockets (from env) ├─▶ Setup control channel (stdin/stdout) ├─▶ Run handler.setup() with log routing ├─▶ Send Ready {slots, schema} ├─▶ Enter event loop: │ - ControlRequest::Cancel → handler.cancel(slot) │ - ControlRequest::Shutdown → exit │ - SlotRequest::Predict → spawn prediction task └─▶ Exit on shutdown or all slots poisoned两个源码级防护机制值得注意:
- panic hook 即致命错误通道:worker 安装全局 panic hook(
install_panic_hook),任何 panic 都会尽力发送ControlResponse::Fatal { reason }给父进程,然后std::process::abort()。这意味着任意调用点的panic!/.expect()都会自动获得正确的致命行为,无需额外辅助代码; - 日志截断:worker 日志经
truncate_worker_log在 4 MiB 处按字符边界截断并追加[**** LOG LINE TRUNCATED AT 4 MiB ****]标记(protocol.rs 中的truncate_worker_log测试覆盖了长/短日志与多字节 UTF-8 场景),避免超大日志行引发 panic 或撑爆通道。
worker 进程内部的 Python 侧结构(见 crates/README.md)包含PythonPredictor(load()/setup()/predict())、SlotLogWriter(基于 ContextVar 的 stdout/stderr 路由)与Audit Hook(保护运行流、对用户覆写采用 Tee 模式),这些由 coglet-python/src 下的log_writer.rs、audit.rs、cancel.rs等实现。
PermitPool:基于槽位的并发控制
permit/pool.rs 实现了"槽位 + 许可"的并发控制模型:max_concurrency决定槽位数量,每个槽位同一时刻最多运行一个预测。README 给出的核心用法:
let pool = PermitPool::new(max_concurrency); // Add slot with its socket writer pool.add_permit(slot_id, writer); // Acquire permit (returns None if at capacity) let permit = pool.try_acquire()?; // Send prediction request permit.send(SlotRequest::Predict { id, input }).await?; // Return permit when done drop(permit);源码实现采用了typestate(类型状态)模式保证编译期状态转换安全,许可(permit)有三种状态类型:
| 状态 | 行为 |
|---|---|
PermitInUse | 正在运行预测;into_idle()转为空闲(drop 时归还池),into_poisoned()转为毒化(永久不归还) |
PermitIdle | 完成后 drop 自动归还池(除非池级 poison 标志已置位) |
PermitPoisoned | 永久失败,drop 时仅告警容量缩减 |
关键设计是毒化(poisoning)是池级属性:pool.poison(slot_id)无论槽位是空闲(在池中)还是在用(被预测持有),都会置位共享的AtomicBool标志;try_acquire会跳过已毒化许可,PermitIdle::drop看到标志后也不再归还。配套的SlotIdleToken机制确保只有 worker 确认槽位空闲后许可才归还——若 5 秒内未被消费会打印告警(ALERT_THRESHOLD),提示槽位可能无法回归池中。相关单元测试(pool_add_and_acquire、permit_orphaned_when_poisoned、pool_poison_idle_slot等)验证了这些语义。
桥接协议:一整套 JSON 消息类型
bridge/protocol.rs 定义了父子通信的全部消息类型,全部 JSON 序列化并使用{"type": "..."}判别字段(serde tag,snake_case)。以下是 README 中的完整清单:
控制通道(stdin/stdout)
ControlRequest(父 → 子):Init、Cancel、Shutdown(源码中还有Healthcheck);ControlResponse(子 → 父):Ready、Log、Idle、Failed、Cancelled、ShuttingDown(源码中还有WorkerLog、Fatal、DroppedLogs、HealthcheckResult)。
槽位通道(Unix socket)
SlotRequest(父 → 子):Predict(携带id、input或input_file、output_dir、context);SlotResponse(子 → 父):Log、Output、Done、Failed、Cancelled(源码中为LogLine、OutputChunk、Metric、Done、Failed、Cancelled、ProtocolVersion、FileOutput)。
README 给出的两个通道消息示例:
// Control Channel {"type": "init", "predictor_ref": "predict.py:Predictor", "num_slots": 2, ...} {"type": "cancel", "slot": "uuid"} {"type": "shutdown"} {"type": "ready", "slots": ["uuid1", "uuid2"], "schema": {...}} {"type": "log", "source": "stdout", "data": "Loading model..."} {"type": "idle", "slot": "uuid"} {"type": "failed", "slot": "uuid", "error": "Setup failed: ..."} {"type": "shutting_down"} // Slot Sockets { "type": "predict", "id": "pred_123", "input": { "prompt": "Hello" } } {"type": "log", "source": "stdout", "data": "Processing..."} {"type": "output", "output": "chunk"} {"type": "done", "id": "pred_123", "output": "Hello, world!", "predict_time": 0.5} {"type": "failed", "id": "pred_123", "error": "ValueError: ..."} {"type": "cancelled", "id": "pred_123"}几个增强细节:SlotId使用UUID v4而非数组索引,避免混淆与意外复用(SlotId文档注释明确说明这一点);Done消息携带predict_time与is_stream信号(predictor 返回 list/generator/iterator 时为 true,作为 schema 缺失时的流式输出兜底);Metric消息支持Replace/Increment/Append三种合并模式;SLOT_RESPONSE_PROTOCOL_VERSION = 1作为未来协议演进的显式标记。
HTTP 传输层与预测流程
HTTP 层由 transport/http/routes.rs 实现,核心端点包括:
GET /—— 服务发现根端点,返回cog_version、docs_url、openapi_url、predictions_url、predictions_idempotent_url、predictions_cancel_url等(支持训练时额外追加/trainings相关端点);GET /health(health_check)—— 返回status(含UNHEALTHY响应态)、setup(SetupResult)、version;POST /predictions(create_prediction)及其幂等变体POST/PUT /predictions/{prediction_id}(create_prediction_idempotent/create_prediction_with_id);DELETE /predictions/{id}或PUT /predictions/{id}/cancel(cancel_prediction)。
PredictionRequest支持id(可选幂等 ID)、input、context(dict[str, str],通过current_scope().context提供给预测器)、webhookURL 与webhook_events_filter。Webhook 发送器(webhook.rs)的默认配置为:非终止更新节流500ms(可用环境变量COG_THROTTLE_RESPONSE_INTERVAL覆盖)、终止 webhook 最多重试12 次、退避基数100ms、重试状态码429/500/502/503/504,并支持WEBHOOK_AUTH_TOKENBearer 认证与 W3C Trace Context 透传。
结合 crates/README.md 的预测流程图,一次完整预测的链路为:HTTP 请求 → 父进程POST /predictions→ 获取槽位许可并注册预测 → 经槽位 socket 下发SlotRequest::Predict→ worker 设置 ContextVar、调用predict()→ 流式回传Log/Output→ 最终回传Done {id, output, predict_time}→ 父进程更新预测状态、释放许可、发送 webhook → 返回200 OK。启动序列则是 HTTP 服务先行启动(健康检查返回 STARTING),编排器异步完成"建 socket → spawn worker → 发 Init → 等 Ready → 填充 PermitPool → 启动事件循环 → 置 READY"。
行为语义:健康状态、预测状态、取消与关闭
健康状态机
Unknown ──▶ Starting ──┬──▶ Ready ◀──▶ Busy │ └──▶ SetupFailed ──▶ Defunct各状态语义(health.rs 中Health枚举定义):
- Unknown:初始状态,健康检查返回 body 中的状态;
- Starting:
setup()进行中; - Ready:可接受预测;
- Busy:READY 但所有槽位都在使用中(新预测返回 HTTP 409);
- SetupFailed:
setup()抛异常; - Defunct:不可恢复错误。
HealthResponse额外包含瞬态Unhealthy(用户自定义健康检查失败,不存储为内部状态);SetupResult记录started_at、completed_at、status(starting/succeeded/failed)与捕获的logs。对应测试可见 integration-tests/tests/healthcheck*.txtar 系列。
预测状态机
Starting ──▶ Processing ──┬──▶ Succeeded ├──▶ Failed └──▶ CanceledPredictionStatus(prediction.rs)提供is_terminal()判定(Succeeded/Failed/Canceled 为终态),并围绕Prediction实现了流式事件广播(start/output/log/metric/completed五类事件)、事件回放(历史容量默认 1024,可用环境变量COG_STREAM_HISTORY_CAPACITY调整,设为 0 可禁用回放)以及用户指标(支持点路径键如timing.preprocess与三种合并模式)。预测状态快照build_state_snapshot是 webhook 载荷、GET 响应与终止响应的统一数据源,终止状态时自动合并predict_time指标。
取消链路
README 给出了完整的取消流程:
- 调用
HTTP DELETE /predictions/{id}或PUT /predictions/{id}/cancel; - 父进程发送
ControlRequest::Cancel { slot }; - worker 调用
handler.cancel(slot); - 同步预测:SIGUSR1 在 Python 侧触发
KeyboardInterrupt; - 异步预测:对 asyncio 任务调用
future.cancel(); - 预测以
SlotResponse::Cancelled返回。
源码侧,service.cancel(id)会同时触发CancellationToken(供 Rust 侧观察者如上传任务使用)并委托编排器发送取消;crates/coglet-python/src/cancel.rs实现了同步预测的 SIGUSR1 取消支持。集成测试覆盖见 integration-tests/tests/cancel_async_prediction.txtar、cancel_sync_prediction.txtar 与 cancel_repeated.txtar。
关闭路径
优雅关闭(SIGTERM +await_explicit_shutdown):
- 停止接受新预测;
- 等待在途预测完成;
- 发送
ControlRequest::Shutdown; - worker 响应
ShuttingDown后退出; - 父进程退出。
立即关闭(SIGTERM 未携带该标志):
- 发送
ControlRequest::Shutdown; - 取消在途预测;
- 退出。
worker 崩溃:
- 控制通道关闭;
- 事件循环检测到,失败所有在途预测;
- 健康状态转为 Defunct。
槽位毒化
若某个槽位 socket 出错(写失败等),该槽位被标记为毒化,不再接收新预测;若所有槽位均被毒化,worker 退出。源码用SlotOutcome枚举在类型层面保证"毒化槽位只能产生 Failed、不能产生 Idle":
enum SlotOutcome { Idle(SlotId), // Ready for next prediction Poisoned { slot, error }, // Slot is dead }关键设计决策回顾
结合 crates/README.md 的总结,coglet 的五个关键设计决策构成了其整体风格:
- 子进程隔离:worker 独立进程运行,换取崩溃隔离、内存隔离与干净关闭;
- 单 worker 模式:始终恰好一个 worker 子进程,不做动态扩缩容——父进程轻量,重活全在 worker 中;
- 槽位并发:每个槽位一对 Unix socket,
max_concurrency决定槽位数,许可制保证单槽单预测; - ContextVar 日志路由:异步预测可能 spawn 子任务,ContextVar 沿调用栈传播预测 ID,即使从派生任务也能正确路由日志;
- Audit Hook 保护:用户代码可能替换
sys.stdout,audit hook 拦截后用TeeWriter包装其流,既保留日志路由又让用户代码按预期工作。
结语与深入阅读
coglet 以"纯 Rust 核心 + PyO3 薄绑定"的分层方式,把 Cog 预测服务器的进程编排、并发控制、IPC 协议与 HTTP 服务全部沉淀为可独立测试的 Rust 代码,是理解 Cog 子进程隔离模型的必读入口。想进一步深入,可以按以下路径继续:
- crates/coglet/README.md 与 crates/README.md:架构总览与本组件文档;
- crates/coglet/src/service.rs、orchestrator.rs、worker.rs:三大核心组件实现;
- crates/coglet/src/bridge/protocol.rs、permit/pool.rs:桥接协议与并发控制细节(含丰富的单元测试);
- crates/coglet-python/src/lib.rs 与 crates/coglet-python/README.md:PyO3 绑定与 Python 侧 worker 桥;
- integration-tests/tests:以
coglet_*.txtar、healthcheck*.txtar、cancel_*.txtar为代表的端到端行为验证用例。
【免费下载链接】cogContainers for machine learning项目地址: https://gitcode.com/GitHub_Trending/co/cog
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考