Skip to content

[TRTLLM-16725][feat] Add selective Model Streamer loading for Kimi K3 - #20782

Open
yijingl-nvidia wants to merge 5 commits into
NVIDIA:mainfrom
yijingl-nvidia:user/yijingl/kimi-k3-model-streamer
Open

yijingl-nvidia wants to merge 5 commits into
NVIDIA:mainfrom
yijingl-nvidia:user/yijingl/kimi-k3-model-streamer

Conversation

@yijingl-nvidia

@yijingl-nvidia yijingl-nvidia commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Description

This PR adds the Model Streamer load for Kimi K3. Each rank lists only the checkpoint tensors that it needs, for example its own experts, so each node streams only what its ranks use.

It also supports Kimi K3 language-only loads, which skip the multimodal encoder (disable_mm_encoder), and adds a Model Streamer section to the Kimi K3 README.

The regular Kimi K3 load (_load_expert_slices()) and the Model Streamer load put every routed expert into its MoE backend slot with the same two functions of modeling_kimi_k3_loader.py. _load_expert_into_slot() prepares a layer's staging buffers on its first expert and finalizes the layer when its last slot fills. _check_expert_slots() checks at the end that every slot is filled and every staged layer is finalized. So a change to these steps lands in both loads.

This PR must merge after #20717 and #20781. Its branch contains the commits of #20711, #20713, #20717 and #20781. Review only the last commit: https://github.com/NVIDIA/TensorRT-LLM/pull/20782/files/c2c1c87a40e0ad51141b09ac60422669fc7250ed..764e372667365352989b60378f954b9ad3fedc88

The Model Streamer expert load also streams the optional per-expert tensors that the checkpoint stores, such as the AWQ pre-quant scale, and gives them to the Kimi K3 expert loaders through their get_optional_tensor argument (#20823). test_streamer_expert_loading_matches_regular_loader covers experts with and without the optional tensor.

Test Coverage

4 tests added, 0 tests changed:

  • tests/unittest/_torch/modeling/test_kimi_k3_checkpoint.py
  • tests/unittest/_torch/models/test_kimi_k3_model_streamer.py

The new tests build their checkpoint with mla_checkpoint(), which this PR splits out of the test helper load_mla_checkpoint(). It keeps the helper's random_gate_up option from #20717.

tests/unittest/_torch/models/test_kimi_k3_model_streamer.py now runs in the B200 pre-merge list (l0_b200.yml).

Load-time experiment

This experiment checks the Model Streamer change from the review: stream_files(..., owned=False), and no torch.cuda.synchronize() after each streamed tensor. With the change, the weights and the outputs stay the same, and Model Streamer loads the weights about 6% faster.

Setup:

  • OCI-JHB, 2 nodes with 4 GB300 GPUs each, Kimi K3 in TEP8: TP8 attention, EP8 MoE, attention DP off. The other LLM settings are the settings of quick_start_kimi_k3.py.
  • Kimi K3 HF checkpoint on Lustre: 96 files, 1.42 TiB. Before each run, we removed the checkpoint from the page cache of each node.
  • NGC nightly image 1.4.0rc3.dev202610290007 with the Python files of commit ddfeb9abf9, and runai-model-streamer 0.17.1. This branch differs from that commit in the checks, errors, names and process-group setup of the Model Streamer load, and its Kimi K3 expert-load steps are in functions that the regular load shares. In the setup of this experiment, each node still streams the tensors that its 4 ranks need, and the 4 ranks share the reads.
  • Old code: model_loading.py before the change (default owned=True, sync after each tensor). New code: this branch.
  • One Slurm job with six runs, in this order: native reader, old, new, old, new, native reader.

Load time in seconds for the slowest of the 8 ranks, with the median rank in parentheses:

Run checkpoint_io_policy Code weight_population_seconds total_model_loading_seconds model_streamer_seconds
1 native new 251.5 (120.3) 263.1 (126.7) -
2 model_streamer old 86.3 (84.9) 95.1 (91.1) not recorded
3 model_streamer new 81.0 (79.6) 89.9 (86.1) 75.4 (73.9)
4 model_streamer old 86.0 (85.5) 95.7 (91.7) 80.0 (79.5)
5 model_streamer new 81.5 (79.8) 89.7 (85.6) 76.0 (74.1)
6 native new 252.7 (122.9) 263.9 (129.9) -
  • The new code loads the weights 4.9 s (5.7%) faster on the slowest rank: 81.2 s against 86.2 s, as the average of two runs. In this value, the two runs of one code differ by 0.5 s or less.
  • On the slowest rank, Model Streamer loads the model 2.9 times faster than the native reader: 89.8 s against 263.5 s.
  • Run 2 has no model_streamer_seconds, because the test hook that records this value started after run 2.

Accuracy: all six runs gave the same weights. On each of the 8 ranks, the checksums of all 3,296 parameters and buffers (209 GB for each rank) are equal. Greedy outputs (temperature 0, 8 prompts, 128 tokens for each prompt) have the same token IDs in all six runs.

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

PRs in this series

These PRs refactor checkpoint loading and add Model Streamer loading. They merge in this order, top to bottom.

PR What it does
#20498 (merged) Change checkpoint_io_policy from plain strings to the string enum CheckpointIOPolicy.
#20703 (merged) Add the string enum CheckpointFormat for the built-in checkpoint formats, make the checkpoint I/O helpers take only LoadFormat, and make the checkpoint I/O policy resolution easier to read.
#20704 (merged) Put the PyTorch process-group setup in one module, distributed/initialization.py.
#20711 Make ModelLoader.load() shorter: move its MX, GMS, memory and model-hook helpers to new files in models/checkpoints/.
#20713 Remove the MX-only hooks from BaseCheckpointLoader.
#20717 Move Kimi K3 weight loading from modeling_kimi_linear.py to modeling_kimi_k3_loader.py.
#20781 Add checkpoint_io_policy="model_streamer", which loads a checkpoint with Run:ai Model Streamer.
#20782 (this PR) Add the Model Streamer load for Kimi K3.

🤖 Generated with Claude Code

@sunnyqgg sunnyqgg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review covers only this PR's own commit, ddfeb9a ("Add selective Model Streamer loading for Kimi K3"), against its parent 255c1a0. The stacked PRs (#20711, #20717, #20781) are reviewed in their own PRs. Note that this branch is based on an older version of #20781: its current head is 3b6ac69. The Model Streamer code this commit uses is identical in both, but #20781's new head no longer contains the #20717 move that this commit builds on. Findings: 1 blocker, 2 possibly blocking, 7 required. Six of the required items are inline; the possibly-blocking items and the remaining required item are below.

  • [Possibly blocking] +1 to cascade812's thread on tensorrt_llm/_torch/models/checkpoints/model_streamer/model_loading.py:288. This commit adds one of those per-rank raises:
    • plan_model_streamer_load() checks only this rank's experts: expected_keys comes from moe.local_expert_ids (modeling_kimi_k3_loader.py:626-629).
    • With a checkpoint missing one routed-expert tensor, only the owning rank raises the KeyError at modeling_kimi_k3_loader.py:709-712. The other ranks block in the MPI allgather.
    • The comment at line 702 ("Fail here, before the streaming code calls other ranks") promises something the check does not deliver.
    • Fix: every rank can derive all experts' key names from the config, so checking all of them makes the error the same on every rank. Otherwise, please reword the comment.
  • [Possibly blocking] +1 to chienchunhung's thread on examples/kimi_k3/README.md:108. quick_start_kimi_k3.py:63 hard-codes enable_attention_dp=True, so --tp-size 8 runs DEP8, not TEP8. The README, the deployment guide (TEP8 = attention DP off) and the description's experiment all use TEP8. The repo's own evidence on whether DEP8 fits on GB300 conflicts:
    • The README's own numbers (114 GB replicated + 181 GB of experts per rank, README.md:263-267) exceed 288 GB.
    • examples/kimi_k3/disagg/README.md:32-37 calls a GB300 fit "likely negative".
    • test_kimi3.py::test_gsm8k_nvfp4_dep8 runs DEP8 only with FP8 MoE-MLP reads, because the default BF16 MLP "leave[s] DEP8 no KV-cache room".
    • Fix: add an attention-DP switch, as run_eval_kimi_k3.sbatch does with --parallel dep|tep, or drop the TEP8 sentence.
  • [Required] The PR description does not match the code:
    1. It says "0 tests changed", but the commit also changes the _model_streamer_load helper that existing tests in test_model_loader_checkpoint_io.py use, and refactors load_mla_checkpoint in test_kimi_k3_checkpoint.py.
    2. It describes language-only loads for Kimi K3, but the commit also makes Kimi K2.5 honor disable_mm_encoder (modeling_kimi_k25.py:1632, 1748, 1791). With the flag, K2.5 now skips the vision tower and rejects image requests. trtllm-bench throughput sets the flag for every text-only run (bench/benchmark/throughput.py:436-446), so K2.5/K3 benchmark numbers will shift because the KV-cache pool grows. Please describe this (or move the K2.5 part to its own PR) and add a K2.5 test for it.
    3. The CodeRabbit block describes the whole 99-file stack and lists tensorrt_llm/_torch/pyexecutor/weight_load_strategies/* files that do not exist at this head. It would end up in the squash commit message.

mm_token_ids = None

if len(multimodal_params) > 0:
if self.model_config.disable_mm_encoder:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Blocker] With disable_mm_encoder: true (the README's Model Streamer YAML sets it), image and video requests are rejected only here, inside the batched forward(). Nothing rejects them earlier:

  • the Kimi input processor still preprocesses the media;
  • the LLM API and trtllm-serve do not check the flag (only gRPC hides media, grpc/openengine/capabilities.py:80).

A forward-time exception fails the whole batch:

  • PyExecutor._forward_step (py_executor.py:8973-8991) calls _handle_errors(error_msg) without requests. That fails every active request and charges the error budget (error_classification.py:118-148), so a few image requests in quick succession shut the executor down.
  • Under attention DP, which quick_start_kimi_k3.py:63 hard-codes, only the rank that received the image raises, before self.llm.forward. The other ranks enter the MoE collectives and wait for it.

The executor already has a per-request hook for this:

  • _validate_request calls model.validate_multimodal_request_data(mm_data) at admission (py_executor.py:6876-6884).
  • A failure there rejects only that request, with charge_budget=False (py_executor.py:7340-7343).
  • The hook's docstring (modeling_multimodal_mixin.py:470-478) says that conditions a model would reject inside forward belong there.

Suggested fix:

  • Implement validate_multimodal_request_data(self, mm_data) on KimiK25ForConditionalGeneration; K3 inherits it. It should raise ValueError naming disable_mm_encoder=True when the flag is set and the request carries multimodal data, the same condition the forward check uses.
  • Keep the forward check as a backstop.
  • Add a test that sends one image request next to a text request and checks that only the image request fails.

Comment thread examples/kimi_k3/README.md Outdated
every serving rank. Choose the `aarch64` or `x86_64` wheel for the worker CPU:

```bash
python -m pip install /path/to/runai_model_streamer-*.whl

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Required] This install step contradicts the rest of the README and has no version:

  • The Slurm jobs see only packages in the mounted .venv-3.12. README.md:49-57 says "Packages installed only in the preparation container's base environment are not" available, and fla-core is installed with .venv-3.12/bin/python -m pip (README.md:68).
  • No package version is given, but the code needs stream_files(tensor_names=..., owned=...), which 0.16.x does not have.

Following it literally, every rank fails with the ImportError or TypeError from stream_batched_tensors. That happens only after the model is built and the ranks have gathered tensor names, so a whole multi-node allocation is wasted. runai-model-streamer 0.17.0 and 0.17.1 are on PyPI (the description's run used 0.17.1), so this can be:

.venv-3.12/bin/python -m pip install "runai-model-streamer>=0.17"

Comment thread examples/kimi_k3/README.md Outdated

The streamer reads only tensors needed by the ranks on each node and uses
node-local distributed loading when multiple ranks share a node. Its current
scope is local Hugging Face safetensors checkpoints for the text Kimi K3 model.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Required] "Its current scope is ... the text Kimi K3 model" contradicts this commit:

  • KimiK3ForConditionalGeneration.plan_model_streamer_load (modeling_kimi_k3_vl.py:550-590) also streams the vision tower and the projector.
  • The quick-start path this section recommends (--checkpoint-io-policy model_streamer, no disable_mm_encoder) takes exactly that vision path with the multimodal checkpoint.
  • Putting disable_mm_encoder: true in the Model Streamer YAML makes it look required.

Please state that both the text-only and the multimodal Kimi K3 classes are supported, or reject the VL plan if the vision path has not been validated end to end. Show disable_mm_encoder as a separate, optional setting.

# ------------- Kimi K3 (KimiLinear) unit tests ---------------
- unittest/_torch/modeling/test_kimi_k3_checkpoint.py TIMEOUT (15)
- unittest/_torch/modeling/test_kimi_k3_mla.py TIMEOUT (15)
- unittest/_torch/models/test_kimi_k3_model_streamer.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Required] All four cases in test_kimi_k3_model_streamer.py run without a GPU:

  • the multimodal tests use fake parts;
  • the expert test's harness returns a CPU torch.empty(0) from parameters();
  • run_model_streamer(..., world_size=1) skips the distributed setup, and stream_batched_tensors is monkeypatched.

tests/AGENTS.md says "CPU-only tests go in l0_cpu.yml", and the CPU stages already import modeling_kimi_linear for other cpu_only Kimi tests. BowenFu asked for this entry to get the expert path into CI; l0_cpu.yml does that too, without using a B200 slot, and also runs on arm.

Please add pytestmark = pytest.mark.cpu_only to the file and move the entry to l0_cpu.yml. The CPU stage runs -m cpu_only, and tests/unittest/conftest.py skips files without the marker.

"that never send image/video/audio inputs; such requests are "
"rejected. Only takes effect for model implementations that support "
"it (currently Mistral3 and the Qwen3-VL / Qwen3.5-VL family); a "
"it (currently Kimi K2.5/K3, Mistral3, and the Qwen3-VL / "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Required] This sentence is being rewritten, and it is still incomplete. DeepSeek-V4.1 multimodal (modeling_deepseekv41_multimodal.py:60) and the GLM-5 Next vision model (modeling_glm5_next_vision.py:1054) also honor disable_mm_encoder. The field description, the regenerated serve schemas and the comment in model_config.py therefore tell their users the flag is a no-op.

Please complete the list, or replace it with "models whose implementation checks ModelConfig.disable_mm_encoder". Then regenerate the schemas with scripts/generate_trtllm_serve_schemas.py.

return model


def test_multimodal_wrapper_adds_vision_group(monkeypatch):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Required] CODING_GUIDELINES.md says: "Always annotate functions. Make the return type None if the function does not return anything".

  • None of the functions, helpers or fake methods in this new file are annotated, nor is the nested stream in test_kimi_k3_checkpoint.py:285.
  • test_model_streamer_matches_regular_loader in the same commit is annotated.
  • It matters here because the fakes stand in for stream_batched_tensors and for the expert-loader signature, which already drifted once in this PR (get_optional_tensor).

Please annotate them as test_model_loader_checkpoint_io.py does: monkeypatch: pytest.MonkeyPatch, -> None, consume: Callable[[str, torch.Tensor], None].

load_function)``.

Raises:
- If the checkpoint lacks a tensor that this rank needs (KeyError).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Required] CODING_GUIDELINES.md asks for Google-style docstrings. Please write this one, and the one at modeling_kimi_k3_vl.py:568-570, as KeyError: If the checkpoint lacks a tensor that this rank needs., the way model_streamer/model_loading.py:132-135 does.

Optional style points in the same code:

  • _ROUTED_EXPERT_KEY is a compiled regex; _ROUTED_EXPERT_KEY_RE would say so.
  • groups could carry a type annotation instead of the # groups: comment.
  • The new signatures mix Set/Dict with set/dict.

@sunnyqgg sunnyqgg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up to my review of 2026-11-03: 16 of the 19 lower-priority points I left out of it still apply at db0aa91. The other 3 are fixed or no longer apply. They are marked [Nice to have] and none blocks the merge on its own.

  • [Nice to have] There is no /bot run on this PR yet, and the description lists the new tests without results, so nothing shows that they run and pass. Please post /bot run once the stack allows and link the run. It should show the 8 test_model_streamer_matches_regular_loader cases and the 4 test_kimi_k3_model_streamer.py cases passing rather than skipped (the expert test calls pytest.importorskip("fla")), and the CPU stage passing the changed _model_streamer_load helper in test_model_loader_checkpoint_io.py.


def load_weights(self, weights) -> None:
"""Load vision + projector + LLM weights from checkpoint."""
self._ensure_mm_encoder()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] load_weights() now calls self._ensure_mm_encoder() first, but test_language_weights_preserve_checkpoint_dir (test_modeling_kimi_k25.py:269-273) calls it on a SimpleNamespace without that method, so the test now fails with AttributeError. CI stays green only because that file has no cpu_only marker and is in no test list. It is the only test of the checkpoint_dir/checkpoint_prefix pass-through that K3's per-file expert loading needs (modeling_kimi_k3_loader.py:1247-1251). Please add _ensure_mm_encoder=lambda: None to the stand-in and mark the test pytest.mark.cpu_only, so the unittest/_torch/modeling entry in l0_cpu.yml:68 runs it.

self.loaded_weights = weights

monkeypatch.setattr(modeling_kimi_k25, "_has_meta_tensors", lambda model: False)
model = _multimodal_wrapper(TextModel(), VisionModel())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] Both VL plan tests make _ensure_mm_encoder() a no-op: this one passes a ready VisionModel(), and the text-only test sets disable_mm_encoder=True (test_kimi_k3_model_streamer.py:83). In production, MetaInitMode leaves mm_encoder as None until the plan rebuilds it (modeling_kimi_k3_vl.py:574), so removing that call keeps these tests green while multimodal streamer loads lose the vision group. The KeyError at modeling_kimi_k3_vl.py:588 is untested too. Please add a case that starts from mm_encoder=None with a fake _VISION_MODEL_CLS and _vlm_pretrained_config and checks that the last group loads into the rebuilt encoder, plus a no-vision-keys case that expects the KeyError.

)
assert sorted(regular_events) == expected_events
assert sorted(streamed_events) == expected_events
assert [len({_expert_layer(name) for name in batch}) for batch in streamed_batches] == [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] This test never checks which tensors were streamed: it compares what the fake loader read, the prepare/finalize events and the layers per batch. The fake loader (test_kimi_k3_model_streamer.py:133-143) reads only its own expert, so a plan whose groups also named expert 1 (owned by another rank) would still pass, although each node would then read experts its ranks do not own, which defeats the selective load. Please assert the exact set, e.g. set().union(*streamed_batches) == expected, where expected holds experts 2 and 0 x w1/w2/w3 x kinds for every layer, plus expert 2's w1.pre_quant_scale.

def build() -> KimiLinearForCausalLM:
with torch.device("cuda"):
model = KimiLinearForCausalLM(
_config(fp8, mapping=mapping, prefix=prefix, deferred=False, cute_bmm=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] This test builds only deferred=False, cute_bmm=True, and FP8 only with weight_scale_inv (test_kimi_k3_checkpoint.py:255). So MLA never creates k_b_proj_trans_dequant/v_b_proj_dequant (mla.py:802), although that is the default SM100 path (use_cute_dsl_blockscaling_bmm defaults to False, llm_args.py:7232); the loop at test_kimi_k3_checkpoint.py:310-315 compares None with None, and the weight_scale branch of tensor_and_scale_names (modeling_kimi_k3_loader.py:721) never runs. Please reuse the sibling test's (deferred, cute_bmm) pairs and three (fp8, scale_name) pairs (test_kimi_k3_checkpoint.py:126-129); the trailing loop can then go, since named_parameters() covers those parameters.

# not be kept for runtime forward.
self.mm_encoder = None
if not DISAGG:
if not DISAGG and not model_config.disable_mm_encoder:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] No test builds the real KimiK25ForConditionalGeneration or KimiK3ForConditionalGeneration with disable_mm_encoder=True; the streamer tests use a wrapper that skips __init__ and forward. If this guard (or modeling_kimi_k3_vl.py:498) is reverted, a model built without MetaInitMode still builds and loads the vision tower, and without the check at modeling_kimi_k25.py:1791 an image request fails on None.forward, with no test noticing. Mistral3 has such a test (test_modeling_mistral.py:182). Please add construction tests for both classes with the flag, with and without MetaInitMode (no mm_encoder.* parameters, encoder still None after load_weights), and a forward test that expects the rejection.

self._load_trunk_params,
params={name: params[name]},
name_map={name: key},
log_summary=False,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] The streamed load drops diagnostics that the native load prints: the plan never runs _validate_checkpoint_keys, so the unmatched-key warning (modeling_kimi_k3_loader.py:916-920) is gone, and with log_summary=False on every group the KV-B summary (:1124-1127) is never logged. A checkpoint with stray tensors therefore warns under native and is silent under model_streamer. K2.5/K3 also log nothing when disable_mm_encoder skips the encoder (modeling_kimi_k25.py:1632, modeling_kimi_k3_vl.py:498), unlike modeling_qwen3vl.py:1565-1569. Please run the unmatched-key check from the plan, log the KV-B summary once in finish(), and add the same info line when the encoder is skipped.

load runs `run_model_streamer()` for real. The returned list records the names
that the fake streamer delivered.
"""
from tensorrt_llm._torch.models.modeling_kimi_linear import KimiLinearForCausalLM

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] This generic ModelLoader helper now imports the Kimi K3 model, and through kimi_kda_mixer.py:27 the fla package, only to spec a MagicMock, so any Kimi/KDA/fla import problem breaks unrelated loader tests. A small local nn.Module subclass that defines load_weights and plan_model_streamer_load would serve as the spec. Also, test_kimi_k3_model_streamer.py calls pytest.importorskip("fla") only in the expert test (test_kimi_k3_model_streamer.py:101), but its two multimodal tests import the same chain and would error instead of skip; please move it to module level, and consider putting that file in tests/unittest/_torch/modeling/ with the other Kimi tests.

self._ensure_mm_encoder()
if self.mm_encoder is not None:
# Name prefixes of vision encoder and projector tensors.
vision_prefixes = (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] These prefixes copy the _detect_prefix candidates in KimiK25VisionModel.load_weights (modeling_kimi_k25.py:910 and :917). If the vision loader learns a new prefix and this tuple is not updated, the streamed load skips those tensors or raises the KeyError at modeling_kimi_k3_vl.py:588, while the native load works. Please hoist the two candidate lists into module constants in modeling_kimi_k25.py and build this tuple from them.

name_map: Dict[str, str],
log_summary: bool = True,
) -> int:
"""Load every non-expert trunk parameter concurrently (with the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] Several docstrings no longer match the code. This one says it loads "every non-expert trunk parameter concurrently", but the plan passes one parameter; _load_attention_fp8 (modeling_kimi_k3_loader.py:386) does not document projection_names; the class docstring (:335-342) is vague ("mixin-like", "would read") where it should say the methods work only as a base of KimiLinearForCausalLM. In modeling_kimi_k3_vl.py, :458-461 says "only __init__ differs", :495-496 says the encoder is recreated in load_weights (the plan now does it too), and :557-558 says every rank keeps the whole vision encoder, which is false when the tower is TP-sharded (modeling_kimi_k25.py:383-385). Please update them.

key for key in checkpoint_keys if key.startswith(vision_prefixes)
}
if not vision_keys:
raise KeyError("Kimi K3 Model Streamer found no vision weights")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Nice to have] With checkpoint_io_policy=model_streamer, a checkpoint that declares a vision tower but stores no vision or projector tensors fails here on every rank, while the native load only warns and continues (modeling_kimi_k25.py:923-925). Raising is safer than serving an uninitialized tower, but the message gives the user no way forward. Please list the searched prefixes and suggest disable_mm_encoder: true, e.g. KeyError(f"Kimi K3 Model Streamer found no tensors starting with {vision_prefixes}; set disable_mm_encoder: true to load only the language model").

@yijingl-nvidia
yijingl-nvidia force-pushed the user/yijingl/kimi-k3-model-streamer branch 6 times, most recently from 2a12f1e to 3a29fdc Compare November 6, 2026 04:24
@yijingl-nvidia
yijingl-nvidia force-pushed the user/yijingl/kimi-k3-model-streamer branch 5 times, most recently from 2e158ca to c8b0cc8 Compare November 6, 2026 22:48
@yijingl-nvidia
yijingl-nvidia force-pushed the user/yijingl/kimi-k3-model-streamer branch 2 times, most recently from b09193f to da61750 Compare November 9, 2026 19:08
To reduce the complex code in model_loader.py, created new files to hold helper functions:
- tensorrt_llm/_torch/models/checkpoints/gms/model_loading.py
- tensorrt_llm/_torch/models/checkpoints/mx/model_loading.py
- tensorrt_llm/_torch/models/checkpoints/memory_init.py
- tensorrt_llm/_torch/models/checkpoints/model_hooks.py

Refactor ModelLoader.load() to be more compact and simpler to understand. Create
private helper functions in model_loader.py to hold load_format=auto loading path,
which includes the HF loading, the rank-striped-read-ahead loading and custom checkpoint
loading options.

Remove an old duplicate doc docs/source/torch/features/checkpoint_loading.md.
Clean up the checkpoint loading doc docs/source/features/checkpoint-loading.md:
- Add missing checkpoint format.
- Simplify explanation.
- Remove extra details most users don't need to know.

Signed-off-by: Yijing Li <257409031+yijingl-nvidia@users.noreply.github.com>
Signed-off-by: Yijing Li <257409031+yijingl-nvidia@users.noreply.github.com>
Move the weight-loading methods of KimiLinearForCausalLM and the helpers that only they use into the new file modeling_kimi_k3_loader.py.
modeling_kimi_linear.py had over 3,300 lines, and about 1,100 of them only turn checkpoint tensors into model parameters.
The code moves unchanged: KimiLinearForCausalLM inherits the methods from KimiK3WeightLoaderMixin, and tests import the moved helpers from the new file.
The inference module _Fp8BlockScaleWeightReadLinear stays in modeling_kimi_linear.py; the loader imports it inside the functions that use it, because modeling_kimi_linear.py imports the loader.
No functional change.

Signed-off-by: Yijing Li <257409031+yijingl-nvidia@users.noreply.github.com>
Add checkpoint_io_policy="model_streamer", which loads a Hugging Face checkpoint with Run:ai Model Streamer.
Model Streamer reads each byte from storage once per node and shares it among the ranks of the node, so large checkpoints load faster.
A model describes what it needs in plan_model_streamer_load(), which returns a ModelStreamerPlan: groups of checkpoint tensor names, each with a function that loads the group.
The new models/checkpoints/model_streamer/model_loading.py streams only the tensors that the ranks of a node need, with run_model_streamer(), and loads each group as soon as its tensors arrive.
A separate draft checkpoint, such as a DSpark drafter, is read with the regular checkpoint reader.
The next commit adds this for Kimi K3.

Signed-off-by: Yijing Li <257409031+yijingl-nvidia@users.noreply.github.com>
Implement plan_model_streamer_load() for Kimi K3 and its multimodal wrapper, with the ModelStreamerPlan from the previous commit.
Each rank lists only the tensors that it needs, for example its own experts, so each node streams only what its ranks use.
Also support Kimi K3 language-only loads that skip the multimodal encoder, document Model Streamer loading in the Kimi K3 README, and add tests.

Signed-off-by: Yijing Li <257409031+yijingl-nvidia@users.noreply.github.com>
@yijingl-nvidia
yijingl-nvidia force-pushed the user/yijingl/kimi-k3-model-streamer branch from da61750 to 764e372 Compare November 10, 2026 18:19

@chienchunhung chienchunhung left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My previous comments have been addressed; thanks. 2 follow-up comments:

  • Reject media requests before scheduling. With disable_mm_encoder=True, image or video requests fail inside batched forward(). The executor then fails all active requests, including valid text requests, and consumes its error budget. Please reject media through validate_multimodal_request_data() before scheduling. Add a mixed batch of separate text and media requests, and verify that only the media request fails. See the forward check.

  • Update the existing K2.5 test double. The new _ensure_mm_encoder() call breaks test_language_weights_preserve_checkpoint_dir. Its SimpleNamespace lacks that method, so the test raises AttributeError before it checks checkpoint metadata. Please update the test double and run the test. See the new helper call.

@hanjingtian

Copy link
Copy Markdown
Collaborator

Could you please rebase this branch onto the latest upstream main to include the CI hang fix in #22101 (6edbe383497a)? The current branch still contains the regression that can cause pipelines to hang for an extended period and worsen CI queueing

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants