Repository navigation
[OMNIML-6078] [1/2] Add the Kimi-VL expert-parallel adapter #3138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: shengliangx/hfep-base-11b-kimi
Are you sure you want to change the base?
Changes from all commits
4bc1e14
910fa1b
0794649
006c5eb
c6535d7
43c1c8b
197ba57
85bd842
7849516
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,6 +91,7 @@ | |
| nemotron_h, | ||
| minimax_m3_vl, | ||
| llama4_text, | ||
| kimi_vl, | ||
| nemotron_h_omni, | ||
| qwen2_moe, | ||
| qwen3, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """What the Kimi-K2.5 and Kimi-VL adapters change in the modeling code their checkpoints ship. | ||
|
|
||
| Both checkpoints' own VLM class builds its language model as ``DeepseekV3ForCausalLM( | ||
| config.text_config)``, a DeepSeek-V3 whose MoE is an ``nn.ModuleList`` of per-expert Linears that | ||
| expert parallelism cannot shard. The adapters point that name at transformers' native class, and | ||
| keep the vision tower and projector from the checkpoint's code. | ||
| """ | ||
|
|
||
| # modelopt.torch.utils and transformers are imported inside the functions: the model registry imports | ||
| # this module, and it must import without transformers, the optional [hf] extra. | ||
| from ._remote_code import keep_patched | ||
|
|
||
|
|
||
| def checkpoint_to_patch( | ||
| label: str, hf_config, ckpt_path: str | None, trust_remote_code: bool | ||
| ) -> str: | ||
| """Return the checkpoint whose own modeling code the adapter ``label`` patches. | ||
|
|
||
| Refuses first, before its ``prepare`` changes anything, a config the adapter cannot patch. | ||
| """ | ||
| if type(hf_config).__module__.startswith("transformers."): | ||
| # transformers' own port (Kimi-K2.5's, from 5.13), which the patch never reaches: a load | ||
| # without trust_remote_code builds it, and so does one of a checkpoint naming no config class. | ||
| raise ValueError( | ||
| f"{label}: expert parallelism runs on the checkpoint's own modeling code, but this " | ||
| f"config is transformers' own port ({type(hf_config).__module__}). Load it with " | ||
| "trust_remote_code=True, from a checkpoint whose auto_map names its own AutoConfig." | ||
| ) | ||
| if not hasattr(hf_config, "text_config"): | ||
| raise ValueError(f"{label}: hf_config has no .text_config (expected a VLM config)") | ||
| if ckpt_path is None: | ||
| raise ValueError( | ||
| f"{label}: expert parallelism patches the checkpoint's own modeling code, so it needs " | ||
| "the checkpoint's path (apply_distributed_support's ckpt_path)." | ||
| ) | ||
| if not trust_remote_code: | ||
| # Patching imports the checkpoint's own modeling code, and transformers' | ||
| # get_class_from_dynamic_module runs it without checking trust_remote_code itself. | ||
| raise ValueError( | ||
| f"{label}: expert parallelism imports the checkpoint's own modeling code; " | ||
| "load it with trust_remote_code=True." | ||
| ) | ||
| return ckpt_path | ||
|
|
||
|
|
||
| def refuse_quantization_only_on_text_config(label: str, hf_config) -> None: | ||
| """Refuse a checkpoint whose ``quantization_config`` only its ``text_config`` declares. | ||
|
|
||
| The adapter replaces ``text_config`` with a native config without it, and the expert-parallel | ||
| load would then read the quantized experts as stored. It loads unquantized checkpoints only, so | ||
| the way in is an unquantized copy of the model. | ||
| """ | ||
| if ( | ||
| getattr(hf_config.text_config, "quantization_config", None) is not None | ||
| and getattr(hf_config, "quantization_config", None) is None | ||
| ): | ||
| raise ValueError( | ||
| f"{label}: the checkpoint declares quantization_config on text_config only, which the " | ||
| "native deepseek_v3 config replacing it drops, so the expert-parallel load would read " | ||
| "its experts as stored. Start from an unquantized copy of the model." | ||
| ) | ||
|
|
||
|
|
||
| def install_compat_import_shims() -> None: | ||
| """Add stubs for symbols the Kimi modeling imports that newer transformers removed, so it imports. | ||
|
|
||
| Each guards a torch.fx / legacy-attn-mask code path the native LM + MoonViT forward never hit, so a benign stub is | ||
| safe. Idempotent (only sets missing attrs). | ||
| """ | ||
| from modelopt.torch.utils import print_rank_0 | ||
|
|
||
| try: | ||
| import transformers.utils.import_utils as iu | ||
|
|
||
| if not hasattr(iu, "is_torch_fx_available"): | ||
| iu.is_torch_fx_available = lambda: False | ||
| except Exception as e: | ||
| print_rank_0(f"[kimi] compat-shim warning (import_utils): {type(e).__name__}: {e}") | ||
| try: | ||
| import transformers.pytorch_utils as pu | ||
|
|
||
| if not hasattr(pu, "is_torch_greater_or_equal_than_1_13"): | ||
| pu.is_torch_greater_or_equal_than_1_13 = True | ||
| except Exception as e: | ||
| print_rank_0(f"[kimi] compat-shim warning (pytorch_utils): {type(e).__name__}: {e}") | ||
| try: | ||
| import transformers.modeling_attn_mask_utils as amu | ||
|
|
||
| if not hasattr(amu, "_prepare_4d_causal_attention_mask"): | ||
|
|
||
| def _prepare_4d_causal_attention_mask(*args, **kwargs): # pragma: no cover | ||
| raise RuntimeError( | ||
| "kimi: remote _prepare_4d_causal_attention_mask stub called -- the native LM " | ||
| "builds its own mask, so this path should be unreachable." | ||
| ) | ||
|
|
||
| amu._prepare_4d_causal_attention_mask = _prepare_4d_causal_attention_mask | ||
| except Exception as e: | ||
| print_rank_0(f"[kimi] compat-shim warning (attn_mask_utils): {type(e).__name__}: {e}") | ||
|
|
||
|
|
||
| def _drop_tie_weights_kwargs(orig_tie): | ||
| """Wrap a remote ``tie_weights()`` that predates the kwargs newer transformers pass to it. | ||
|
|
||
| Dropping ``missing_keys`` as well as ``recompute_mapping`` is safe because the remote | ||
| ``tie_weights`` only calls its language model's, and the Kimi checkpoints set | ||
| ``tie_word_embeddings=False``: nothing is tied, so no tied key is left to report to the load. A | ||
| tied one would still load its embedding into both, with ``lm_head.weight`` reported missing. | ||
| """ | ||
|
|
||
| def _tie_weights(self, *args, **kwargs): # drop recompute_mapping etc. | ||
| return orig_tie(self) | ||
|
|
||
| setattr(_tie_weights, "_kimi_tie_weights_shim", True) | ||
| return _tie_weights | ||
|
|
||
|
|
||
| def _native_for_the_prepared_config(own_class, native_class): | ||
| """The ``DeepseekV3ForCausalLM`` the patched module builds its language model with. | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The comment at module scope explains the local
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in fed6e2f. |
||
| transformers' native class for the native ``DeepseekV3Config`` ``prepare`` puts on | ||
| ``text_config``, and the checkpoint's own class for its own config. Deciding the plan prepares a | ||
| copy of the config before the plan is known, and the patch outlives that copy: a load the plan | ||
| keeps off expert parallelism builds from the config as read, and so gets the checkpoint's own | ||
| language model, as it would with no patch. | ||
| """ | ||
| from transformers import DeepseekV3Config | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The earlier local-import documentation request is still open. The module-scope comment explains the local ModelOpt imports, but not these Transformers imports or those in the shim/patch helpers.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in fed6e2f. |
||
|
|
||
| def build(config, *args, **kwargs): | ||
| built = native_class if isinstance(config, DeepseekV3Config) else own_class | ||
| return built(config, *args, **kwargs) | ||
|
|
||
| setattr(build, "_kimi_native_swap", True) | ||
|
Comment on lines
+133
to
+148
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [SUGGESTION] The swap now binds a plain function where a class used to live, so Under the two routes this PR exercises that holds — the LM is only ever built by the VLM's A callable object that forwards attribute lookups to the native class keeps the dispatch and the class-like surface, for about the same number of lines: def _native_for_the_prepared_config(own_class, native_class):
"""..."""
from transformers import DeepseekV3Config
class _Build:
_kimi_native_swap = True
def __call__(self, config, *args, **kwargs):
built = native_class if isinstance(config, DeepseekV3Config) else own_class
return built(config, *args, **kwargs)
def __getattr__(self, name): # from_pretrained, config_class, ... land on the native class
return getattr(native_class, name)
return _Build()
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kept as a function: nothing reaches this name except the direct call.
The forwarding object would also make |
||
| return build | ||
|
|
||
|
|
||
| def patch_language_model_to_native( | ||
| label: str, modeling: str, vlm_class: str, ckpt_path: str, trust_remote_code: bool | ||
| ) -> None: | ||
| """Point the checkpoint's ``modeling`` module's ``DeepseekV3ForCausalLM`` at the native class. | ||
|
|
||
| Loads the checkpoint's remote ``modeling`` module and replaces its ``DeepseekV3ForCausalLM``, so | ||
| ``vlm_class.__init__`` builds the EP-capable native deepseek_v3 for ``self.language_model`` | ||
| instead of the per-expert Linears version, from the config ``prepare`` made | ||
| (:func:`_native_for_the_prepared_config`). | ||
| """ | ||
| from transformers.dynamic_module_utils import get_class_from_dynamic_module | ||
| from transformers.models.deepseek_v3 import DeepseekV3ForCausalLM as NativeDeepseekV3ForCausalLM | ||
|
|
||
| from modelopt.torch.utils import print_rank_0 | ||
|
|
||
| # Force the dynamic-module loader to stage + import the module into sys.modules. | ||
| get_class_from_dynamic_module( | ||
| f"{modeling}.{vlm_class}", ckpt_path, trust_remote_code=trust_remote_code | ||
| ) | ||
|
|
||
| def to_native(module): | ||
| current = getattr(module, "DeepseekV3ForCausalLM", None) | ||
| if current is None: | ||
| # Not "already native": the VLM class then builds its language model from a class this | ||
| # swap does not reach (renamed, or imported inside __init__), with per-expert Linears. | ||
| raise RuntimeError( | ||
| f"{label}: {module.__name__} defines no module-level DeepseekV3ForCausalLM to swap " | ||
| "for the transformers-native class; the checkpoint's modeling code changed." | ||
| ) | ||
| if getattr(current, "_kimi_native_swap", False): | ||
| return False | ||
| module.DeepseekV3ForCausalLM = _native_for_the_prepared_config( | ||
| current, NativeDeepseekV3ForCausalLM | ||
| ) | ||
| # The remote tie_weights() takes no kwargs, but transformers' post_init and load call | ||
| # tie_weights(recompute_mapping=False) -> TypeError. | ||
| kimi_cls = getattr(module, vlm_class, None) | ||
| if kimi_cls is not None: | ||
| own_tie = vars(kimi_cls).get("tie_weights") | ||
| if own_tie is not None and not getattr(own_tie, "_kimi_tie_weights_shim", False): | ||
| kimi_cls.tie_weights = _drop_tie_weights_kwargs(own_tie) | ||
| return True | ||
|
|
||
| # The dynamic-module loader names the module like | ||
| # ``transformers_modules.<short-repo>.<hash>.<modeling>``. It stays patched when from_pretrained | ||
| # resolves the class again, even from a re-executed module. | ||
| patched = keep_patched(f".{modeling}", to_native) | ||
| if not patched: | ||
| raise RuntimeError( | ||
| f"{label}: could not find a loaded {modeling} in sys.modules to monkey-patch" | ||
| ) | ||
| print_rank_0( | ||
| f"[{label}] monkey-patched DeepseekV3ForCausalLM -> transformers-native (+tie_weights shim) " | ||
| f"in: {patched}" | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """kimi_vl specs (HF model type ``kimi_vl``).""" | ||
|
|
||
| from . import specs |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Expert-parallel preparation for remote-code Kimi-VL (``kimi_vl``). | ||
|
|
||
| ``prepare`` replaces the per-expert DeepSeek-V3 language model the checkpoint's own code builds with | ||
| transformers' native DeepSeek-V3, whose experts the expert-parallel load can shard, and keeps the | ||
| vision tower and projector from that code. It needs trusted remote code and transformers 5.7. | ||
| """ | ||
|
|
||
| from .. import _kimi, _onfly_decompress | ||
| from .._remote_code import stage_full_remote_closure | ||
|
|
||
|
|
||
| def prepare(hf_config, *, ckpt_path: str | None = None, trust_remote_code: bool = False, **_): | ||
| """Load Kimi-VL's language model as the transformers-native deepseek_v3. | ||
|
|
||
| Swap text_config to native deepseek_v3 + apply the EP plan + monkey-patch Kimi-VL's modeling to | ||
| build the native EP-capable DeepseekV3ForCausalLM for the language sub-model. No decompressor: the | ||
| Kimi-VL checkpoint is plain bf16. | ||
| """ | ||
| # Not at module scope: the model registry imports this module, and modelopt.torch.utils.moe | ||
| # imports the registry. | ||
| from modelopt.torch.utils.moe import apply_distributed_support | ||
|
|
||
| ckpt_path = _kimi.checkpoint_to_patch("kimi_vl", hf_config, ckpt_path, trust_remote_code) | ||
| _kimi.refuse_quantization_only_on_text_config("kimi_vl", hf_config) | ||
|
|
||
| _kimi.install_compat_import_shims() | ||
| stage_full_remote_closure() | ||
|
|
||
| # Native deepseek_v3 config for the LM (drops the remote auto_map so the native class is used). | ||
| hf_config.text_config = _onfly_decompress.to_native_backbone_config(hf_config) | ||
| # EP plan onto the native text_config (bubbles up to the VLM root under language_model.*). | ||
| hf_config.text_config = apply_distributed_support("deepseek_v3", hf_config.text_config) | ||
|
|
||
| _kimi.patch_language_model_to_native( | ||
| "kimi_vl", | ||
| "modeling_kimi_vl", | ||
| "KimiVLForConditionalGeneration", | ||
| ckpt_path, | ||
| trust_remote_code, | ||
| ) | ||
| return hf_config |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """kimi_vl specs (HF model type ``kimi_vl``). | ||
|
|
||
| Kimi-VL-A3B: a remote-code VLM whose text backbone is a Moonlight MoE. Its bundled | ||
| modeling builds the language model from per-expert Linears, which the EP machinery cannot shard, so | ||
| ``ep.py`` swaps in the transformers-native deepseek_v3 language model. | ||
|
|
||
| Only the expert-parallel section is registered under ``kimi_vl``: the MoE and export sections stay | ||
| ``deepseek_v3``'s, so lookups by ``kimi_vl`` find neither yet. Nor do ``is_moe``'s name and structure | ||
| fallbacks recognize the native ``DeepseekV3MoE`` blocks ``prepare`` installs, so the HF export's | ||
| MoE-block handling skips them. Sharing the sections is left to a follow-up. | ||
| """ | ||
|
|
||
| from ..deepseek_v3.specs import BASE_MODEL_EP_PLAN | ||
| from ..specs import EPSpec, ModelSpec, register | ||
| from .ep import prepare | ||
|
|
||
| __all__: list[str] = [] | ||
|
|
||
| register( | ||
| ModelSpec( | ||
| model_type="kimi_vl", | ||
| modeling_source="remote_code", | ||
| # prepare swaps a native deepseek_v3 in as the language model, with deepseek_v3's plan. | ||
| ep_spec=EPSpec(prepare=prepare, prepared_ep_plan=BASE_MODEL_EP_PLAN), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [SUGGESTION] With only the EP section registered, The docstring says the MoE/export sections "stay
That is exactly what Nothing in this PR reaches those call sites, and from ..deepseek_v3.specs import BASE_MODEL_EP_PLAN
from ..specs import EPSpec, ExportSpec, ModelSpec, MoESpec, register
register(
ModelSpec(
model_type="kimi_vl",
modeling_source="remote_code",
# prepare swaps a native deepseek_v3 in as the language model, with deepseek_v3's plan.
ep_spec=EPSpec(prepare=prepare, prepared_ep_plan=BASE_MODEL_EP_PLAN),
# prepare's language model is native deepseek_v3: its MoE blocks and export are too.
moe_spec=MoESpec(
block_names=("DeepseekV3MoE",),
expert_linear_names=("gate_proj", "down_proj", "up_proj"),
..., # deepseek_v3's, ideally hoisted to a shared constant as the plan was
),
export_spec=ExportSpec(grouped_expert_export=True),
)
)If you'd rather keep this PR to the EP section, consider making the gap loud instead of silent — e.g. have the follow-up tracked in the docstring name the lookups that miss (
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taking the docstring half in d6ca10f; the shared sections stay with the follow-up. The docstring now names the miss: I'm keeping the sections out of this PR because registering them changes how the HF export treats these models:
That needs its own real-model export check, for both Kimi-VL and Kimi-K2.5 together. Today, for the fused experts these models load with, the skipped input-preparation handler is a no-op, since the amax fallback for fused experts runs during export. The AWQ |
||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The PR checklist still says this is additive and 'runs only on the expert-parallel path', but registering this hook makes
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in the description. The checklist now says what registering the spec reaches beyond the expert-parallel load in this PR on its own:
#2748 later runs |
||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[SUGGESTION] The shim's safety rests on
tie_word_embeddings=False, which nothing checks at runtime.Dropping
recompute_mappingis unconditionally fine, but droppingmissing_keysis only fine under the docstring's premise that nothing is tied.to_native_backbone_configcarriestie_word_embeddingsover from the remotetext_configverbatim, so a Kimi-VL/K2.5 variant (or a user-edited config) that ties embeddings would silently lose the tied-key bookkeeping: the native LM ties the weight butlm_head.weightis never removed from the load'smissing_keys, so a strict load reports a spurious missing key — or, depending on the release's ordering, re-initializes it.Cheap guard inside the wrapper, where the config is reachable:
That turns an assumption documented in a comment into one the code enforces, at no cost on the supported checkpoints.
(2) Why it matters: the failure is silent-to-confusing at load time rather than at patch time, on a path that only triggers for a checkpoint nobody tested. (3) Fix: the guard above, or state the assumption as a refusal in
checkpoint_to_patchnext to the other pre-patch refusals.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declined. I'm not adding the guard, because a tied checkpoint loads correctly through the shim. I documented what the shim loses instead, in d6ca10f.
I checked this with a small VLM whose
tie_weights()takes no kwargs, as the Kimi remote code's does, wrapped by this shim:tie_word_embeddings=True.lm_head.weightand loaded it withfrom_pretrained.lm_head.weightcomes back as the same tensor as the embedding, holding the saved values.lm_head.weightstays in the load's missing-keys report. Nothing is re-initialized.A refusal would therefore block a load that works. No supported checkpoint is tied in any case: Kimi-VL-A3B (Instruct and Thinking-2506) and Kimi-K2.5, K2.6 and K2.7-Code all set
tie_word_embeddings=falseat the top level and ontext_config.