Skip to content

Commit c15d2b5

Browse files
sugunav14claudekevalmorabia97
authored
bug fix 6567139 (#2152)
### What does this PR do? Type of change: Bug fix Fixes [nvbug 6567139](https://nvbugspro.nvidia.com/bug/6567139): `--use_fsdp2` PTQ of Nemotron-3-Nano-30B-A3B dies on the first calibration forward with ``` AssertionError: FSDP expects uniform original parameter dtype but got {torch.bfloat16, torch.float32} ``` **Root cause.** `fsdp2_wrap` calls `fully_shard` on each decoder layer, and FSDP2 requires every parameter in a shard group to share one dtype. Nemotron-3-Nano's remote modeling code pins the MoE router gate to fp32 while the rest of the checkpoint is bf16: ```python # modeling_nemotron_h.py:885 self.weight = nn.Parameter(torch.empty((self.n_routed_experts, config.hidden_size), dtype=torch.float32)) ``` so every MoE layer's param group is `{bf16, fp32}` and `FSDPParamGroup._init_mp_dtypes` asserts. The crash surfaces during calibration only because FSDP2's `lazy_init` runs at the first forward — the model is already unwrappable at `fully_shard` time. Nothing about quantization is involved; `--use_fsdp2` on this checkpoint fails regardless of recipe. This only bites under `--trust_remote_code`: transformers' native `nemotron_h` builds fully bf16. **Fix.** `fsdp2_wrap` now finds parameters whose dtype differs from the model's dominant one (by element count) and passes them to `fully_shard(ignored_params=...)`. They stay replicated in their original dtype instead of being cast, so router precision and the exported checkpoint are unchanged. A warning names them and reports their share of the model — for Nemotron-3-Nano that is 23 fp32 MoE router gates (`backbone.layers.N.mixer.gate.weight`, 128 experts x 2688 hidden), ~30 MB replicated per rank against a 30B model. Casting to a uniform dtype was rejected because it would change both calibration routing and the exported weights. `mp_policy` is not an alternative: FSDP2 asserts on *original* dtypes regardless of it. Replication was chosen over giving the off-dtype params their own nested FSDP group (which would shard them) because router gates are `[n_experts, hidden]` — ~2 MiB each, 30-100 MiB total across every affected model — so sharding them would add a latency-bound all-gather per MoE layer on a tensor whose dim-0 (64-128 experts) cannot even split cleanly across ranks. `fully_shard` filters ignored params out in `_get_managed_states`, so it never moves them to the compute device. `fsdp2_wrap` therefore moves them itself, reading the device off the FSDP param group rather than guessing; meta params are skipped so `parallel_load_and_prepare_fsdp2`'s deferred init is unaffected. The mixed-dtype warning goes through `warn_rank_0` so it fires once per job, not once per rank. ### Second fix: FSDP2 export param mapping Folding in a second, independent bug found while verifying the first. `create_fsdp_param_mapping` resolved each `FSDPParam`'s module by scanning all of `model.named_parameters()`, and export calls it once per quantized module — quadratic in (parameters x modules). Harmless for dense models, intractable for a large MoE. Exporting Nemotron-3-Nano-30B-A3B (6,243 parameter tensors, 6,004 quantized modules, ~256 FSDPParams per MoE layer group) produced no output for 3h35m with every GPU at 0% util. py-spy showed every sample `active+gil` in `get_prefixed_param_names`, so it was CPU burn, not a stalled collective — roughly 9.6e9 Python-level id comparisons. `build_param_index` now maps `id(param) -> (position, name)` once per mapping call. The position ordering preserves the previous "first in `named_parameters()` order" result, which matters for tied weights. Measured at that scale: **1151 ms -> 5.1 ms per call (227x)**, i.e. ~1.9 h -> ~31 s of export. The index is deliberately *not* cached across calls: `fsdp2_aware_weight_update` swaps in quantized parameters and rebuilds `FSDPParam`s between them, so a shared map would go stale. This was unreachable before the dtype fix — every prior run on this checkpoint died at calibration — which is why the two ship together. ### Usage No API change — the existing command now works: ```bash torchrun --nproc_per_node=8 hf_ptq.py --use_fsdp2 \ --model /local/Nemotron-3-Nano-30B-A3B --trust_remote_code \ --recipe general/ptq/fp8_default-kv_fp8 --export_path /local/out ``` ### Testing - `tests/unit/torch/utils/test_distributed.py` (new): 4 tests for `_off_dtype_params`, including that "dominant" is by element count rather than parameter count. Passing. - `tests/gpu/torch/utils/test_distributed.py` (new): `test_fsdp2_wrap_mixed_dtypes` wraps a model carrying an fp32 router gate, forwards it, and checks the fp32 parameter stays non-DTensor, fp32, and on the compute device alongside the shards. `test_fsdp2_wrap_moves_ignored_params_to_device` builds the model on CPU and checks the ignored params are moved onto the shards' device. - Run on 2x RTX PRO 6000 Blackwell (torch 2.8.0+cu128, NCCL) through the `dist_workers` fixture: 3 passed, including `cpu_offload=True`. Without the fix the wrap raises `AssertionError: FSDP expects uniform original parameter dtype`. - Separately probed the loader path: `set_model_state_dict(..., full_state_dict=True)` into a layer mixing sharded DTensor and ignored plain params writes both correctly, which is what `_broadcast_load_group` does per decoder layer. - `tests/unit/torch/utils/`: 155 passed. `pre-commit` clean. ### Before your PR is "*Ready for review*" - Is this change backward compatible?: ✅ - If you copied code from any other sources or added a new PIP dependency, did you follow guidance in `CONTRIBUTING.md`: N/A - Did you write any new necessary tests?: ✅ - Did you update [Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?: ✅ - Did you get Claude approval on this PR?: ❌ ### Additional Information `fully_shard(ignored_params=...)` requires torch >= 2.7; the repo already pins `torch>=2.8`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved FSDP2 post-training quantization for mixed-dtype models by keeping off-dtype parameters replicated in their original precision. - Ensured ignored/replicated parameters are moved to the correct FSDP2 compute device before inference. - Added warnings that list affected parameter names and their relative model share when mixed dtypes are detected. - **Performance** - Optimized Hugging Face export to reduce overhead on large MoE checkpoints, improving parameter name resolution for tied weights. - **Tests** - Added GPU and unit tests covering mixed-dtype wrapping behavior and parameter indexing. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Suguna Velury <178320438+sugunav14@users.noreply.github.com> Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
1 parent 96b4aac commit c15d2b5

6 files changed

Lines changed: 411 additions & 10 deletions

File tree

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ Changelog
113113
- Fix HF checkpoint export failing with ``AttributeError: 'list' object has no attribute 'keys'`` for models whose modeling code still declares tied weights in the ``transformers<5`` list format (common among ``trust_remote_code`` checkpoints, e.g. ``stepfun-ai/Step-3.7-Flash``). Such models load fine but died at the end of PTQ, after calibration. ModelOpt's ``save_pretrained`` patch now normalizes a list-style declaration to the equivalent dict for the duration of the save.
114114
- Fix unified HF export of multimodal models whose vision tower carries its own ``PrefixChange`` conversion (``LlavaForConditionalGeneration`` and ``Gemma3ForConditionalGeneration`` on ``transformers>=5.12``). The quant-aware reverse conversion ignored transformers' ``scope_prefix``, so the vision tower's prefix rule was applied to *every* key in the state dict and vLLM rejected the checkpoint with ``ValueError: There is no module or parameter named 'vision_model'``. Reverse rename rules now carry their scope and are applied only to keys under it.
115115
- Fix QLoRA export in ``examples/llm_qat/export.py`` failing with ``AssertionError: Model already has modelopt state!``: ``enable_huggingface_checkpointing`` already restores the quantized base model's state, so the export now restores only when the model is not already converted. Two further breakages on the same path are also fixed: ``_restore_qtensor_wrappers`` matched no modules because PEFT re-parents the quantized linear as ``<name>.base_layer``, and ``postprocess_state_dict`` silently dropped every ``base_layer.*`` key missing from a hand-maintained rename map (losing the NVFP4 ``weight_scale_2`` global scale and any linear ``bias``) — the rename is now a generic ``.base_layer.`` strip.
116+
- Fix ``--use_fsdp2`` PTQ (``examples/hf_ptq``) failing on models that hold a few parameters in a dtype other than the model's own, with ``AssertionError: FSDP expects uniform original parameter dtype`` on the first calibration forward. Nemotron-3-Nano is one such model: its MoE router gates are declared ``float32`` while the rest of the checkpoint is bfloat16, so each decoder layer's FSDP2 shard group mixed dtypes. ``fsdp2_wrap`` now passes those off-dtype parameters to ``fully_shard(ignored_params=...)``, leaving them replicated in their original dtype instead of casting them, and warns with their names and their share of the model.
117+
- Fix ``--use_fsdp2`` HF export making no progress for hours on large MoE checkpoints. ``create_fsdp_param_mapping`` resolved each ``FSDPParam``'s module by scanning every ``model.named_parameters()``, and export calls it once per quantized module, so the cost was quadratic in (parameters x modules): harmless for dense models, intractable for a MoE with many experts. Exporting Nemotron-3-Nano-30B-A3B (6,243 parameter tensors, 6,004 quantized modules) spent an estimated 1.9 hours there with every GPU idle. The parameter index is now built once per mapping instead of once per ``FSDPParam`` (1151 ms -> 5.1 ms per call), preserving the previous ``named_parameters()``-order resolution for tied weights.
116118

117119
0.45 (2026-07-02)
118120
^^^^^^^^^^^^^^^^^

modelopt/torch/quantization/utils/core_utils.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -828,22 +828,30 @@ def _disable_fsdp_unshard_reshard(layer):
828828
yield
829829

830830

831-
def get_prefixed_param_names(parent_model, target_module):
831+
def build_param_index(model):
832+
"""Map ``id(param)`` to its ``(position, name)`` in ``model.named_parameters()``.
833+
834+
Lets callers resolve many modules against one walk of the parameters instead of one walk
835+
each; the position keeps "first in ``named_parameters()`` order" resolvable.
836+
"""
837+
return {id(param): (i, name) for i, (name, param) in enumerate(model.named_parameters())}
838+
839+
840+
def get_prefixed_param_names(parent_model, target_module, param_index=None):
832841
"""Get parameter names for a target module prefixed with the parent model name.
833842
834843
This function is used to get full parameter name from FSDPParam module_info which stores the
835844
unprefixed parameter name.
836845
846+
Pass ``param_index`` (see :func:`build_param_index`) when resolving many target modules
847+
against the same parent, so the parent's parameters are walked once rather than per module.
837848
"""
849+
if param_index is None:
850+
param_index = build_param_index(parent_model)
838851
target_ids = {id(p) for p in target_module.parameters()}
839-
return next(
840-
(
841-
name.rsplit(".", 1)[0]
842-
for name, param in parent_model.named_parameters()
843-
if id(param) in target_ids
844-
),
845-
None, # default value if no match
846-
)
852+
# Lowest position == first in named_parameters() order, matching a linear scan's result.
853+
match = min((param_index[pid] for pid in target_ids if pid in param_index), default=None)
854+
return match[1].rsplit(".", 1)[0] if match is not None else None
847855

848856

849857
def create_fsdp_param_mapping(fsdp_param_list, model):
@@ -856,10 +864,14 @@ def create_fsdp_param_mapping(fsdp_param_list, model):
856864
Returns:
857865
dict: Full parameter name → FSDP parameter.
858866
"""
867+
# Built once per call, not once per FSDPParam: export resolves every quantized module, so the
868+
# per-param walk made this quadratic in (params x modules) and stalled MoE exports for hours.
869+
# It cannot be cached across calls -- callers swap in quantized params between them.
870+
param_index = build_param_index(model)
859871
mapping = {}
860872
for param in fsdp_param_list:
861873
# Get the module name
862-
module_name = get_prefixed_param_names(model, param._module_info.module)
874+
module_name = get_prefixed_param_names(model, param._module_info.module, param_index)
863875
if module_name is not None:
864876
# Get the parameter name from _module_info and construct full param name
865877
param_name = param._module_info.param_name

modelopt/torch/utils/distributed.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,80 @@ def is_fsdp2_model(model) -> bool:
252252
return any(isinstance(m, FSDPModule) for m in model.modules())
253253

254254

255+
def _off_dtype_params(model) -> set[torch.nn.Parameter]:
256+
"""Params whose dtype differs from the model's dominant (by element count) param dtype.
257+
258+
FSDP2 needs one dtype per shard group, but HF models routinely keep a few params in fp32 for
259+
stability (e.g. MoE router gates). Pass these to ``fully_shard(ignored_params=...)``.
260+
261+
TODO: Drop this and shard the off-dtype params once a stable PyTorch release includes FSDP2
262+
mixed-precision parameter dtype support (already on nightly).
263+
"""
264+
numel_by_dtype: dict[torch.dtype, int] = {}
265+
for param in model.parameters():
266+
numel_by_dtype[param.dtype] = numel_by_dtype.get(param.dtype, 0) + param.numel()
267+
if len(numel_by_dtype) <= 1:
268+
return set()
269+
270+
# Lazy import: logging imports this module at top level (circular).
271+
from modelopt.torch.utils.logging import warn_rank_0
272+
273+
dominant = max(numel_by_dtype, key=lambda d: numel_by_dtype[d])
274+
off_dtype = {n: p for n, p in model.named_parameters() if p.dtype != dominant}
275+
off_numel = sum(numel_by_dtype[d] for d in numel_by_dtype if d != dominant)
276+
names = sorted(off_dtype)
277+
warn_rank_0(
278+
f"Model has mixed parameter dtypes {set(numel_by_dtype)}; FSDP2 needs one dtype per shard "
279+
f"group, so {len(names)} non-{dominant} parameter(s) "
280+
f"({100 * off_numel / sum(numel_by_dtype.values()):.2f}% of elements) will stay replicated "
281+
f"rather than sharded: {names[:3]}{' ...' if len(names) > 3 else ''}"
282+
)
283+
return set(off_dtype.values())
284+
285+
286+
def _move_to_fsdp_device(model, params: set[torch.nn.Parameter]) -> None:
287+
"""Move ``params`` onto the device FSDP2 computes on for ``model``.
288+
289+
``fully_shard`` only moves the params it manages, so ignored ones would be stranded on
290+
whatever device the caller built the model on. Meta params are left alone for deferred init.
291+
292+
The device comes from a sharded param's mesh rather than its local shard: under
293+
``cpu_offload`` the shard rests on CPU while compute still happens on the accelerator.
294+
"""
295+
# Lazy import: logging imports this module at top level (circular).
296+
from modelopt.torch.utils.logging import warn_rank_0
297+
298+
mesh = next((p.device_mesh for p in model.parameters() if isinstance(p, DTensor)), None)
299+
if mesh is None:
300+
warn_rank_0(
301+
f"FSDP2 sharded no parameter of {type(model).__name__}, so the compute device for "
302+
f"{len(params)} unsharded off-dtype parameter(s) cannot be determined; leaving them "
303+
"where they are. Move them to the compute device or the forward will fail."
304+
)
305+
return
306+
307+
device = (
308+
torch.device("cpu")
309+
if mesh.device_type == "cpu"
310+
else torch.device(mesh.device_type, getattr(torch, mesh.device_type).current_device())
311+
)
312+
for param in params:
313+
if not param.is_meta and param.device != device:
314+
param.data = param.data.to(device)
315+
316+
255317
def fsdp2_wrap(model, shard_root=True, mp_policy=None, cpu_offload: bool = False):
256318
"""Auto-detect a HF causal-LM's decoder layers and FSDP2 ``fully_shard`` each one.
257319
258320
By default (``shard_root=True``) the root module is wrapped too, so embed/lm_head/norm are
259321
sharded instead of replicated per rank; pass ``shard_root=False`` to leave the root replicated
260322
(only decoder layers sharded). Returns the detected decoder layers so callers can reuse the
261323
detection result.
324+
325+
Parameters whose dtype differs from the model's dominant one are excluded from the wrap (see
326+
:func:`_off_dtype_params`), since FSDP2 rejects a shard group that mixes dtypes. They stay
327+
replicated and are moved onto the shards' device, which ``fully_shard`` does not do for the
328+
params it ignores.
262329
"""
263330
# Lazy import: layerwise_calib imports this module at top level (circular).
264331
from modelopt.torch.quantization.utils.layerwise_calib import LayerActivationCollector
@@ -274,6 +341,9 @@ def fsdp2_wrap(model, shard_root=True, mp_policy=None, cpu_offload: bool = False
274341
fsdp_kwargs["mp_policy"] = mp_policy
275342
if cpu_offload:
276343
fsdp_kwargs["offload_policy"] = CPUOffloadPolicy()
344+
ignored_params = _off_dtype_params(model)
345+
if ignored_params:
346+
fsdp_kwargs["ignored_params"] = ignored_params
277347

278348
# Snapshot/restore config.architectures: some HF builders mutate it during fully_shard.
279349
config = getattr(model, "config", None)
@@ -282,6 +352,8 @@ def fsdp2_wrap(model, shard_root=True, mp_policy=None, cpu_offload: bool = False
282352
fully_shard(layer, **fsdp_kwargs)
283353
if shard_root:
284354
fully_shard(model, **fsdp_kwargs)
355+
if ignored_params:
356+
_move_to_fsdp_device(model, ignored_params)
285357
if config is not None and architectures:
286358
config.architectures = architectures
287359

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""GPU/distributed tests for ``modelopt.torch.utils.distributed``."""
17+
18+
from functools import partial
19+
20+
import pytest
21+
import torch
22+
import torch.nn as nn
23+
import torch.nn.functional as F
24+
from _test_utils.torch.transformers_models import get_tiny_llama
25+
from torch.distributed.checkpoint.state_dict import StateDictOptions, set_model_state_dict
26+
from torch.distributed.tensor import DTensor
27+
28+
from modelopt.torch.utils.distributed import fsdp2_wrap
29+
30+
VOCAB_SIZE = 32
31+
N_EXPERTS = 4
32+
33+
34+
class _Fp32Router(nn.Module):
35+
"""An MoE router gate pinned to fp32, as Nemotron-3-Nano's modeling code declares it."""
36+
37+
def __init__(self, hidden_size: int):
38+
super().__init__()
39+
self.weight = nn.Parameter(torch.empty(N_EXPERTS, hidden_size, dtype=torch.float32))
40+
nn.init.normal_(self.weight, std=0.02)
41+
42+
def forward(self, hidden_states):
43+
return F.linear(hidden_states.float(), self.weight.float())
44+
45+
46+
class _RoutedMLP(nn.Module):
47+
"""Fronts a bf16 MLP with the fp32 router, so one decoder layer holds both dtypes."""
48+
49+
def __init__(self, mlp: nn.Module, hidden_size: int):
50+
super().__init__()
51+
self.mlp = mlp
52+
self.gate = _Fp32Router(hidden_size)
53+
54+
def forward(self, hidden_states):
55+
scale = self.gate(hidden_states).softmax(-1)[..., :1].to(hidden_states.dtype)
56+
return self.mlp(hidden_states) * scale
57+
58+
59+
def _mixed_dtype_model(device):
60+
model = get_tiny_llama(vocab_size=VOCAB_SIZE).to(device)
61+
for layer in model.model.layers:
62+
layer.mlp = _RoutedMLP(layer.mlp, model.config.hidden_size).to(device)
63+
return model.eval()
64+
65+
66+
def _test_fsdp2_wrap_mixed_dtypes(rank, size):
67+
"""A model with a few fp32 params must still wrap, forward, and load state dicts."""
68+
device = torch.device(f"cuda:{rank}")
69+
model = _mixed_dtype_model(device)
70+
assert {p.dtype for p in model.model.layers[0].parameters()} == {
71+
torch.bfloat16,
72+
torch.float32,
73+
}
74+
75+
fsdp2_wrap(model)
76+
77+
# Raised "FSDP expects uniform original parameter dtype" before the ignored-param fix.
78+
input_ids = torch.randint(0, VOCAB_SIZE, (1, 8), device=device)
79+
with torch.no_grad():
80+
assert model(input_ids=input_ids).logits.shape == (1, 8, VOCAB_SIZE)
81+
82+
# bf16 weights are sharded; the fp32 router is left replicated in its original dtype.
83+
gate_weight = model.model.layers[0].mlp.gate.weight
84+
sharded_weight = model.model.layers[0].mlp.mlp.up_proj.weight
85+
assert isinstance(sharded_weight, DTensor)
86+
assert not isinstance(gate_weight, DTensor)
87+
assert gate_weight.dtype == torch.float32
88+
# Left out of the wrap, it still has to sit on the compute device alongside the shards.
89+
assert gate_weight.device == sharded_weight.to_local().device
90+
91+
# The FSDP2 loader pushes full tensors into each decoder layer; that must still reach the
92+
# replicated fp32 param as well as the sharded bf16 ones.
93+
layer = model.model.layers[0]
94+
hidden_size = model.config.hidden_size
95+
set_model_state_dict(
96+
layer,
97+
{"mlp.gate.weight": torch.full((N_EXPERTS, hidden_size), 3.0, device=device)},
98+
options=StateDictOptions(full_state_dict=True, broadcast_from_rank0=False, strict=False),
99+
)
100+
assert torch.equal(
101+
layer.mlp.gate.weight, torch.full((N_EXPERTS, hidden_size), 3.0, device=device)
102+
)
103+
104+
105+
def test_fsdp2_wrap_mixed_dtypes(dist_workers):
106+
dist_workers.run(_test_fsdp2_wrap_mixed_dtypes)
107+
108+
109+
def _test_fsdp2_wrap_moves_ignored_params_to_device(rank, size, cpu_offload):
110+
"""A CPU-resident model must end up computing on GPU: fully_shard skips the params it ignores."""
111+
model = _mixed_dtype_model(torch.device("cpu"))
112+
assert model.model.layers[0].mlp.gate.weight.device.type == "cpu"
113+
114+
fsdp2_wrap(model, cpu_offload=cpu_offload)
115+
116+
# Under cpu_offload the shard rests on CPU, but compute — and so the ignored params — is
117+
# still on GPU, which is why the device is taken from the mesh and not from the local shard.
118+
sharded_weight = model.model.layers[0].mlp.mlp.up_proj.weight
119+
assert sharded_weight.to_local().device.type == ("cpu" if cpu_offload else "cuda")
120+
assert model.model.layers[0].mlp.gate.weight.device.type == "cuda"
121+
122+
input_ids = torch.randint(0, VOCAB_SIZE, (1, 8), device=torch.device(f"cuda:{rank}"))
123+
with torch.no_grad():
124+
assert model(input_ids=input_ids).logits.shape == (1, 8, VOCAB_SIZE)
125+
126+
127+
@pytest.mark.parametrize("cpu_offload", [False, True])
128+
def test_fsdp2_wrap_moves_ignored_params_to_device(dist_workers, cpu_offload):
129+
dist_workers.run(
130+
partial(_test_fsdp2_wrap_moves_ignored_params_to_device, cpu_offload=cpu_offload)
131+
)

0 commit comments

Comments
 (0)