mistral.rs Python 绑定多模型实战:Runner 管理多模型、model_id 路由与卸载重载机制
【免费下载链接】mistral.rsFast, flexible LLM inference项目地址: https://gitcode.com/GitHub_Trending/mi/mistral.rs
本文基于 mistral.rs 仓库的官方示例 multi_model_example.py 及其对应文档 multi-model-example.md,系统讲解如何在 Python 侧使用Runner完成多模型场景下的五项核心操作:通过Which描述符加载文本/多模态模型、按model_id向指定模型发送请求、管理默认模型、以及模型的卸载与重载。读完后你可以掌握:多模型Runner的构造方式、list_models/get_default_model_id等模型管理 API 的语义,以及底层引擎(engine)映射、UnloadedModelState状态保存等实现细节,从而在有限显存下按需调度多个模型。
1. 多模型能力总览
在 mistral.rs 中,一个Runner实例内部维护着一组并行的推理引擎(每个模型对应一个 engine),并记录一个“默认模型”ID。其核心数据结构定义在 mistralrs-core/src/lib.rs:
engines:model_id -> 引擎实例的映射,list_models()返回的即该映射的全部 key;default_engine_id:全局默认模型 ID,请求未指定model_id时派发到它;- 每个模型有明确的状态机,由
ModelStatus枚举表达(见 mistralrs-core/src/lib.rs):Loaded、Unloaded、Reloading三种取值。
Python 侧的Runner通过 PyO3 将上述能力暴露出来。相关方法集中在 mistralrs-pyo3/src/lib.rs 的Runner实现中,完整类型签名可在 mistralrs.pyi 中查阅。Rust SDK 侧的对应包装在 mistralrs/src/model.rs,其中有一组明确的“Multi-Model Management Methods”注释区块,包含list_models、get_default_model_id、set_default_model_id、add_model、remove_model、unload_model、reload_model、is_model_loaded、list_models_with_status九个方法,与 Python API 一一对应。
官方示例使用的两个模型(与文档保持一致):
| 类别 | 模型 ID | 架构枚举 |
|---|---|---|
| 多模态 | google/gemma-4-E4B-it | MultimodalArchitecture.Gemma4 |
| 文本 | Qwen/Qwen3-4B | Architecture.Qwen3 |
2. 用 Which 描述符构造 Runner
Runner的构造入口是which参数,其类型为Which枚举,定义在 mistralrs-pyo3/src/which.rs。每个变体代表一类加载方式,示例中用到两种:
Which.Plain(model_id, arch, ...):加载纯文本模型。arch为Option<Architecture>,Architecture枚举(which.rs)覆盖 Mistral、Gemma、Llama、Phi 系列、Qwen2/Qwen3/Qwen3Moe/Qwen3Next、GLM4、DeepSeek、GptOss 等;Which.MultimodalPlain(model_id, arch, ...):加载多模态模型。arch为Option<MultimodalArchitecture>,该枚举(which.rs)包含 Phi3V、Qwen2VL、Gemma3、Llama4、Gemma4、Voxtral 等。
此外还有Embedding、XLora、Lora、GGUF、XLoraGGUF、LoraGGUF、GGML及对应 XLora/Lora 变体、DiffusionPlain、Speech等。每个变体都支持topology、dtype、auto_map_params、hf_cache_path、imatrix等可选参数;从源码结构看,Architecture/MultimodalArchitecture枚举会经由From实现转换为 core 层的NormalLoaderType/MultimodalLoaderType(which.rs),最终交给模型加载器。
Runner的公共构造参数中,in_situ_quant表示加载时即时量化(in-situ quantization)。示例统一使用"Q4K",即将 BF16 权重加载后立刻量化为 4-bit GGUF 风格格式以节省显存——这在同时驻留多个模型时尤为关键。
3. 示例一:Runner 与 model_id 参数
完整代码(与 multi_model_example.py 一致):
from mistralrs import ( Runner, Which, ChatCompletionRequest, Architecture, MultimodalArchitecture, ) def example_runner_with_model_id(): """Demonstrate using Runner with model_id in requests.""" # Create a runner with Gemma 4 E4B multimodal model runner = Runner( which=Which.MultimodalPlain( model_id="google/gemma-4-E4B-it", arch=MultimodalArchitecture.Gemma4, ), in_situ_quant="Q4K", ) # List available models model_ids = runner.list_models() print("Available models:", model_ids) # Get default model default_model = runner.get_default_model_id() print(f"Default model: {default_model}") # Send request with specific model_id messages = [{"role": "user", "content": "Hello, how are you?"}] request = ChatCompletionRequest(messages=messages, model="default") if model_ids: # Request to specific model response = runner.send_chat_completion_request( request=request, model_id=model_ids[0] ) print(f"Response from {model_ids[0]}:", response.choices[0].message.content) # Request without model_id (uses default) response = runner.send_chat_completion_request(request=request) print("Response from default model:", response.choices[0].message.content)要点解析:
runner.list_models()返回当前engines映射中所有模型 ID 的列表,底层直接遍历引擎字典的 key(mistralrs-core/src/lib.rs);ChatCompletionRequest(messages=..., model="default")使用 OpenAI 兼容的请求结构,model字段是请求体内部的名字,而真正决定派发目标的是send_chat_completion_request的model_id关键字参数——PyO3 签名为send_chat_completion_request(request, model_id=None)(mistralrs-pyo3/src/lib.rs),传None时落到默认模型;- 该返回值类型为
Either[ChatCompletionResponse, ChatCompletionStreamer]:请求中stream=False时得到完整响应对象,stream=True时得到可迭代的流式对象。
4. 示例二:模型管理操作(列表、状态、默认模型切换)
def example_model_management(): """Demonstrate model management operations.""" runner = Runner( which=Which.Plain( model_id="Qwen/Qwen3-4B", arch=Architecture.Qwen3, ), in_situ_quant="Q4K", ) # List models with their status print("Initial models with status:", runner.list_models_with_status()) # Get default model current_default = runner.get_default_model_id() print(f"Current default model: {current_default}") # Check if a model is loaded model_ids = runner.list_models() if model_ids: is_loaded = runner.is_model_loaded(model_ids[0]) print(f"Is {model_ids[0]} loaded? {is_loaded}") # In a multi-model setup, you could change the default if model_ids and len(model_ids) > 1: runner.set_default_model_id(model_ids[1]) print(f"Changed default model to: {model_ids[1]}")各 API 的语义(对应 mistralrs-pyo3/mistralrs.pyi 中的声明):
list_models_with_status() -> list[tuple[str, str]]:返回(model_id, status)列表,status 字符串来自ModelStatus的Display实现,取值为loaded/unloaded/reloading(mistralrs-core/src/lib.rs);get_default_model_id() -> str | None:读取default_engine_id,若尚未设置则返回None;is_model_loaded(model_id) -> bool:区分“已加载”与“已卸载但仍登记”两种状态;set_default_model_id(model_id):将默认模型切换为指定 ID。底层实现会先校验目标模型存在于engines中,否则返回Model {id} not found错误,随后更新default_engine_id并打日志Default model changed: ...(mistralrs-core/src/lib.rs)。
5. 示例三:卸载与重载(显存管理核心机制)
def example_unload_reload(): """Demonstrate model unloading and reloading.""" runner = Runner( which=Which.MultimodalPlain( model_id="google/gemma-4-E4B-it", arch=MultimodalArchitecture.Gemma4, ), in_situ_quant="Q4K", ) model_ids = runner.list_models() if not model_ids: print("No models loaded") return model_id = model_ids[0] print(f"Initial status: {runner.list_models_with_status()}") # Unload the model to free memory # Note: This preserves the model configuration for later reload print(f"Unloading model: {model_id}") runner.unload_model(model_id) # Check status after unload print(f"Status after unload: {runner.list_models_with_status()}") print(f"Is {model_id} loaded? {runner.is_model_loaded(model_id)}") # Reload the model when needed print(f"Reloading model: {model_id}") runner.reload_model(model_id) # Check status after reload print(f"Status after reload: {runner.list_models_with_status()}")这是多模型场景中最有实用价值的一组 API,其底层机制值得展开:
卸载(unload_model)实现于 mistralrs-core/src/lib.rs,流程为:
- 若模型已在
unloaded_models中,直接报ModelAlreadyUnloaded错误; - 从
engines中移除该模型,并构造UnloadedModelState保存其loader_config(模型来源、dtype、device、ISQ 配置、chat template 等)、scheduler_config、engine_config(KV cache / prefix cache 开关、tool callbacks、search embedding model 等)、mcp_client_config与mistralrs_config——即“保配置、弃权重”; - 向引擎发送
Request::Terminate信号,释放该模型占用的显存; - 若被卸载的恰是当前默认模型,则自动把
default_engine_id切到剩余引擎中的第一个,保证后续无model_id的请求依然可派发。
重载(reload_model)实现于 mistralrs-core/src/lib.rs:
- 用
reloading_models集合做重入保护,避免并发重复加载(重复调用会报ModelReloading); - 取回
UnloadedModelState,通过LoaderBuilder依据保存的配置重新构建 loader,并调用load_model_from_hf按原 dtype、device、ISQ 量化设置重新加载权重; - 源码注释指出,当请求经
get_sender()路由到一个已卸载的模型时也会自动触发重载,因此unload_model后即使不手动reload_model,下一次向该模型发请求也会自动恢复(代价是等待重新加载)。
另外,Python API 还提供list_unloaded_models()用于列出所有“已卸载但仍登记”的模型 ID(mistralrs-pyo3/src/lib.rs 中Runner的实现)。
6. 示例四:针对指定模型的流式输出
def example_streaming_with_models(): """Demonstrate streaming responses from specific models.""" runner = Runner( which=Which.Plain( model_id="Qwen/Qwen3-4B", arch=Architecture.Qwen3, ), in_situ_quant="Q4K", ) messages = [{"role": "user", "content": "Tell me a short story"}] request = ChatCompletionRequest(messages=messages, model="default", stream=True) model_ids = runner.list_models() if model_ids: # Stream from specific model stream = runner.send_chat_completion_request( request=request, model_id=model_ids[0] ) print(f"Streaming from {model_ids[0]}:") for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) print() # New line after streaming要点:
stream=True写在ChatCompletionRequest中,send_chat_completion_request因此返回ChatCompletionStreamer(Either的右分支);- 每个 chunk 的结构与 OpenAI 流式响应一致:增量内容位于
chunk.choices[0].delta.content,可能为空(如 role 片段),因此示例中先判空再打印; - 流式与非流式共用同一个
model_id路由参数,多模型场景下可按需对任一模型开启流式。
7. 示例五:多模态 + 文本模型组合,以及 Rust 侧的动态多模型对照
def example_multi_model_setup(): """ Example showing a real multi-model setup with multimodal and text models. This example loads: - Multimodal model: google/gemma-4-E4B-it - Text model: Qwen/Qwen3-4B """ # Load a multimodal model first runner = Runner( which=Which.MultimodalPlain( model_id="google/gemma-4-E4B-it", arch=MultimodalArchitecture.Gemma4, ), in_situ_quant="Q4K", ) print("Initial models:", runner.list_models()) # Add a text model dynamically (if add_model is available) # runner.add_model( # model_id="qwen", # which=Which.Plain( # model_id="Qwen/Qwen3-4B", # arch=Architecture.Qwen3, # ), # in_situ_quant="Q4K", # ) # print("After add_model:", runner.list_models()) # Send a request to gemma messages = [{"role": "user", "content": "What is 2 + 2?"}] request = ChatCompletionRequest(messages=messages, model="default") response = runner.send_chat_completion_request(request) print(f"Gemma response: {response.choices[0].message.content}")注意示例中对runner.add_model的注释写法(if add_model is available):Rust SDK 层确实提供了动态添加模型的方法add_model(model_id, pipeline, method, config)(mistralrs/src/model.rs),但 Python 绑定当前暴露的动态多模型入口有限,因此该示例以注释形式保留了调用形态,供后续版本可用时直接启用。
真正“同进程、双模型”的完整形态,仓库中另有 Rust 端官方示例 mistralrs/examples/advanced/multi_model/main.rs,它展示了MultiModelBuilder的典型用法(可运行:cargo run --release --example multi_model -p mistralrs):
// 模型 ID 为 HuggingFace 路径,alias 为 API 请求中使用的短 ID const GEMMA_MODEL_ID: &str = "google/gemma-4-E4B-it"; const QWEN_MODEL_ID: &str = "Qwen/Qwen3-4B"; const GEMMA_ALIAS: &str = "gemma-multimodal"; const QWEN_ALIAS: &str = "qwen-text"; let model = MultiModelBuilder::new() .add_model_with_alias( GEMMA_ALIAS, MultimodalModelBuilder::new(GEMMA_MODEL_ID) .with_auto_isq(IsqBits::Four) .with_logging(), ) .add_model_with_alias( QWEN_ALIAS, TextModelBuilder::new(QWEN_MODEL_ID).with_auto_isq(IsqBits::Four), ) .with_default_model(GEMMA_ALIAS) .build() .await?;该示例同时演示了list_models()、get_default_model_id()、list_models_with_status()在同一 Builder 上的调用,与 Python 侧 API 语义完全一致(如IsqBits::Four对应 Python 的in_situ_quant="Q4K"的自动 ISQ 路径)。这印证了多模型架构的核心设计:模型 ID 是路由键,alias 是请求侧的友好名,默认模型保证无路由请求有明确落点。
8. Runner 多模型 API 速查
结合 mistralrs.pyi 与 mistralrs-pyo3/src/lib.rs 的实现,PythonRunner上与本主题相关的多模型 API 汇总如下:
| API | 返回/作用 | 底层依据 |
|---|---|---|
list_models() | 所有已登记模型 ID 列表 | 遍历engines的 key(mistralrs-core/src/lib.rs) |
list_models_with_status() | (model_id, "loaded"/"unloaded"/"reloading")列表 | ModelStatus枚举(mistralrs-core/src/lib.rs) |
list_unloaded_models() | 仅“已卸载”的模型 ID | unloaded_models映射 |
get_default_model_id() | 当前默认模型 ID,可能为None | default_engine_id读写锁保护字段 |
set_default_model_id(model_id) | 切换默认模型,模型不存在时报错 | mistralrs-core/src/lib.rs |
is_model_loaded(model_id) | 是否处于已加载状态 | 区分engines与unloaded_models |
unload_model(model_id) | 终止引擎、释放显存、保留配置 | mistralrs-core/src/lib.rs |
reload_model(model_id) | 按保存的UnloadedModelState重新加载;被请求自动路由触发时也会执行 | mistralrs-core/src/lib.rs |
send_chat_completion_request(request, model_id=None) | 向指定/默认模型发送 OpenAI 兼容请求,支持流式 | mistralrs-pyo3/src/lib.rs |
9. 实践小结
- 路由三层结构:
Which决定“怎么加载”,model_id决定“请求发给谁”,default_engine_id兜底“不指定时发给谁”。三者分离使得同一 Runner 内可以混载文本模型与多模态模型; - 显存预算下的调度策略:
in_situ_quant="Q4K"降低单模型驻留成本,unload_model+reload_model实现“热登记、冷加载”,且重载对请求方透明(自动触发),适合按会话轮转模型的 Agent 场景; - 状态可观测:
list_models_with_status()是排查“请求落在未加载模型上导致长时间重载等待”的首选诊断手段; - 跨语言一致性:Python API 与 Rust
MultiModelBuilder(mistralrs/examples/advanced/multi_model/main.rs)共享同一 core 层实现(mistralrs-core/src/lib.rs),行为可互相印证。
延伸阅读路径:Python 示例 examples/python/multi_model_example.py、Rust 多模型示例 mistralrs/examples/advanced/multi_model/main.rs、PyO3 绑定 mistralrs-pyo3/src/lib.rs、core 层多模型管理 mistralrs-core/src/lib.rs、类型存根 mistralrs-pyo3/mistralrs.pyi。
【免费下载链接】mistral.rsFast, flexible LLM inference项目地址: https://gitcode.com/GitHub_Trending/mi/mistral.rs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考