Text Generation
Transformers
Safetensors
Chinese
English
yunmo_next
chat
traditional-chinese
hybrid-attention
kimi-delta-attention
linear-attention
research
custom_code
Instructions to use yuhuanstudio/Yunmo-Next-1B-Instruct with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use yuhuanstudio/Yunmo-Next-1B-Instruct with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="yuhuanstudio/Yunmo-Next-1B-Instruct", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("yuhuanstudio/Yunmo-Next-1B-Instruct", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use yuhuanstudio/Yunmo-Next-1B-Instruct with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "yuhuanstudio/Yunmo-Next-1B-Instruct" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "yuhuanstudio/Yunmo-Next-1B-Instruct", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/yuhuanstudio/Yunmo-Next-1B-Instruct
- SGLang
How to use yuhuanstudio/Yunmo-Next-1B-Instruct with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "yuhuanstudio/Yunmo-Next-1B-Instruct" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "yuhuanstudio/Yunmo-Next-1B-Instruct", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "yuhuanstudio/Yunmo-Next-1B-Instruct" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "yuhuanstudio/Yunmo-Next-1B-Instruct", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use yuhuanstudio/Yunmo-Next-1B-Instruct with Docker Model Runner:
docker model run hf.co/yuhuanstudio/Yunmo-Next-1B-Instruct
Download code/yunmo_next/model.py from yuhuanstudio/Yunmo-Next-1B-Instruct: direct link, hf CLI and curl.
- Browser
- Download file 43.3 kB
-
https://huggingface.co/yuhuanstudio/Yunmo-Next-1B-Instruct/resolve/main/code/yunmo_next/model.py
- Command line
-
hf download hf://yuhuanstudio/Yunmo-Next-1B-Instruct/code/yunmo_next/model.py
-
curl -L -o model.py https://huggingface.co/yuhuanstudio/Yunmo-Next-1B-Instruct/resolve/main/code/yunmo_next/model.py
43.3 kB
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from torch.utils.checkpoint import checkpoint | |
| from .attention import GQAAttentionCache, GatedGroupedQueryAttention | |
| from .attn_res import ( | |
| SlicedDepthAttentionRouter, | |
| SublayerDepthRouters, | |
| sliced_block_accumulate, | |
| sliced_phase1_from_logits, | |
| sliced_phase2_merge, | |
| sliced_source_logits, | |
| ) | |
| from .config import YunmoNextConfig | |
| from .kda_runtime import configure_kda_mixer_runtime | |
| from .packed import document_position_ids | |
| from .training_engine import TrainingEngineConfig | |
| ATTENTION_RESIDUAL_QUERY_INIT_SEED = 17 | |
| ATTENTION_RESIDUAL_QUERY_INIT_OFFSET = 7_900_031 | |
| ATTENTION_RESIDUAL_QUERY_INIT_STD = 0.02 | |
| ATTENTION_RESIDUAL_ORTHOGONAL_QUERY_DEPTH = 24 | |
| ACTIVATION_CHECKPOINT_POLICY_FULL = "full" | |
| ACTIVATION_CHECKPOINT_POLICIES = frozenset({ACTIVATION_CHECKPOINT_POLICY_FULL}) | |
| def orthogonal_attention_residual_query_bank( | |
| query_count: int, | |
| key_dim: int, | |
| *, | |
| device: torch.device | str = "cpu", | |
| dtype: torch.dtype = torch.float32, | |
| ) -> torch.Tensor: | |
| """Return the adopted private-seed orthogonal 24-layer query bank. | |
| The first row is generated for lineage stability but remains zero in the | |
| model: the first pre-mixer read has one completed source, so that query is | |
| structurally unidentifiable. A private CPU generator prevents this | |
| initialization from advancing the common model RNG stream. | |
| """ | |
| if query_count <= 0 or key_dim <= 0 or query_count > key_dim: | |
| raise ValueError( | |
| "orthogonal Attention Residual query bank requires " | |
| "0 < queries <= key_dim" | |
| ) | |
| generator = torch.Generator(device="cpu") | |
| generator.manual_seed( | |
| ( | |
| ATTENTION_RESIDUAL_QUERY_INIT_SEED | |
| + ATTENTION_RESIDUAL_QUERY_INIT_OFFSET | |
| ) | |
| % (2**63 - 1) | |
| ) | |
| basis = torch.randn( | |
| key_dim, | |
| query_count, | |
| generator=generator, | |
| dtype=torch.float32, | |
| ) | |
| rows = torch.linalg.qr(basis, mode="reduced").Q.T.contiguous() | |
| rows.mul_((key_dim**0.5) * ATTENTION_RESIDUAL_QUERY_INIT_STD) | |
| return rows.to(device=device, dtype=dtype) | |
| def _build_recurrent_mixer( | |
| config: YunmoNextConfig, | |
| engine: TrainingEngineConfig, | |
| layer_idx: int, | |
| ) -> nn.Module: | |
| try: | |
| from fla.layers import KimiDeltaAttention | |
| except ImportError as exc: | |
| if not torch.cuda.is_available(): | |
| if engine.recurrent_segment_tokens: | |
| raise RuntimeError( | |
| "serial recurrent execution requires the CUDA KDA runtime" | |
| ) from exc | |
| from .reference_recurrent import ReferenceKimiDeltaAttention | |
| return ReferenceKimiDeltaAttention( | |
| hidden_size=config.hidden_size, | |
| head_dim=config.recurrent_head_dim, | |
| num_heads=config.recurrent_num_heads, | |
| expand_v=config.recurrent_expand_v, | |
| use_short_conv=True, | |
| conv_size=config.short_conv_size, | |
| layer_idx=layer_idx, | |
| norm_eps=config.rms_norm_eps, | |
| ) | |
| raise ImportError( | |
| "Yunmo-Next recurrent layers require the FLA CUDA environment" | |
| ) from exc | |
| mixer = KimiDeltaAttention( | |
| hidden_size=config.hidden_size, | |
| head_dim=config.recurrent_head_dim, | |
| num_heads=config.recurrent_num_heads, | |
| expand_v=config.recurrent_expand_v, | |
| use_short_conv=True, | |
| conv_size=config.short_conv_size, | |
| mode="chunk", | |
| layer_idx=layer_idx, | |
| norm_eps=config.rms_norm_eps, | |
| ) | |
| return configure_kda_mixer_runtime(mixer, engine) | |
| class _SerialChunkedSwiGLU(torch.autograd.Function): | |
| def forward( | |
| ctx, | |
| hidden_states: torch.Tensor, | |
| gate_weight: torch.Tensor, | |
| up_weight: torch.Tensor, | |
| down_weight: torch.Tensor, | |
| chunk_size: int, | |
| ) -> torch.Tensor: | |
| ctx.chunk_size = chunk_size | |
| ctx.save_for_backward(hidden_states, gate_weight, up_weight, down_weight) | |
| return torch.cat( | |
| [ | |
| F.linear( | |
| F.silu(F.linear(chunk, gate_weight)) | |
| * F.linear(chunk, up_weight), | |
| down_weight, | |
| ) | |
| for chunk in hidden_states.split(chunk_size, dim=-2) | |
| ], | |
| dim=-2, | |
| ) | |
| def backward(ctx, grad_output: torch.Tensor): | |
| hidden_states, gate_weight, up_weight, down_weight = ctx.saved_tensors | |
| grad_hidden_chunks: list[torch.Tensor] = [] | |
| accumulator_dtype = ( | |
| torch.float64 if gate_weight.dtype == torch.float64 else torch.float32 | |
| ) | |
| grad_gate = torch.zeros_like(gate_weight, dtype=accumulator_dtype) | |
| grad_up = torch.zeros_like(up_weight, dtype=accumulator_dtype) | |
| grad_down = torch.zeros_like(down_weight, dtype=accumulator_dtype) | |
| hidden_chunks = hidden_states.split(ctx.chunk_size, dim=-2) | |
| output_grad_chunks = grad_output.split(ctx.chunk_size, dim=-2) | |
| create_graph = torch.is_grad_enabled() | |
| for hidden_chunk, output_grad_chunk in zip( | |
| hidden_chunks, output_grad_chunks, strict=True | |
| ): | |
| with torch.enable_grad(): | |
| chunk = hidden_chunk.detach().requires_grad_(True) | |
| gate = gate_weight.detach().requires_grad_(True) | |
| up = up_weight.detach().requires_grad_(True) | |
| down = down_weight.detach().requires_grad_(True) | |
| output = F.linear( | |
| F.silu(F.linear(chunk, gate)) * F.linear(chunk, up), | |
| down, | |
| ) | |
| chunk_grad, gate_grad, up_grad, down_grad = torch.autograd.grad( | |
| output, | |
| (chunk, gate, up, down), | |
| output_grad_chunk, | |
| create_graph=create_graph, | |
| ) | |
| grad_hidden_chunks.append(chunk_grad) | |
| grad_gate.add_(gate_grad.to(accumulator_dtype)) | |
| grad_up.add_(up_grad.to(accumulator_dtype)) | |
| grad_down.add_(down_grad.to(accumulator_dtype)) | |
| return ( | |
| torch.cat(grad_hidden_chunks, dim=-2), | |
| grad_gate.to(gate_weight.dtype), | |
| grad_up.to(up_weight.dtype), | |
| grad_down.to(down_weight.dtype), | |
| None, | |
| ) | |
| class SwiGLU(nn.Module): | |
| def __init__( | |
| self, | |
| hidden_size: int, | |
| intermediate_size: int, | |
| engine: TrainingEngineConfig, | |
| ) -> None: | |
| super().__init__() | |
| self.sequence_chunk_size = engine.feed_forward_chunk_tokens | |
| self.sequence_serial_backward = engine.feed_forward_serial_backward | |
| self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False) | |
| self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False) | |
| self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False) | |
| def _project(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| gate = self.gate_proj(hidden_states) | |
| up = self.up_proj(hidden_states) | |
| activated = F.silu(gate) * up | |
| return self.down_proj(activated) | |
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| chunk_size = self.sequence_chunk_size | |
| if chunk_size <= 0 or hidden_states.shape[-2] <= chunk_size: | |
| return self._project(hidden_states) | |
| if self.sequence_serial_backward: | |
| return _SerialChunkedSwiGLU.apply( | |
| hidden_states, | |
| self.gate_proj.weight, | |
| self.up_proj.weight, | |
| self.down_proj.weight, | |
| chunk_size, | |
| ) | |
| return torch.cat( | |
| [ | |
| self._project(chunk) | |
| for chunk in hidden_states.split(chunk_size, dim=-2) | |
| ], | |
| dim=-2, | |
| ) | |
| class YunmoBlock(nn.Module): | |
| def __init__( | |
| self, | |
| config: YunmoNextConfig, | |
| engine: TrainingEngineConfig, | |
| layer_idx: int, | |
| ) -> None: | |
| super().__init__() | |
| self.layer_idx = layer_idx | |
| self.input_norm = nn.RMSNorm( | |
| config.hidden_size, | |
| eps=config.rms_norm_eps, | |
| ) | |
| self.post_mixer_norm = nn.RMSNorm( | |
| config.hidden_size, | |
| eps=config.rms_norm_eps, | |
| ) | |
| self.mixer = ( | |
| GatedGroupedQueryAttention(config, layer_idx) | |
| if config.is_attention_layer(layer_idx) | |
| else _build_recurrent_mixer(config, engine, layer_idx) | |
| ) | |
| self.mlp = SwiGLU( | |
| config.hidden_size, | |
| config.intermediate_size, | |
| engine, | |
| ) | |
| self.depth_routers = SublayerDepthRouters( | |
| config.hidden_size, | |
| config.rms_norm_eps, | |
| ) | |
| def mixer_output( | |
| self, | |
| hidden_states: torch.Tensor, | |
| position_ids: torch.Tensor, | |
| cu_seqlens: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| """Return one mixer transformation before its residual write.""" | |
| normalized = self.input_norm(hidden_states) | |
| if isinstance(self.mixer, GatedGroupedQueryAttention): | |
| return self.mixer(normalized, position_ids, cu_seqlens) | |
| return self.mixer(normalized, cu_seqlens=cu_seqlens)[0] | |
| def mlp_output(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| """Return one feed-forward transformation before its residual write.""" | |
| return self.mlp(self.post_mixer_norm(hidden_states)) | |
| def mixer_output_cached( | |
| self, | |
| hidden_states: torch.Tensor, | |
| position_ids: torch.Tensor, | |
| *, | |
| fla_cache: object, | |
| attention_cache: GQAAttentionCache | None, | |
| ) -> tuple[torch.Tensor, GQAAttentionCache | None]: | |
| normalized = self.input_norm(hidden_states) | |
| if isinstance(self.mixer, GatedGroupedQueryAttention): | |
| if attention_cache is not None and not isinstance( | |
| attention_cache, | |
| GQAAttentionCache, | |
| ): | |
| raise TypeError("wrong cache type for grouped-query attention") | |
| return self.mixer.forward_cached( | |
| normalized, | |
| position_ids, | |
| attention_cache, | |
| ) | |
| mixed, _, _ = self.mixer( | |
| normalized, | |
| past_key_values=fla_cache, | |
| use_cache=True, | |
| ) | |
| return mixed, attention_cache | |
| class YunmoNextOutput: | |
| loss: torch.Tensor | None = None | |
| logits: torch.Tensor | None = None | |
| hidden_states: torch.Tensor | None = None | |
| class YunmoNextCUDAState: | |
| """Bounded KDA/local-attention state plus sparse global KV.""" | |
| fla_cache: object | |
| attention: dict[int, GQAAttentionCache] | |
| seen_tokens: int = 0 | |
| class YunmoNextForCausalLM(nn.Module): | |
| def __init__( | |
| self, | |
| config: YunmoNextConfig, | |
| training_engine: TrainingEngineConfig | None = None, | |
| ) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.training_engine = ( | |
| TrainingEngineConfig() | |
| if training_engine is None | |
| else training_engine | |
| ) | |
| if not isinstance(self.training_engine, TrainingEngineConfig): | |
| raise TypeError("training_engine must be TrainingEngineConfig") | |
| self.activation_checkpointing_runtime_policy = ( | |
| ACTIVATION_CHECKPOINT_POLICY_FULL | |
| ) | |
| self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) | |
| self.layers = nn.ModuleList( | |
| [ | |
| YunmoBlock(config, self.training_engine, layer_idx) | |
| for layer_idx in range(config.num_hidden_layers) | |
| ] | |
| ) | |
| self._enable_cell_echo_biases() | |
| self.final_depth_router = SlicedDepthAttentionRouter( | |
| config.hidden_size, | |
| config.rms_norm_eps, | |
| ) | |
| self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps) | |
| self.reset_parameters() | |
| self._install_cell_phase_mlp_geometry() | |
| def set_activation_checkpointing_runtime_policy( | |
| self, | |
| policy: str, | |
| ) -> None: | |
| if policy not in ACTIVATION_CHECKPOINT_POLICIES: | |
| raise ValueError( | |
| f"unsupported activation-checkpointing runtime policy: {policy}" | |
| ) | |
| self.activation_checkpointing_runtime_policy = policy | |
| def _activation_checkpoint( | |
| self, | |
| function, | |
| *args: torch.Tensor, | |
| ): | |
| return checkpoint(function, *args, use_reentrant=False) | |
| def reset_parameters(self) -> None: | |
| nn.init.normal_( | |
| self.embed_tokens.weight, | |
| mean=0.0, | |
| std=self.config.initializer_range, | |
| ) | |
| for module in self.modules(): | |
| if isinstance(module, nn.Linear): | |
| nn.init.normal_( | |
| module.weight, | |
| mean=0.0, | |
| std=self.config.initializer_range, | |
| ) | |
| residual_scale = self.config.residual_write_initialization_scale | |
| for block in self.layers: | |
| block.mlp.down_proj.weight.mul_(residual_scale) | |
| output_projection = getattr(block.mixer, "o_proj", None) | |
| if output_projection is not None: | |
| output_projection.weight.mul_(residual_scale) | |
| self._reset_attention_residual_queries() | |
| def _install_cell_phase_mlp_geometry(self) -> None: | |
| """Install layer-local widths without perturbing non-MLP RNG mapping. | |
| Every layer first constructs and initializes the common baseline width. | |
| Differently shaped MLPs are then installed from private layer-local | |
| random streams without moving the rest of the model's seed mapping. | |
| """ | |
| seed = int(torch.initial_seed()) | |
| modulus = 2**63 - 1 | |
| for layer_idx, (layer, width) in enumerate( | |
| zip( | |
| self.layers, | |
| self.config.mlp_intermediate_sizes, | |
| strict=True, | |
| ) | |
| ): | |
| mlp = SwiGLU( | |
| self.config.hidden_size, | |
| width, | |
| self.training_engine, | |
| ) | |
| if mlp.gate_proj.weight.device.type == "meta": | |
| layer.mlp = mlp | |
| continue | |
| generator = torch.Generator(device=mlp.gate_proj.weight.device) | |
| generator.manual_seed( | |
| (seed * 1_000_000 + 3 + layer_idx) % modulus | |
| ) | |
| for projection in ( | |
| mlp.gate_proj, | |
| mlp.up_proj, | |
| mlp.down_proj, | |
| ): | |
| nn.init.normal_( | |
| projection.weight, | |
| mean=0.0, | |
| std=self.config.initializer_range, | |
| generator=generator, | |
| ) | |
| mlp.down_proj.weight.mul_( | |
| self.config.residual_write_initialization_scale | |
| ) | |
| layer.mlp = mlp | |
| def _reset_attention_residual_queries(self) -> None: | |
| if ( | |
| self.config.num_hidden_layers | |
| != ATTENTION_RESIDUAL_ORTHOGONAL_QUERY_DEPTH | |
| ): | |
| return | |
| routers = self._sliced_router_bank() | |
| expected = 2 * ATTENTION_RESIDUAL_ORTHOGONAL_QUERY_DEPTH + 1 | |
| if len(routers) != expected or any( | |
| router.key_dim != 64 for router in routers | |
| ): | |
| raise RuntimeError( | |
| "orthogonal Attention Residual initialization requires " | |
| "49 rank-64 queries" | |
| ) | |
| bank = orthogonal_attention_residual_query_bank( | |
| len(routers), | |
| routers[0].key_dim, | |
| device=routers[0].query.device, | |
| dtype=routers[0].query.dtype, | |
| ) | |
| routers[0].query.zero_() | |
| for router, row in zip(routers[1:], bank[1:], strict=True): | |
| router.query.copy_(row) | |
| def parameter_count(self) -> int: | |
| return sum(parameter.numel() for parameter in self.parameters()) | |
| def _apply_depth_router( | |
| self, | |
| router: SlicedDepthAttentionRouter, | |
| sources: list[torch.Tensor], | |
| routing_trace: list[torch.Tensor] | None, | |
| ) -> torch.Tensor: | |
| if ( | |
| self.training_engine.activation_checkpointing | |
| and self.training | |
| and routing_trace is None | |
| ): | |
| def route_checkpointed(*inputs: torch.Tensor) -> torch.Tensor: | |
| routed = router(list(inputs)) | |
| assert isinstance(routed, torch.Tensor) | |
| return routed | |
| return self._activation_checkpoint(route_checkpointed, *sources) | |
| if routing_trace is None: | |
| output = router(sources) | |
| assert isinstance(output, torch.Tensor) | |
| return output | |
| output, weights = router(sources, return_weights=True) | |
| routing_trace.append(weights.detach()) | |
| return output | |
| def _mixer_output( | |
| self, | |
| layer: YunmoBlock, | |
| hidden_states: torch.Tensor, | |
| position_ids: torch.Tensor, | |
| cu_seqlens: torch.Tensor | None, | |
| ) -> torch.Tensor: | |
| if self.training_engine.activation_checkpointing and self.training: | |
| return self._activation_checkpoint( | |
| layer.mixer_output, | |
| hidden_states, | |
| position_ids, | |
| cu_seqlens, | |
| ) | |
| return layer.mixer_output(hidden_states, position_ids, cu_seqlens) | |
| def _mlp_output( | |
| self, | |
| layer: YunmoBlock, | |
| hidden_states: torch.Tensor, | |
| ) -> torch.Tensor: | |
| if self.training_engine.activation_checkpointing and self.training: | |
| return self._activation_checkpoint(layer.mlp_output, hidden_states) | |
| return layer.mlp_output(hidden_states) | |
| def _sliced_router_bank(self) -> list[SlicedDepthAttentionRouter]: | |
| routers: list[SlicedDepthAttentionRouter] = [] | |
| for layer in self.layers: | |
| routers.extend( | |
| ( | |
| layer.depth_routers.pre_mixer, | |
| layer.depth_routers.pre_mlp, | |
| ) | |
| ) | |
| routers.append(self.final_depth_router) | |
| return routers | |
| def _enable_cell_echo_biases(self) -> None: | |
| """Address each attention-first cell's completed GQA source.""" | |
| block_size = self.config.attn_res_sublayers_per_block | |
| period = self.config.recurrent_period | |
| for cell_start in range(0, len(self.layers), period): | |
| target_layer = cell_start + period - 1 | |
| router = self.layers[target_layer].depth_routers.pre_mixer | |
| gqa_sublayer = 2 * cell_start | |
| preferred_source = 1 + gqa_sublayer // block_size | |
| target_read = 2 * target_layer | |
| if preferred_source * block_size > target_read: | |
| raise RuntimeError( | |
| "cell GQA source is not completed before Cell-Echo read" | |
| ) | |
| router.enable_source_bias(preferred_source) | |
| def _forward_hidden_checkpointed_blocks( | |
| self, | |
| embedding: torch.Tensor, | |
| position_ids: torch.Tensor, | |
| cu_seqlens: torch.Tensor | None, | |
| routers: list[SlicedDepthAttentionRouter], | |
| ) -> torch.Tensor: | |
| """Checkpoint each complete two-phase Attention Residual block.""" | |
| block_size = self.config.attn_res_sublayers_per_block | |
| total_sublayers = 2 * len(self.layers) | |
| if block_size % 2 != 0 or total_sublayers % block_size != 0: | |
| raise ValueError( | |
| "block-checkpointed Attention Residual requires complete " | |
| "even sublayer blocks" | |
| ) | |
| layers_per_block = block_size // 2 | |
| completed_values = [embedding] | |
| for layer_first in range(0, len(self.layers), layers_per_block): | |
| layer_end = layer_first + layers_per_block | |
| sublayer_first = 2 * layer_first | |
| block_layers = tuple(self.layers[layer_first:layer_end]) | |
| block_routers = tuple( | |
| routers[sublayer_first : sublayer_first + block_size] | |
| ) | |
| source_count = len(completed_values) | |
| def run_block( | |
| *inputs: torch.Tensor, | |
| block_layers: tuple[YunmoBlock, ...] = block_layers, | |
| block_routers: tuple[SlicedDepthAttentionRouter, ...] = block_routers, | |
| source_count: int = source_count, | |
| ) -> torch.Tensor: | |
| checkpoint_sources = list(inputs[:source_count]) | |
| logits = torch.stack( | |
| [ | |
| sliced_source_logits( | |
| source, | |
| block_routers, | |
| source_index=source_index, | |
| ) | |
| for source_index, source in enumerate(checkpoint_sources) | |
| ], | |
| dim=1, | |
| ) | |
| outputs, lses, _ = sliced_phase1_from_logits( | |
| checkpoint_sources, | |
| logits, | |
| routers=block_routers, | |
| ) | |
| phase_outputs = outputs.unbind(0) | |
| phase_lses = lses.unbind(0) | |
| partial_block: torch.Tensor | None = None | |
| local_sublayer = 0 | |
| for layer in block_layers: | |
| mixer_input = phase_outputs[local_sublayer] | |
| if partial_block is not None: | |
| mixer_input, _ = sliced_phase2_merge( | |
| block_routers[local_sublayer], | |
| partial_block, | |
| mixer_input, | |
| phase_lses[local_sublayer], | |
| ) | |
| mixer_delta = layer.mixer_output( | |
| mixer_input, | |
| position_ids, | |
| cu_seqlens, | |
| ) | |
| partial_block = sliced_block_accumulate( | |
| partial_block, | |
| mixer_delta, | |
| ) | |
| local_sublayer += 1 | |
| mlp_input, _ = sliced_phase2_merge( | |
| block_routers[local_sublayer], | |
| partial_block, | |
| phase_outputs[local_sublayer], | |
| phase_lses[local_sublayer], | |
| ) | |
| partial_block = sliced_block_accumulate( | |
| partial_block, | |
| layer.mlp_output(mlp_input), | |
| ) | |
| local_sublayer += 1 | |
| if partial_block is None or local_sublayer != len(block_routers): | |
| raise RuntimeError( | |
| "Attention Residual block did not close exactly" | |
| ) | |
| return partial_block | |
| completed_values.append( | |
| self._activation_checkpoint(run_block, *completed_values) | |
| ) | |
| hidden_states = self._apply_depth_router( | |
| self.final_depth_router, | |
| completed_values, | |
| None, | |
| ) | |
| return self.norm(hidden_states) | |
| def _forward_hidden_two_phase_attention_residual( | |
| self, | |
| embedding: torch.Tensor, | |
| position_ids: torch.Tensor, | |
| cu_seqlens: torch.Tensor | None, | |
| layer_trace: list[torch.Tensor] | None, | |
| routing_trace: list[torch.Tensor] | None, | |
| ) -> torch.Tensor: | |
| routers = self._sliced_router_bank() | |
| total_sublayers = 2 * len(self.layers) | |
| block_size = self.config.attn_res_sublayers_per_block | |
| if ( | |
| self.training_engine.activation_checkpointing | |
| and self.training | |
| and layer_trace is None | |
| and routing_trace is None | |
| and block_size % 2 == 0 | |
| and total_sublayers % block_size == 0 | |
| ): | |
| return self._forward_hidden_checkpointed_blocks( | |
| embedding, | |
| position_ids, | |
| cu_seqlens, | |
| routers, | |
| ) | |
| completed_values = [embedding] | |
| completed_logits = [ | |
| sliced_source_logits(embedding, routers, source_index=0) | |
| ] | |
| completed_first_reads = [0] | |
| partial_block: torch.Tensor | None = None | |
| phase_first = -1 | |
| phase_end = -1 | |
| phase_outputs: tuple[torch.Tensor, ...] = () | |
| phase_lses: tuple[torch.Tensor, ...] = () | |
| phase_weights: tuple[torch.Tensor, ...] | None = None | |
| def invalidate_phase() -> None: | |
| nonlocal phase_first, phase_end, phase_outputs, phase_lses, phase_weights | |
| phase_first = -1 | |
| phase_end = -1 | |
| phase_outputs = () | |
| phase_lses = () | |
| phase_weights = None | |
| def append_completed(source: torch.Tensor, first_read: int) -> None: | |
| source_index = len(completed_values) | |
| completed_values.append(source) | |
| completed_logits.append( | |
| sliced_source_logits( | |
| source, | |
| routers[first_read:], | |
| source_index=source_index, | |
| ) | |
| ) | |
| completed_first_reads.append(first_read) | |
| invalidate_phase() | |
| def ensure_phase(read_idx: int) -> None: | |
| nonlocal phase_first, phase_end, phase_outputs, phase_lses, phase_weights | |
| if phase_first <= read_idx < phase_end: | |
| return | |
| phase_first = read_idx | |
| phase_end = ( | |
| min( | |
| ((read_idx // block_size) + 1) * block_size, | |
| total_sublayers, | |
| ) | |
| if read_idx < total_sublayers | |
| else total_sublayers + 1 | |
| ) | |
| logits = torch.stack( | |
| [ | |
| source_logits[ | |
| phase_first - first_read : phase_end - first_read | |
| ] | |
| for source_logits, first_read in zip( | |
| completed_logits, | |
| completed_first_reads, | |
| strict=True, | |
| ) | |
| ], | |
| dim=1, | |
| ) | |
| outputs, lses, weights = sliced_phase1_from_logits( | |
| completed_values, | |
| logits, | |
| routers=routers[phase_first:phase_end], | |
| return_weights=routing_trace is not None, | |
| ) | |
| phase_outputs = outputs.unbind(0) | |
| phase_lses = lses.unbind(0) | |
| phase_weights = weights.unbind(0) if weights is not None else None | |
| def read_residual(read_idx: int) -> torch.Tensor: | |
| ensure_phase(read_idx) | |
| phase_idx = read_idx - phase_first | |
| output = phase_outputs[phase_idx] | |
| weights = ( | |
| phase_weights[phase_idx] if phase_weights is not None else None | |
| ) | |
| if partial_block is not None: | |
| output, partial_probability = sliced_phase2_merge( | |
| routers[read_idx], | |
| partial_block, | |
| output, | |
| phase_lses[phase_idx], | |
| ) | |
| if weights is not None: | |
| completed_mass = 1.0 - partial_probability | |
| weights = torch.cat( | |
| [ | |
| weights * completed_mass.unsqueeze(0), | |
| partial_probability.unsqueeze(0), | |
| ], | |
| dim=0, | |
| ) | |
| if routing_trace is not None: | |
| assert weights is not None | |
| routing_trace.append(weights.detach()) | |
| return output | |
| sublayer_idx = 0 | |
| for layer in self.layers: | |
| mixer_input = read_residual(sublayer_idx) | |
| mixer_delta = self._mixer_output( | |
| layer, | |
| mixer_input, | |
| position_ids, | |
| cu_seqlens, | |
| ) | |
| partial_block = sliced_block_accumulate( | |
| partial_block, | |
| mixer_delta, | |
| ) | |
| sublayer_idx += 1 | |
| if sublayer_idx % block_size == 0: | |
| append_completed(partial_block, sublayer_idx) | |
| partial_block = None | |
| mlp_input = read_residual(sublayer_idx) | |
| mlp_delta = self._mlp_output(layer, mlp_input) | |
| partial_block = sliced_block_accumulate(partial_block, mlp_delta) | |
| sublayer_idx += 1 | |
| if layer_trace is not None: | |
| layer_trace.append(partial_block) | |
| if sublayer_idx % block_size == 0: | |
| append_completed(partial_block, sublayer_idx) | |
| partial_block = None | |
| if partial_block is not None: | |
| append_completed(partial_block, total_sublayers) | |
| hidden_states = read_residual(total_sublayers) | |
| return self.norm(hidden_states) | |
| def forward_hidden( | |
| self, | |
| input_ids: torch.Tensor, | |
| position_ids: torch.Tensor | None = None, | |
| cu_seqlens: torch.Tensor | None = None, | |
| *, | |
| layer_trace: list[torch.Tensor] | None = None, | |
| routing_trace: list[torch.Tensor] | None = None, | |
| ) -> torch.Tensor: | |
| return self._forward_hidden_from_embeddings( | |
| self.embed_tokens(input_ids), | |
| position_ids, | |
| cu_seqlens, | |
| layer_trace=layer_trace, | |
| routing_trace=routing_trace, | |
| ) | |
| def _forward_hidden_from_embeddings( | |
| self, | |
| embeddings: torch.Tensor, | |
| position_ids: torch.Tensor | None = None, | |
| cu_seqlens: torch.Tensor | None = None, | |
| *, | |
| layer_trace: list[torch.Tensor] | None = None, | |
| routing_trace: list[torch.Tensor] | None = None, | |
| ) -> torch.Tensor: | |
| """Run the adopted text stack from already-constructed embeddings. | |
| This is the only non-token entry seam. It adds no module or parameter | |
| and leaves creation or transformation of embeddings to the caller, | |
| keeping the ordinary token path independent of those concerns. | |
| """ | |
| if embeddings.ndim != 3: | |
| raise ValueError( | |
| "embeddings must have shape [batch, sequence, hidden]" | |
| ) | |
| batch, sequence, hidden_size = embeddings.shape | |
| if hidden_size != self.config.hidden_size: | |
| raise ValueError("embedding hidden size disagrees with model config") | |
| if position_ids is None: | |
| position_ids = ( | |
| document_position_ids(cu_seqlens, sequence) | |
| if cu_seqlens is not None | |
| else torch.arange( | |
| sequence, | |
| device=embeddings.device, | |
| ).expand(batch, -1) | |
| ) | |
| elif position_ids.shape != (batch, sequence): | |
| raise ValueError( | |
| "position IDs must match embedding batch and sequence" | |
| ) | |
| return self._forward_hidden_two_phase_attention_residual( | |
| embeddings, | |
| position_ids, | |
| cu_seqlens, | |
| layer_trace, | |
| routing_trace, | |
| ) | |
| def _forward_hidden_two_phase_attention_residual_cached( | |
| self, | |
| embedding: torch.Tensor, | |
| position_ids: torch.Tensor, | |
| cache: YunmoNextCUDAState, | |
| layer_trace: list[torch.Tensor] | None, | |
| routing_trace: list[torch.Tensor] | None, | |
| ) -> torch.Tensor: | |
| routers = self._sliced_router_bank() | |
| total_sublayers = 2 * len(self.layers) | |
| block_size = self.config.attn_res_sublayers_per_block | |
| completed_values = [embedding] | |
| completed_logits = [ | |
| sliced_source_logits(embedding, routers, source_index=0) | |
| ] | |
| completed_first_reads = [0] | |
| partial_block: torch.Tensor | None = None | |
| phase_first = -1 | |
| phase_end = -1 | |
| phase_outputs: tuple[torch.Tensor, ...] = () | |
| phase_lses: tuple[torch.Tensor, ...] = () | |
| phase_weights: tuple[torch.Tensor, ...] | None = None | |
| def invalidate_phase() -> None: | |
| nonlocal phase_first, phase_end, phase_outputs, phase_lses, phase_weights | |
| phase_first = -1 | |
| phase_end = -1 | |
| phase_outputs = () | |
| phase_lses = () | |
| phase_weights = None | |
| def append_completed(source: torch.Tensor, first_read: int) -> None: | |
| source_index = len(completed_values) | |
| completed_values.append(source) | |
| completed_logits.append( | |
| sliced_source_logits( | |
| source, | |
| routers[first_read:], | |
| source_index=source_index, | |
| ) | |
| ) | |
| completed_first_reads.append(first_read) | |
| invalidate_phase() | |
| def ensure_phase(read_idx: int) -> None: | |
| nonlocal phase_first, phase_end, phase_outputs, phase_lses, phase_weights | |
| if phase_first <= read_idx < phase_end: | |
| return | |
| phase_first = read_idx | |
| phase_end = ( | |
| min( | |
| ((read_idx // block_size) + 1) * block_size, | |
| total_sublayers, | |
| ) | |
| if read_idx < total_sublayers | |
| else total_sublayers + 1 | |
| ) | |
| logits = torch.stack( | |
| [ | |
| source_logits[ | |
| phase_first - first_read : phase_end - first_read | |
| ] | |
| for source_logits, first_read in zip( | |
| completed_logits, | |
| completed_first_reads, | |
| strict=True, | |
| ) | |
| ], | |
| dim=1, | |
| ) | |
| outputs, lses, weights = sliced_phase1_from_logits( | |
| completed_values, | |
| logits, | |
| routers=routers[phase_first:phase_end], | |
| return_weights=routing_trace is not None, | |
| ) | |
| phase_outputs = outputs.unbind(0) | |
| phase_lses = lses.unbind(0) | |
| phase_weights = weights.unbind(0) if weights is not None else None | |
| def read_residual(read_idx: int) -> torch.Tensor: | |
| ensure_phase(read_idx) | |
| phase_idx = read_idx - phase_first | |
| output = phase_outputs[phase_idx] | |
| weights = ( | |
| phase_weights[phase_idx] if phase_weights is not None else None | |
| ) | |
| if partial_block is not None: | |
| output, partial_probability = sliced_phase2_merge( | |
| routers[read_idx], | |
| partial_block, | |
| output, | |
| phase_lses[phase_idx], | |
| ) | |
| if weights is not None: | |
| weights = torch.cat( | |
| [ | |
| weights | |
| * (1.0 - partial_probability).unsqueeze(0), | |
| partial_probability.unsqueeze(0), | |
| ], | |
| dim=0, | |
| ) | |
| if routing_trace is not None: | |
| assert weights is not None | |
| routing_trace.append(weights.detach()) | |
| return output | |
| sublayer_idx = 0 | |
| for layer_idx, layer in enumerate(self.layers): | |
| mixer_input = read_residual(sublayer_idx) | |
| mixer_delta, layer_attention_cache = layer.mixer_output_cached( | |
| mixer_input, | |
| position_ids, | |
| fla_cache=cache.fla_cache, | |
| attention_cache=cache.attention.get(layer_idx), | |
| ) | |
| if layer_attention_cache is not None: | |
| cache.attention[layer_idx] = layer_attention_cache | |
| partial_block = sliced_block_accumulate( | |
| partial_block, | |
| mixer_delta, | |
| ) | |
| sublayer_idx += 1 | |
| if sublayer_idx % block_size == 0: | |
| append_completed(partial_block, sublayer_idx) | |
| partial_block = None | |
| mlp_input = read_residual(sublayer_idx) | |
| mlp_delta = layer.mlp_output(mlp_input) | |
| partial_block = sliced_block_accumulate(partial_block, mlp_delta) | |
| sublayer_idx += 1 | |
| if layer_trace is not None: | |
| layer_trace.append(partial_block) | |
| if sublayer_idx % block_size == 0: | |
| append_completed(partial_block, sublayer_idx) | |
| partial_block = None | |
| if partial_block is not None: | |
| append_completed(partial_block, total_sublayers) | |
| return self.norm(read_residual(total_sublayers)) | |
| def forward_hidden_cached( | |
| self, | |
| input_ids: torch.Tensor, | |
| cache: YunmoNextCUDAState | None = None, | |
| position_ids: torch.Tensor | None = None, | |
| *, | |
| layer_trace: list[torch.Tensor] | None = None, | |
| routing_trace: list[torch.Tensor] | None = None, | |
| ) -> tuple[torch.Tensor, YunmoNextCUDAState]: | |
| """Exact single-document chunked prefill for the adopted model.""" | |
| if self.training: | |
| raise RuntimeError("cached inference requires model.eval()") | |
| return self._forward_hidden_cached_from_embeddings( | |
| self.embed_tokens(input_ids), | |
| cache, | |
| position_ids, | |
| layer_trace=layer_trace, | |
| routing_trace=routing_trace, | |
| ) | |
| def _forward_hidden_cached_from_embeddings( | |
| self, | |
| embeddings: torch.Tensor, | |
| cache: YunmoNextCUDAState | None = None, | |
| position_ids: torch.Tensor | None = None, | |
| *, | |
| layer_trace: list[torch.Tensor] | None = None, | |
| routing_trace: list[torch.Tensor] | None = None, | |
| ) -> tuple[torch.Tensor, YunmoNextCUDAState]: | |
| if self.training: | |
| raise RuntimeError("cached inference requires model.eval()") | |
| if embeddings.ndim != 3: | |
| raise ValueError( | |
| "embeddings must have shape [batch, sequence, hidden]" | |
| ) | |
| batch, sequence, hidden_size = embeddings.shape | |
| if hidden_size != self.config.hidden_size: | |
| raise ValueError("embedding hidden size disagrees with model config") | |
| if cache is None: | |
| try: | |
| from fla.models.utils import Cache | |
| except ImportError as exc: | |
| raise ImportError( | |
| "cached recurrent inference requires FLA" | |
| ) from exc | |
| cache = YunmoNextCUDAState( | |
| fla_cache=Cache(), | |
| attention={}, | |
| seen_tokens=0, | |
| ) | |
| if position_ids is None: | |
| position_ids = torch.arange( | |
| cache.seen_tokens, | |
| cache.seen_tokens + sequence, | |
| device=embeddings.device, | |
| ).expand(batch, -1) | |
| elif position_ids.shape != (batch, sequence): | |
| raise ValueError("cached position IDs must match embeddings") | |
| hidden_states = self._forward_hidden_two_phase_attention_residual_cached( | |
| embeddings, | |
| position_ids, | |
| cache, | |
| layer_trace, | |
| routing_trace, | |
| ) | |
| cache.seen_tokens += sequence | |
| return hidden_states, cache | |
| def forward_cached( | |
| self, | |
| input_ids: torch.Tensor, | |
| cache: YunmoNextCUDAState | None = None, | |
| position_ids: torch.Tensor | None = None, | |
| *, | |
| materialize_logits: bool = True, | |
| ) -> tuple[YunmoNextOutput, YunmoNextCUDAState]: | |
| hidden_states, cache = self.forward_hidden_cached( | |
| input_ids, | |
| cache, | |
| position_ids, | |
| ) | |
| logits = ( | |
| F.linear(hidden_states, self.embed_tokens.weight) | |
| if materialize_logits | |
| else None | |
| ) | |
| return YunmoNextOutput( | |
| logits=logits, | |
| hidden_states=hidden_states, | |
| ), cache | |
| def _forward_cached_from_embeddings( | |
| self, | |
| embeddings: torch.Tensor, | |
| cache: YunmoNextCUDAState | None = None, | |
| position_ids: torch.Tensor | None = None, | |
| *, | |
| materialize_logits: bool = True, | |
| ) -> tuple[YunmoNextOutput, YunmoNextCUDAState]: | |
| hidden_states, cache = self._forward_hidden_cached_from_embeddings( | |
| embeddings, | |
| cache, | |
| position_ids, | |
| ) | |
| logits = ( | |
| F.linear(hidden_states, self.embed_tokens.weight) | |
| if materialize_logits | |
| else None | |
| ) | |
| return YunmoNextOutput( | |
| logits=logits, | |
| hidden_states=hidden_states, | |
| ), cache | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor, | |
| labels: torch.Tensor | None = None, | |
| position_ids: torch.Tensor | None = None, | |
| cu_seqlens: torch.Tensor | None = None, | |
| materialize_logits: bool = False, | |
| ) -> YunmoNextOutput: | |
| return self._forward_from_embeddings( | |
| self.embed_tokens(input_ids), | |
| labels=labels, | |
| position_ids=position_ids, | |
| cu_seqlens=cu_seqlens, | |
| materialize_logits=materialize_logits, | |
| ) | |
| def _forward_from_embeddings( | |
| self, | |
| embeddings: torch.Tensor, | |
| labels: torch.Tensor | None = None, | |
| position_ids: torch.Tensor | None = None, | |
| cu_seqlens: torch.Tensor | None = None, | |
| materialize_logits: bool = False, | |
| ) -> YunmoNextOutput: | |
| hidden_states = self._forward_hidden_from_embeddings( | |
| embeddings, | |
| position_ids, | |
| cu_seqlens, | |
| ) | |
| loss = None | |
| logits = None | |
| if ( | |
| labels is not None | |
| and self.training_engine.cross_entropy_backend == "liger" | |
| ): | |
| try: | |
| from liger_kernel.transformers import ( | |
| LigerFusedLinearCrossEntropyLoss, | |
| ) | |
| except ImportError as exc: | |
| raise ImportError( | |
| "the configured cross-entropy backend requires liger-kernel" | |
| ) from exc | |
| loss_fn = LigerFusedLinearCrossEntropyLoss( | |
| accum_dtype=torch.float32 | |
| ) | |
| loss = loss_fn( | |
| self.embed_tokens.weight, | |
| hidden_states.reshape(-1, self.config.hidden_size), | |
| labels.reshape(-1), | |
| ) | |
| elif labels is not None or materialize_logits: | |
| logits = F.linear(hidden_states, self.embed_tokens.weight) | |
| if labels is not None: | |
| loss = F.cross_entropy( | |
| logits.float().reshape(-1, self.config.vocab_size), | |
| labels.reshape(-1), | |
| ) | |
| return YunmoNextOutput( | |
| loss=loss, | |
| logits=logits, | |
| hidden_states=hidden_states, | |
| ) | |