[Rust Frontend] Support HTTP RL weight synchronization (#56567)
Co-authored-by: Bugen Zhao <i@bugenzhao.com> Co-authored-by: Codex <noreply@openai.com> Signed-off-by: Wang Zupeng <zupenwang@gmail.com> Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Zupeng Wang committed
Sep 14, 2026 at 11:37 UTC
7632f767f0d896a902566acab29775816b39a92d
8 files changed
+483
-6
rust/README.md
+11
@@ -43,6 +43,17 @@ For example:
43
VLLM_USE_RUST_FRONTEND=1 vllm serve Qwen/Qwen3-0.6B
44
```
45
46
+### RL weight synchronization
47
+
48
+With `VLLM_SERVER_DEV_MODE=1`, the Rust frontend supports the HTTP weight-transfer
49
+lifecycle used by `HTTPVLLMWeightSyncClient`: initialization, starting an update,
50
+transferring weights, and finishing the update. It also supports draft-model
51
+updates and `/update_weight_version` and `/weight_info` for version tracking.
52
+
53
+For trainer-side usage, see the [IPC](../examples/rl/rlhf_http_ipc.py) and
54
+[NCCL](../examples/rl/rlhf_http_nccl.py) examples. Configure the weight-transfer
55
+backend on the server and pause generation during updates, as shown there.
56
+
57
### External Engine
58
59
`vllm-rs serve` can be run standalone with `--data-parallel-size-local 0` when the Python engines
rust/src/server/src/error.rs
+7
@@ -2,6 +2,7 @@
2
// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
use axum::Json;
5
+use axum::extract::rejection::JsonRejection;
6
use axum::http::StatusCode;
7
use axum::response::{IntoResponse, Response};
8
use thiserror_ext::{AsReport as _, Construct, Macro};
@@ -70,6 +71,12 @@ impl ApiError {
71
}
72
}
73
74
+impl From<JsonRejection> for ApiError {
75
+ fn from(error: JsonRejection) -> Self {
76
+ Self::json_parse_error(error.body_text())
77
+ }
78
+}
79
+
80
impl IntoResponse for ApiError {
81
fn into_response(self) -> Response {
82
(self.status_code(), Json(self.to_error_response())).into_response()
rust/src/server/src/routes.rs
+23
@@ -17,6 +17,7 @@ mod server_info;
17
mod sleep;
18
mod tokenize;
19
mod version;
20
+mod weight_transfer;
21
mod world_size;
22
23
use std::sync::Arc;
@@ -139,6 +140,28 @@ fn build_router_with_options(
140
.route("/reset_mm_cache", post(cache::reset_mm_cache))
141
.route("/reset_encoder_cache", post(cache::reset_encoder_cache))
142
.route("/collective_rpc", post(collective_rpc::collective_rpc))
143
+ .route(
144
+ "/init_weight_transfer_engine",
145
+ post(weight_transfer::init_weight_transfer_engine),
146
+ )
147
+ .route(
148
+ "/start_weight_update",
149
+ post(weight_transfer::start_weight_update),
150
+ )
151
+ .route(
152
+ "/start_draft_weight_update",
153
+ post(weight_transfer::start_draft_weight_update),
154
+ )
155
+ .route("/update_weights", post(weight_transfer::update_weights))
156
+ .route(
157
+ "/finish_weight_update",
158
+ post(weight_transfer::finish_weight_update),
159
+ )
160
+ .route(
161
+ "/update_weight_version",
162
+ post(weight_transfer::update_weight_version),
163
+ )
164
+ .route("/weight_info", get(weight_transfer::weight_info))
165
.route("/abort_requests", post(abort_requests::abort_requests))
166
.route("/sleep", post(sleep::sleep))
167
.route("/wake_up", post(sleep::wake_up))
rust/src/server/src/routes/abort_requests.rs
+1
-1
@@ -22,7 +22,7 @@ pub async fn abort_requests(
22
State(state): State<Arc<AppState>>,
23
body: Result<Json<AbortRequestsRequest>, JsonRejection>,
24
) -> Result<StatusCode, ApiError> {
25
- let Json(body) = body.map_err(|error| ApiError::json_parse_error(error.body_text()))?;
25
+ let Json(body) = body?;
26
// Empty/missing `request_ids` aborts all in-flight requests.
27
let request_ids = body.request_ids.unwrap_or_default();
28
rust/src/server/src/routes/collective_rpc.rs
+1
-1
@@ -36,7 +36,7 @@ pub async fn collective_rpc(
36
State(state): State<Arc<AppState>>,
37
body: Result<Json<CollectiveRpcRequest>, JsonRejection>,
38
) -> Result<Json<CollectiveRpcResponse>, ApiError> {
39
- let Json(body) = body.map_err(|error| ApiError::json_parse_error(error.body_text()))?;
39
+ let Json(body) = body?;
40
let method = body.method.ok_or_else(|| {
41
ApiError::invalid_request(
42
"Missing 'method' in request body".to_string(),
rust/src/server/src/routes/openai/utils/validated_json.rs
+1
-4
@@ -5,7 +5,6 @@
5
//! Variation of https://github.com/lightseekorg/smg/blob/main/crates/protocols/src/validated.rs
6
7
use axum::Json;
8
-use axum::extract::rejection::JsonRejection;
8
use axum::extract::{FromRequest, Request};
9
use serde::de::DeserializeOwned;
10
use validator::Validate;
@@ -30,9 +29,7 @@ where
29
type Rejection = ApiError;
30
31
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
33
- let Json(mut data) = Json::<T>::from_request(req, state)
34
- .await
35
- .map_err(|err: JsonRejection| ApiError::json_parse_error(err.body_text()))?;
32
+ let Json(mut data) = Json::<T>::from_request(req, state).await?;
33
34
data.normalize();
35
rust/src/server/src/routes/tests.rs
+249
@@ -6113,6 +6113,13 @@ async fn admin_routes_are_hidden_when_dev_mode_is_disabled() {
6113
("POST", "/reset_prefix_cache"),
6114
("POST", "/reset_mm_cache"),
6115
("POST", "/reset_encoder_cache"),
6116
+ ("POST", "/init_weight_transfer_engine"),
6117
+ ("POST", "/start_weight_update"),
6118
+ ("POST", "/start_draft_weight_update"),
6119
+ ("POST", "/update_weights"),
6120
+ ("POST", "/finish_weight_update"),
6121
+ ("POST", "/update_weight_version"),
6122
+ ("GET", "/weight_info"),
6123
] {
6124
let response = app
6125
.clone()
@@ -6132,6 +6139,248 @@ async fn admin_routes_are_hidden_when_dev_mode_is_disabled() {
6139
engine_task.abort_and_join().await;
6140
}
6141
6142
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6143
+#[serial]
6144
+async fn weight_transfer_routes_support_the_http_training_lifecycle() {
6145
+ let (mut app, engine_task) = test_admin_app_with_engine_script(|dealer, push| {
6146
+ boxed_test_future(async move {
6147
+ let mut calls = Vec::new();
6148
+ for _ in 0..10 {
6149
+ let utility = recv_engine_message(dealer).await;
6150
+ assert_eq!(utility[0].as_ref(), &[0x03]);
6151
+ let payload = decode_value(&utility[1]).expect("decode utility");
6152
+ let array = payload.as_array().expect("utility array");
6153
+ let call_id = array[1].as_u64().expect("call id");
6154
+ let args: serde_json::Value =
6155
+ rmpv::ext::from_value(array[3].clone()).expect("decode args");
6156
+ calls.push(format!("{} {args}", array[2].as_str().expect("method")));
6157
+ let result = if array[2] == Value::from("get_weight_version") {
6158
+ utility_result_value("step-2")
6159
+ } else if array[2] == Value::from("collective_rpc") {
6160
+ utility_result_value(vec![(), ()])
6161
+ } else {
6162
+ utility_none_result()
6163
+ };
6164
+ send_outputs(push, utility_outputs(call_id, result)).await;
6165
+ }
6166
+ expect_test::expect![[r#"
6167
+ [
6168
+ "collective_rpc [\"init_weight_transfer_engine\",null,[],{\"init_info\":{\"master_address\":\"127.0.0.1\",\"master_port\":29500,\"rank_offset\":1,\"world_size\":3}}]",
6169
+ "collective_rpc [\"start_weight_update\",null,[],{}]",
6170
+ "collective_rpc [\"update_weights\",null,[],{\"update_info\":{\"names\":[\"model.embed_tokens.weight\"],\"dtype_names\":[\"bfloat16\"],\"shapes\":[[32,16]],\"is_checkpoint_format\":true}}]",
6171
+ "collective_rpc [\"update_weights\",null,[],{\"update_info\":[{\"rank\":0,\"ipc_handles_pickled\":\"opaque-handle-0\"},{\"rank\":1,\"ipc_handles_pickled\":\"opaque-handle-1\"}]}]",
6172
+ "collective_rpc [\"finish_weight_update\",null,[],{}]",
6173
+ "set_weight_version [\"step-1\"]",
6174
+ "collective_rpc [\"start_draft_weight_update\",null,[],{}]",
6175
+ "collective_rpc [\"finish_weight_update\",null,[],{}]",
6176
+ "set_weight_version [\"step-2\"]",
6177
+ "get_weight_version []",
6178
+ ]
6179
+ "#]].assert_debug_eq(&calls);
6180
+ })
6181
+ })
6182
+ .await;
6183
+
6184
+ let mut responses = Vec::new();
6185
+ for (method, path, body) in [
6186
+ (
6187
+ "POST",
6188
+ "/init_weight_transfer_engine",
6189
+ Some(json!({
6190
+ "init_info": {"master_address": "127.0.0.1", "master_port": 29500,
6191
+ "rank_offset": 1, "world_size": 3}
6192
+ })),
6193
+ ),
6194
+ ("POST", "/start_weight_update", None),
6195
+ (
6196
+ "POST",
6197
+ "/update_weights",
6198
+ Some(json!({
6199
+ "update_info": {"names": ["model.embed_tokens.weight"],
6200
+ "dtype_names": ["bfloat16"], "shapes": [[32, 16]],
6201
+ "is_checkpoint_format": true}
6202
+ })),
6203
+ ),
6204
+ (
6205
+ "POST",
6206
+ "/update_weights",
6207
+ Some(json!({
6208
+ "update_info": [{"rank": 0, "ipc_handles_pickled": "opaque-handle-0"},
6209
+ {"rank": 1, "ipc_handles_pickled": "opaque-handle-1"}]
6210
+ })),
6211
+ ),
6212
+ (
6213
+ "POST",
6214
+ "/finish_weight_update",
6215
+ Some(json!({"weight_version": "step-1"})),
6216
+ ),
6217
+ ("POST", "/start_draft_weight_update", None),
6218
+ ("POST", "/finish_weight_update", None),
6219
+ (
6220
+ "POST",
6221
+ "/update_weight_version",
6222
+ Some(json!({"new_version": "step-2"})),
6223
+ ),
6224
+ ("GET", "/weight_info", None),
6225
+ ] {
6226
+ let mut request = Request::builder().method(method).uri(path);
6227
+ let body = match body {
6228
+ Some(body) => {
6229
+ request = request.header("content-type", "application/json");
6230
+ Body::from(body.to_string())
6231
+ }
6232
+ None => Body::empty(),
6233
+ };
6234
+ let response =
6235
+ app.call(request.body(body).expect("build request")).await.expect("call app");
6236
+ assert_eq!(response.status(), StatusCode::OK, "{path}");
6237
+ let body = to_bytes(response.into_body(), usize::MAX).await.expect("read body");
6238
+ responses.push(
6239
+ serde_json::from_slice::<serde_json::Value>(&body)
6240
+ .expect("decode json")
6241
+ .to_string(),
6242
+ );
6243
+ }
6244
+ engine_task.await.expect("mock engine task");
6245
+ expect_test::expect![[r#"
6246
+ [
6247
+ "{\"message\":\"Weight transfer initialized\"}",
6248
+ "{\"message\":\"Weight update started\"}",
6249
+ "{\"message\":\"Weights updated\"}",
6250
+ "{\"message\":\"Weights updated\"}",
6251
+ "{\"message\":\"Weight update finished\"}",
6252
+ "{\"message\":\"Draft weight update started\"}",
6253
+ "{\"message\":\"Weight update finished\"}",
6254
+ "{\"success\":true,\"new_version\":\"step-2\"}",
6255
+ "{\"weight_version\":\"step-2\"}",
6256
+ ]
6257
+ "#]]
6258
+ .assert_debug_eq(&responses);
6259
+}
6260
+
6261
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6262
+#[serial]
6263
+async fn weight_transfer_routes_reject_invalid_payloads_before_engine_calls() {
6264
+ let (mut app, engine_task) = test_admin_app_with_engine_script(|dealer, _push| {
6265
+ boxed_test_future(async move {
6266
+ let message = recv_engine_message(dealer).await;
6267
+ panic!("invalid payload reached engine: {message:?}");
6268
+ })
6269
+ })
6270
+ .await;
6271
+
6272
+ for (path, body) in [
6273
+ ("/init_weight_transfer_engine", "{"),
6274
+ ("/init_weight_transfer_engine", "{}"),
6275
+ ("/init_weight_transfer_engine", r#"{"init_info":null}"#),
6276
+ ("/init_weight_transfer_engine", r#"{"init_info":[]}"#),
6277
+ ("/update_weights", "{"),
6278
+ ("/update_weights", "{}"),
6279
+ ("/update_weights", r#"{"update_info":null}"#),
6280
+ ("/update_weights", r#"{"update_info":1}"#),
6281
+ ("/update_weights", r#"{"update_info":[{},null]}"#),
6282
+ ("/finish_weight_update", "{"),
6283
+ ("/finish_weight_update", r#"{"weight_version":1}"#),
6284
+ ("/update_weight_version", "{}"),
6285
+ ("/update_weight_version", r#"{"new_version":null}"#),
6286
+ ] {
6287
+ let response = app
6288
+ .call(
6289
+ Request::builder()
6290
+ .method("POST")
6291
+ .uri(path)
6292
+ .header("content-type", "application/json")
6293
+ .body(Body::from(body))
6294
+ .expect("build request"),
6295
+ )
6296
+ .await
6297
+ .expect("call app");
6298
+ assert_eq!(response.status(), StatusCode::BAD_REQUEST, "{path}: {body}");
6299
+ }
6300
+ engine_task.abort_and_join().await;
6301
+}
6302
+
6303
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6304
+#[serial]
6305
+async fn weight_transfer_routes_do_not_commit_version_when_finish_fails() {
6306
+ let (mut app, engine_task) = test_admin_app_with_engine_script(|dealer, push| {
6307
+ boxed_test_future(async move {
6308
+ let utility = recv_engine_message(dealer).await;
6309
+ let payload = decode_value(&utility[1]).expect("decode utility");
6310
+ let array = payload.as_array().expect("utility array");
6311
+ assert_eq!(array[2], Value::from("collective_rpc"));
6312
+ assert_eq!(
6313
+ array[3].as_array().expect("args")[0],
6314
+ Value::from("finish_weight_update")
6315
+ );
6316
+ send_outputs(
6317
+ push,
6318
+ UtilityCallOutput {
6319
+ output: UtilityOutput {
6320
+ call_id: array[1].as_u64().expect("call id").into(),
6321
+ failure_message: Some("weight reload failed".to_string()),
6322
+ result: None,
6323
+ },
6324
+ ..Default::default()
6325
+ }
6326
+ .into(),
6327
+ )
6328
+ .await;
6329
+ let utility = recv_engine_message(dealer).await;
6330
+ let payload = decode_value(&utility[1]).expect("decode utility");
6331
+ let array = payload.as_array().expect("utility array");
6332
+ assert_eq!(array[2], Value::from("get_weight_version"));
6333
+ send_outputs(
6334
+ push,
6335
+ utility_outputs(
6336
+ array[1].as_u64().expect("call id"),
6337
+ utility_result_value("step-0"),
6338
+ ),
6339
+ )
6340
+ .await;
6341
+ })
6342
+ })
6343
+ .await;
6344
+
6345
+ let response = app
6346
+ .call(
6347
+ Request::builder()
6348
+ .method("POST")
6349
+ .uri("/finish_weight_update")
6350
+ .header("content-type", "application/json")
6351
+ .body(Body::from(r#"{"weight_version":"step-1"}"#))
6352
+ .expect("build request"),
6353
+ )
6354
+ .await
6355
+ .expect("call app");
6356
+ assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
6357
+ let body = to_bytes(response.into_body(), usize::MAX).await.expect("read body");
6358
+ let error: serde_json::Value = serde_json::from_slice(&body).expect("decode json");
6359
+ assert!(
6360
+ error["error"]["message"]
6361
+ .as_str()
6362
+ .expect("error message")
6363
+ .contains("weight reload failed")
6364
+ );
6365
+
6366
+ let response = app
6367
+ .call(
6368
+ Request::builder()
6369
+ .uri("/weight_info")
6370
+ .body(Body::empty())
6371
+ .expect("build request"),
6372
+ )
6373
+ .await
6374
+ .expect("call app");
6375
+ assert_eq!(response.status(), StatusCode::OK);
6376
+ let body = to_bytes(response.into_body(), usize::MAX).await.expect("read body");
6377
+ assert_eq!(
6378
+ serde_json::from_slice::<serde_json::Value>(&body).expect("decode json"),
6379
+ json!({"weight_version": "step-0"})
6380
+ );
6381
+ engine_task.await.expect("mock engine task");
6382
+}
6383
+
6384
// ========================= Stop string tests =========================
6385
6386
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
rust/src/server/src/routes/weight_transfer.rs
new
+190
@@ -0,0 +1,190 @@
1
+// SPDX-License-Identifier: Apache-2.0
2
+// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+use std::sync::Arc;
5
+
6
+use axum::Json;
7
+use axum::extract::State;
8
+use axum::extract::rejection::JsonRejection;
9
+use serde::{Deserialize, Serialize};
10
+use serde_json::Value as JsonValue;
11
+
12
+use crate::error::{ApiError, invalid_request};
13
+use crate::state::AppState;
14
+use crate::utils::utility_call_error;
15
+
16
+#[derive(Deserialize)]
17
+pub(crate) struct InitWeightTransferRequest {
18
+ init_info: Option<JsonValue>,
19
+}
20
+
21
+#[derive(Deserialize)]
22
+pub(crate) struct UpdateWeightsRequest {
23
+ update_info: Option<JsonValue>,
24
+}
25
+
26
+#[derive(Default, Deserialize)]
27
+pub(crate) struct FinishWeightUpdateRequest {
28
+ weight_version: Option<String>,
29
+}
30
+
31
+#[derive(Deserialize)]
32
+pub(crate) struct UpdateWeightVersionRequest {
33
+ new_version: String,
34
+}
35
+
36
+#[derive(Serialize)]
37
+pub(crate) struct MessageResponse {
38
+ message: &'static str,
39
+}
40
+
41
+#[derive(Serialize)]
42
+pub(crate) struct UpdateWeightVersionResponse {
43
+ success: bool,
44
+ new_version: String,
45
+}
46
+
47
+#[derive(Serialize)]
48
+pub(crate) struct WeightInfoResponse {
49
+ weight_version: String,
50
+}
51
+
52
+/// Initialize weight transfer for RL training.
53
+pub async fn init_weight_transfer_engine(
54
+ State(state): State<Arc<AppState>>,
55
+ body: Result<Json<InitWeightTransferRequest>, JsonRejection>,
56
+) -> Result<Json<MessageResponse>, ApiError> {
57
+ let Json(body) = body?;
58
+ let init_info = body.init_info.filter(JsonValue::is_object).ok_or_else(|| {
59
+ invalid_request!(
60
+ param = Some("init_info"),
61
+ "'init_info' must be a JSON object"
62
+ )
63
+ })?;
64
+
65
+ state
66
+ .engine_core_client()
67
+ .init_weight_transfer_engine(init_info)
68
+ .await
69
+ .map_err(|error| utility_call_error("init_weight_transfer_engine", error))?;
70
+
71
+ Ok(Json(MessageResponse {
72
+ message: "Weight transfer initialized",
73
+ }))
74
+}
75
+
76
+/// Start a weight update transaction.
77
+pub async fn start_weight_update(
78
+ State(state): State<Arc<AppState>>,
79
+) -> Result<Json<MessageResponse>, ApiError> {
80
+ state
81
+ .engine_core_client()
82
+ .start_weight_update()
83
+ .await
84
+ .map_err(|error| utility_call_error("start_weight_update", error))?;
85
+
86
+ Ok(Json(MessageResponse {
87
+ message: "Weight update started",
88
+ }))
89
+}
90
+
91
+/// Start a draft-model weight update transaction.
92
+pub async fn start_draft_weight_update(
93
+ State(state): State<Arc<AppState>>,
94
+) -> Result<Json<MessageResponse>, ApiError> {
95
+ state
96
+ .engine_core_client()
97
+ .start_draft_weight_update()
98
+ .await
99
+ .map_err(|error| utility_call_error("start_draft_weight_update", error))?;
100
+
101
+ Ok(Json(MessageResponse {
102
+ message: "Draft weight update started",
103
+ }))
104
+}
105
+
106
+/// Update model weights with backend-specific transfer metadata.
107
+pub async fn update_weights(
108
+ State(state): State<Arc<AppState>>,
109
+ body: Result<Json<UpdateWeightsRequest>, JsonRejection>,
110
+) -> Result<Json<MessageResponse>, ApiError> {
111
+ let Json(body) = body?;
112
+ let update_info = body
113
+ .update_info
114
+ .filter(|info| {
115
+ info.is_object()
116
+ || info.as_array().is_some_and(|items| items.iter().all(JsonValue::is_object))
117
+ })
118
+ .ok_or_else(|| {
119
+ invalid_request!(
120
+ param = Some("update_info"),
121
+ "'update_info' must be a JSON object or a list of per-worker JSON objects",
122
+ )
123
+ })?;
124
+
125
+ state
126
+ .engine_core_client()
127
+ .update_weights(update_info)
128
+ .await
129
+ .map_err(|error| utility_call_error("update_weights", error))?;
130
+
131
+ Ok(Json(MessageResponse {
132
+ message: "Weights updated",
133
+ }))
134
+}
135
+
136
+/// Finish a weight update transaction and optionally set the weight version.
137
+pub async fn finish_weight_update(
138
+ State(state): State<Arc<AppState>>,
139
+ body: Result<Option<Json<FinishWeightUpdateRequest>>, JsonRejection>,
140
+) -> Result<Json<MessageResponse>, ApiError> {
141
+ // HTTPVLLMWeightSyncClient omits the body when no version is supplied.
142
+ let request = body?.map(|Json(request)| request).unwrap_or_default();
143
+
144
+ let client = state.engine_core_client();
145
+ client
146
+ .finish_weight_update()
147
+ .await
148
+ .map_err(|error| utility_call_error("finish_weight_update", error))?;
149
+ if let Some(version) = request.weight_version {
150
+ client
151
+ .set_weight_version(&version)
152
+ .await
153
+ .map_err(|error| utility_call_error("set_weight_version", error))?;
154
+ }
155
+
156
+ Ok(Json(MessageResponse {
157
+ message: "Weight update finished",
158
+ }))
159
+}
160
+
161
+/// Update the weight version identifier.
162
+pub async fn update_weight_version(
163
+ State(state): State<Arc<AppState>>,
164
+ body: Result<Json<UpdateWeightVersionRequest>, JsonRejection>,
165
+) -> Result<Json<UpdateWeightVersionResponse>, ApiError> {
166
+ let Json(body) = body?;
167
+ state
168
+ .engine_core_client()
169
+ .set_weight_version(&body.new_version)
170
+ .await
171
+ .map_err(|error| utility_call_error("set_weight_version", error))?;
172
+
173
+ Ok(Json(UpdateWeightVersionResponse {
174
+ success: true,
175
+ new_version: body.new_version,
176
+ }))
177
+}
178
+
179
+/// Get current weight metadata.
180
+pub async fn weight_info(
181
+ State(state): State<Arc<AppState>>,
182
+) -> Result<Json<WeightInfoResponse>, ApiError> {
183
+ let weight_version = state
184
+ .engine_core_client()
185
+ .get_weight_version()
186
+ .await
187
+ .map_err(|error| utility_call_error("get_weight_version", error))?;
188
+
189
+ Ok(Json(WeightInfoResponse { weight_version }))
190
+}