Skip to content

Commit 219491b

Browse files
committed
fix(loader): degrade unsupported fastsafetensors capabilities
1 parent 42c68d0 commit 219491b

10 files changed

Lines changed: 1452 additions & 346 deletions

File tree

docs/backend/server_arguments.md

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -205,34 +205,87 @@ The following legacy options and environment variables were removed and must no
205205

206206
### FastSafeTensors loader configuration
207207

208-
When `LOAD_METHOD=fastsafetensors`, RTP-LLM uses the config-driven `AutoLoader`.
208+
When `LOAD_METHOD=fastsafetensors`, or when the default `auto` mode selects the
209+
FastSafeTensors path, RTP-LLM uses the config-driven `AutoLoader`. RTP checks
210+
the installed package capabilities before loading: the full capability set uses
211+
bounded `per-expert` delivery, a package without `dim0_split_templates` falls
212+
back to the higher-memory `full-stacked` compatibility path, and a package
213+
without `local_copyout_filter` continues with full materialization and RTP
214+
consumer-side filtering. A missing package/`AutoLoader`, an import/ABI failure,
215+
an unmet AUTO prerequisite, or an insufficient memory preflight falls back to
216+
`scratch`. These compatibility paths apply to both `auto` and an explicit
217+
`LOAD_METHOD=fastsafetensors`, so package age alone does not fail model startup.
218+
The two optional keywords control independent optimizations:
219+
220+
| Capability | Present | Missing |
221+
|---|---|---|
222+
| `local_copyout_filter` | rank-local copy-out | full materialization, RTP consumer filtering |
223+
| `dim0_split_templates` | bounded `per-expert` MoE delivery | `full-stacked` MoE delivery |
224+
225+
When a degraded FastSafeTensors mode remains usable, RTP logs
226+
`requested_mode`, `effective_mode` and `degraded_reason`. A scratch fallback
227+
contains `falls back to scratch`; package absence is INFO and other fallback
228+
causes are WARNING. CI or image builds that require both optimizations must
229+
install the matching wheel and treat a missing capability as a packaging
230+
failure. Set `RTP_LLM_EXPECT_FASTSAFETENSORS_TIER=per-expert` for the installed
231+
wheel contract test to turn a lower tier into a test failure; supported tiers
232+
are `scratch`, `consumer-filter`, `full-stacked`, and `per-expert`.
233+
209234
Pass the standard fastsafetensors configuration as either an inline JSON string
210-
or a JSON file path. Inline JSON has higher priority when both are set:
235+
or a JSON file path. The installed FastSafeTensors version defines the precise
236+
configuration defaults and precedence:
211237

212238
```bash
213-
# Inline JSON string
214-
export FASTSAFETENSORS_CONFIG_JSON='{"loader":"base","base":{"copier_type":"nogds"}}'
239+
# Inline JSON string; progress is controlled by the upstream parallel config.
240+
export FASTSAFETENSORS_CONFIG_JSON='{"loader":"base","base":{"copier_type":"nogds"},"parallel":{"use_tqdm_on_load":true}}'
215241

216242
# JSON file path; the file contains the same JSON object
217243
export FASTSAFETENSORS_CONFIG=/path/to/fastsafetensors.json
218244
```
219245

246+
The same configuration also affects `auto` selection. RTP reads
247+
`estimated_peak_device_bytes` from the installed package; missing or invalid
248+
values use the historical `3 × max checkpoint shard` estimate. Larger buffers,
249+
queues or producer counts can raise `transient_mem` enough for `auto` to choose
250+
`scratch`. Inspect the `fastsafetensor memory check` log and its `enough` field.
251+
220252
For compatibility with existing development environments,
221-
`FASTSAFETENSORS_NOGDS=1` remains supported. Before constructing `AutoLoader`,
222-
RTP-LLM directly overrides `FASTSAFETENSORS_CONFIG_JSON` with
253+
`FASTSAFETENSORS_NOGDS=1` remains supported. Before memory preflight or
254+
constructing `AutoLoader`, RTP-LLM overrides `FASTSAFETENSORS_CONFIG_JSON`
255+
process-wide with
223256
`{"loader":"base","base":{"copier_type":"nogds"}}`. This compatibility switch
224-
therefore takes priority over other fastsafetensors configuration. Prefer one
225-
of the standard configuration variables above for new deployments.
257+
therefore remains in effect for subsequent loaders in the same process. Prefer
258+
one of the standard configuration variables above for new deployments. When
259+
`FASTSAFETENSORS_CONFIG` is also set, the final precedence remains an upstream
260+
package contract; current pinned wheels prefer the inline JSON value.
226261

227262
Stacked MoE checkpoints use bounded-memory per-expert delivery by default: the
228263
source rank slices the stacked tensor first, then every rank broadcasts one
229-
expert at a time. The higher-memory full-stacked path is retained only for
230-
controlled performance comparisons:
264+
expert at a time. The higher-memory full-stacked path is a temporary
265+
compatibility rollback for wheels or deployments that cannot use the bounded
266+
split path, and it may also be used for controlled performance comparisons:
231267

232268
```bash
233269
export RTP_FASTSAFETENSORS_STACKED_MOE_MODE=full-stacked
234270
```
235271

236-
The accepted values are `per-expert` (default) and `full-stacked`. This RTP
237-
switch only selects how stacked MoE tensors are delivered; ordinary tensors
238-
continue to use the FastSafeTensors bucket and rank-local-copy settings.
272+
The accepted values are `per-expert` (default) and `full-stacked`; an empty
273+
value also selects the default. `full-stacked` adds a conservative extra shard
274+
to the FastSafeTensors memory preflight because it materializes a whole stacked
275+
tensor before RTP clones expert slices. A passive downgrade logs a warning with
276+
`degraded_reason`; an explicit request is reported as the selected mode. The
277+
additional warning is emitted only when the checkpoint actually contains raw
278+
stacked MoE tensors. Use `LOAD_METHOD=scratch` as the more conservative
279+
rollback. This transitional RTP switch only selects stacked MoE delivery.
280+
Bucket size, copier/backend, queue depth, producer count, loading progress and
281+
tensor ordering are otherwise owned by the installed FastSafeTensors
282+
configuration; rank-local copy-out is supplied by RTP's local checkpoint-key
283+
predicate.
284+
285+
`RTP_FASTSAFETENSORS_STACKED_MOE_MODE` is a transitional, environment-only
286+
switch: it has no command-line flag, is not shown by `--help`, and is not part
287+
of the startup config dump. It is read only when the FastSafeTensors path is
288+
considered. Values are case-sensitive and use a hyphen; any non-empty value
289+
other than `per-expert` or `full-stacked` raises `ValueError` during
290+
FastSafeTensors selection. It has no effect for `LOAD_METHOD=scratch` or for
291+
weights that cannot use the FastSafeTensors path.

docs/release/breaking-changes.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,41 @@
44

55
---
66

7+
## FastSafeTensors configuration ownership moves to AutoLoader
8+
9+
**Introduced in:** [PR #1354](https://github.com/alibaba/rtp-llm/pull/1354).
10+
11+
**Summary:** RTP no longer constructs a private `ParallelLoader` variant or
12+
forces its loader tuning through constructor keywords. Backend selection,
13+
buffer size, shared-memory copier, producer/queue depth, tensor ordering and
14+
loading progress are read from the installed FastSafeTensors configuration.
15+
RTP supplies only rank-local key selection and the transitional stacked-MoE
16+
delivery policy.
17+
18+
**Impact and migration:** Previous RTP code forced a 2 GiB read buffer,
19+
selected shm versus nogds from `FASTSAFETENSORS_NOGDS`, and enabled the loading
20+
progress bar. New deployments should express equivalent intent through
21+
`FASTSAFETENSORS_CONFIG_JSON`; the exact keys are versioned upstream. For the
22+
current pinned wheel, a minimal nogds/progress example is:
23+
24+
```json
25+
{"loader":"base","base":{"copier_type":"nogds"},"parallel":{"use_tqdm_on_load":true}}
26+
```
27+
28+
At startup, degraded-but-usable FastSafeTensors paths report
29+
`requested_mode`, `effective_mode` and `degraded_reason`. Scratch fallbacks use
30+
the `falls back to scratch` marker. Missing optional APIs independently disable
31+
bounded per-expert delivery or rank-local copy-out; a missing package,
32+
AutoLoader/import failure, unmet AUTO prerequisite or insufficient memory
33+
preflight selects scratch.
34+
35+
**Rollback:** Set `LOAD_METHOD=scratch` for the conservative native loader, or
36+
temporarily set `RTP_FASTSAFETENSORS_STACKED_MOE_MODE=full-stacked` when the
37+
installed AutoLoader lacks bounded dim-0 splitting and sufficient GPU memory is
38+
available.
39+
40+
---
41+
742
## JIT cache unified local root and remote snapshot boundary
843

944
**Introduced in:** [PR #1112](https://github.com/alibaba/rtp-llm/pull/1112) (JIT remote cache).

rtp_llm/model_loader/ffn_weight.py

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,57 @@ def _get_expert_weights(self) -> List[CkptWeightInfo]:
361361
for idx in range(len(self.weights))
362362
]
363363

364+
def _raw_stacked_tensor_names(self, layer_id: Optional[int]) -> List[str]:
365+
"""Return concrete raw stacked keys, or empty for per-expert templates."""
366+
367+
if not self.stacked_ckpt_keys or not self.weights:
368+
return []
369+
names = []
370+
for ckpt_weight in self.weights:
371+
# A checkpoint template that names ``expert_id`` is already a
372+
# per-expert layout. Formatting it as a raw stacked key would
373+
# either raise KeyError or misclassify expert 0 as a stacked tensor.
374+
if "{expert_id" in ckpt_weight.name:
375+
return []
376+
names.append(ckpt_weight.tensor_name(layer_id))
377+
return names
378+
379+
def _has_raw_stacked_tensors(self, tensor_source, layer_id: Optional[int]) -> bool:
380+
"""Return whether every raw tensor required by this atomic weight exists."""
381+
382+
names = self._raw_stacked_tensor_names(layer_id)
383+
return bool(names) and all(tensor_source.has_tensor(name) for name in names)
384+
385+
def uses_stacked_expert_keys(self, database, layer_id: Optional[int]) -> bool:
386+
"""Return whether the checkpoint layout requires logical expert keys.
387+
388+
This decision must use the immutable checkpoint database. A
389+
TensorCollector populated by AutoLoader contains logical expert keys
390+
even when the underlying checkpoint stores raw stacked tensors.
391+
"""
392+
393+
return self._has_raw_stacked_tensors(database, layer_id)
394+
395+
def _has_logical_expert_tensors(
396+
self,
397+
tensor_source: TensorSource,
398+
layer_id: Optional[int],
399+
selected_experts: List[int],
400+
) -> bool:
401+
"""Return whether the source already contains every logical expert key."""
402+
403+
if not self.stacked_ckpt_keys or not selected_experts:
404+
return False
405+
return all(
406+
tensor_source.has_tensor(
407+
self._expert_key_pattern(idx).format(
408+
i=str(layer_id), expert_id=str(expert_id)
409+
)
410+
)
411+
for idx in range(len(self.weights))
412+
for expert_id in selected_experts
413+
)
414+
364415
def _build_split_config(
365416
self, layer_id: Optional[int], load_config: LoadConfig
366417
) -> Dict[str, Tuple[str, int, Callable]]:
@@ -369,8 +420,10 @@ def _build_split_config(
369420
selected_experts = load_config.get_selected_experts(
370421
layer_id, self.config.expert_num
371422
)
372-
for idx, ckpt_weight in enumerate(self.weights):
373-
stacked_key = ckpt_weight.tensor_name(layer_id)
423+
stacked_keys = self._raw_stacked_tensor_names(layer_id)
424+
for idx, (ckpt_weight, stacked_key) in enumerate(
425+
zip(self.weights, stacked_keys)
426+
):
374427
pattern = self._expert_key_pattern(idx)
375428
for expert_id in selected_experts:
376429
per_expert_key = pattern.format(
@@ -413,23 +466,25 @@ def _load_raw_tensor(
413466
if pre_sharded is not None:
414467
return {self.name: PreShardedTensor(pre_sharded)}
415468

416-
if self.stacked_ckpt_keys and tensor_source.has_tensor(
417-
self.weights[0].tensor_name(layer_id)
418-
):
469+
selected_experts = load_config.get_selected_experts(
470+
layer_id, self.config.expert_num
471+
)
472+
uses_stacked_keys = self.uses_stacked_expert_keys(
473+
tensor_source.get_database(), layer_id
474+
) or self._has_logical_expert_tensors(tensor_source, layer_id, selected_experts)
475+
source_contains_raw_stacked = uses_stacked_keys and (
476+
self._has_raw_stacked_tensors(tensor_source, layer_id)
477+
)
478+
if source_contains_raw_stacked:
419479
tensor_source = StackSplitTensorSource(
420480
tensor_source,
421481
self._build_split_config(layer_id, load_config),
422482
)
423-
ckpt_weights = (
424-
self._get_expert_weights() if self.stacked_ckpt_keys else self.weights
425-
)
483+
ckpt_weights = self._get_expert_weights() if uses_stacked_keys else self.weights
426484

427485
convert_type = (
428486
self.data_type if self.data_type is not None else load_config.compute_dtype
429487
)
430-
selected_experts = load_config.get_selected_experts(
431-
layer_id, self.config.expert_num
432-
)
433488
num_experts = len(selected_experts)
434489
num_ckpt_weights = len(ckpt_weights)
435490

@@ -509,9 +564,7 @@ def _load_pure_tp(
509564
return None
510565

511566
split_dim, segments, requires_stacked, split_func = layout
512-
is_stacked = self.stacked_ckpt_keys and tensor_source.has_tensor(
513-
self.weights[0].tensor_name(layer_id)
514-
)
567+
is_stacked = self.uses_stacked_expert_keys(database, layer_id)
515568
if (requires_stacked and not is_stacked) or (
516569
self._get_split_func() is not split_func
517570
):
@@ -706,8 +759,11 @@ def _load_raw_tensor_gpu_preallocate(
706759
def get_tensor_names(
707760
self, layer_id: Optional[int], load_config: LoadConfig
708761
) -> set[str]:
762+
has_stacked_tensor = self.uses_stacked_expert_keys(
763+
load_config.database, layer_id
764+
)
709765
ckpt_weights = (
710-
self._get_expert_weights() if self.stacked_ckpt_keys else self.weights
766+
self._get_expert_weights() if has_stacked_tensor else self.weights
711767
)
712768

713769
names = set[str]()

0 commit comments

Comments
 (0)