执行摘要
本 PR 是对 Rust 前端协议层的基础性重构,将字符串化的 tensor 元数据(dtype、endianness、modality)替换为强类型枚举(TensorDtype、NumpyDtype、MmModality)。通过泛型化的 WireArray<Dtype> 结构复用类型化元数据和内联存储验证逻辑,消除了多模态降低和 logprobs 解码中重复的表示,为 PR#55047 奠定了基础。
功能与动机
本 PR 是 PR#55047 的支持性重构。其动机在于替换字符串化的 tensor 元数据为强类型枚举,统一协议层和多模态模块的表示,消除重复的 TensorKind 和 ScalarType,提升类型安全性和代码一致性,便于后续功能扩展。
实现拆解
- 引入强类型 dtype 枚举:在
rust/src/engine-core-client/src/protocol/dtype.rs 中新增 TensorDtype(13 种类型)和 NumpyDtype(带字节序)枚举,实现了 Display/FromStr 以支持 MessagePack 序列化,并兼容 Torch dtype 名称和 NumPy 字节序描述符。
- 泛型化 WireArray 结构:在
rust/src/engine-core-client/src/protocol/tensor.rs 中,将 WireNdArray 泛型化为 WireArray<Dtype>,并定义 WireTensor = WireArray<TensorDtype> 和 WireNdArray = WireArray<NumpyDtype>。新增了 validate_inline() 方法用于统一的内联存储验证。
- 统一多模态模态键:在
rust/src/engine-core-client/src/protocol/multimodal.rs 中新增 MmModality 枚举,并在 rust/src/chat/src/multimodal.rs 中用其替换了本地的 MmLimitModality,统一了协议层和 chat 模块的模态表示。
- 简化多模态降低逻辑:在
rust/src/chat/src/multimodal/tensor.rs 中,移除了冗余的 TensorKind 枚举,其逻辑由 TensorDtype 的方法替代。KwargValue::Tensor 变体简化,切片和验证函数利用新的类型化结构。
- 更新 logprobs 解码:在
rust/src/engine-core-client/src/protocol/logprobs/array.rs 中,移除本地的 ScalarType 和 Endianness 枚举,改用全局的 TensorDtype 和 Endianness,简化了元数据解析。
- 测试与兼容性:新增了针对
NumpyDtype 解析和 WireArray dtype 契约的测试,验证了序列化格式与 Python 端的兼容性。
rust/src/engine-core-client/src/protocol/tensor.rs
核心变更:将 WireNdArray 泛型化为 WireArray,并定义了 WireTensor 和 WireNdArray 类型别名。
/// 泛型化的 Python ndarray/tensor wire 元组 (dtype, shape, data)。
/// Dtype 参数可以是 TensorDtype(用于 tensor)或 NumpyDtype(用于 ndarray)。
#[derive(Debug, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct WireArray<Dtype: Serialize + DeserializeOwned> {
pub dtype: Dtype,
pub shape: Vec<usize>,
pub data: WireArrayData,
}
impl<Dtype: From<TensorDtype> + Serialize + DeserializeOwned> WireArray<Dtype> {
/// 从原始字节构建 tensor/ndarray。
pub fn from_raw(dtype: impl Into<Dtype>, shape: Vec<usize>, data: Vec<u8>) -> Self {
Self::from_raw_bytes(dtype, shape, Bytes::from(data))
}
/// 从拥有的不可变原始视图缓冲区构建 tensor/ndarray。
pub fn from_raw_bytes(dtype: impl Into<Dtype>, shape: Vec<usize>, data: Bytes) -> Self {
Self { dtype: dtype.into(), shape, data: WireArrayData::RawView(data) }
}
}
/// Torch tensor wire 表示,dtype 为 TensorDtype。
pub type WireTensor = WireArray<TensorDtype>;
/// NumPy ndarray wire 表示,dtype 为 NumpyDtype。
pub type WireNdArray = WireArray<NumpyDtype>;
impl WireTensor {
/// 验证内联 tensor 的 shape 和存储。
pub fn validate_inline(&self) -> Result<(), String> {
let expected = self.shape.checked_numel()
.and_then(|numel| numel.checked_mul(self.dtype.element_size()))
.ok_or_else(|| format!("tensor shape or byte length overflows: {:?}", self.shape))?;
match &self.data {
WireArrayData::RawView(bytes) if bytes.len() == expected => Ok(()),
WireArrayData::RawView(bytes) => Err(format!(
"tensor byte length {} does not match expected {expected}", bytes.len()
)),
WireArrayData::AuxIndex(index) => {
Err(format!("inline tensor references auxiliary frame {index}"))
}
}
}
}
rust/src/chat/src/multimodal/tensor.rs
关键重构:移除了冗余的 TensorKind 枚举,简化了多模态降低逻辑,利用 WireTensor 的类型化 dtype。
/// 多模态 kwarg 值的表示,用于转换。
#[derive(Debug)]
pub(super) enum KwargValue {
/// 带行主序扁平数据和形状的张量。
/// 移除了 TensorKind,直接使用 WireTensor 的类型化 dtype。
Tensor { wire: WireTensor },
/// 非张量 kwarg 值,共享或按原样复制。
Passthrough(ProtocolKwargValue),
}
impl KwargValue {
// ... from_model_specific 和 from_f32_array 方法现在直接构造 Self::Tensor { wire }
// 而不是调用 Self::tensor(kind, wire) 辅助方法。
}
/// 沿第一轴切片扁平行主序张量。
fn slice_first_axis_range(
tensor: &WireTensor,
start: usize,
end: usize,
drop_axis: bool,
) -> Result<WireTensor> {
let shape = tensor.shape.as_slice();
// 现在调用 WireTensor::validate_inline() 进行验证
raw_tensor_bytes(tensor)?;
// 直接从 tensor.dtype 获取元素大小,无需传入 element_size 参数
let element_size = tensor.dtype.element_size();
// ... 切片逻辑
}
/// 获取张量的原始字节。
fn raw_tensor_bytes(tensor: &WireTensor) -> Result<&[u8]> {
// 使用 WireTensor::validate_inline() 进行统一验证
tensor.validate_inline().map_err(Error::Multimodal)?;
Ok(tensor.data.as_raw_view().expect("validated inline tensor"))
}
评论区精华
- claude[bot]: "This pull request is from a fork — automated review is disabled. A repository maintainer can comment
@claude review to run a one-time review."
- njhill: "APPROVED" (无额外评论)
风险与影响
- 协议兼容性:
TensorDtype 和 NumpyDtype 的 FromStr 实现需严格匹配 Python 端序列化的字符串格式,测试覆盖了主要情况,但未来新增的 dtype 变体可能导致反序列化失败。
- 序列化契约变更:
WireArray<Dtype> 的序列化格式变更需确保所有消费者(包括 Python 引擎核心)能正确处理类型化字段。测试(tensor_and_ndarray_preserve_their_dtype_wire_contracts)验证了兼容性。
- 重构引入的错误:移除
TensorKind 后,多模态降低逻辑依赖于 WireTensor 的 dtype 字段,若构造时 dtype 与实际数据不匹配,验证逻辑可能无法捕获所有类型错误。
关联脉络
- 本 PR 是 PR#55047 的支持性重构,为后续功能做准备。
- 与 PR#56058(修复 Rust 前端 HTTP 指标标签)和 PR#56018(统一 Rust 前端解析器选择)同属 Rust 前端模块的改进,体现了该模块近期向更强类型和一致性发展的趋势。
参与讨论