Text Generation
Transformers
PyTorch
English
shram
research
sparse-attention
mixture-of-experts
custom_code
Instructions to use smithblack-0/SHRAM-dev with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use smithblack-0/SHRAM-dev with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="smithblack-0/SHRAM-dev", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("smithblack-0/SHRAM-dev", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use smithblack-0/SHRAM-dev with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "smithblack-0/SHRAM-dev" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "smithblack-0/SHRAM-dev", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/smithblack-0/SHRAM-dev
- SGLang
How to use smithblack-0/SHRAM-dev 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 "smithblack-0/SHRAM-dev" \ --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": "smithblack-0/SHRAM-dev", "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 "smithblack-0/SHRAM-dev" \ --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": "smithblack-0/SHRAM-dev", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use smithblack-0/SHRAM-dev with Docker Model Runner:
docker model run hf.co/smithblack-0/SHRAM-dev
Update architecture and tokenizer
Browse files- README.md +1 -0
- config.json +1 -0
- configuration.py +13 -0
- huggingface.py +308 -55
README.md
CHANGED
|
@@ -84,6 +84,7 @@ contains no weights. All values are overridable via kwargs.
|
|
| 84 |
| `inference_sequence_length` | 1024 |
|
| 85 |
| `load_balance_p` | 2.0 |
|
| 86 |
| `local_rope_theta` | 10000.0 |
|
|
|
|
| 87 |
| `mlp_width` | 1366 |
|
| 88 |
| `mosrah_overallocation_factor` | 2.0 |
|
| 89 |
| `mosrah_rope_theta` | 10000.0 |
|
|
|
|
| 84 |
| `inference_sequence_length` | 1024 |
|
| 85 |
| `load_balance_p` | 2.0 |
|
| 86 |
| `local_rope_theta` | 10000.0 |
|
| 87 |
+
| `max_bid_rounds` | 10 |
|
| 88 |
| `mlp_width` | 1366 |
|
| 89 |
| `mosrah_overallocation_factor` | 2.0 |
|
| 90 |
| `mosrah_rope_theta` | 10000.0 |
|
config.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
| 11 |
"inference_sequence_length": 1024,
|
| 12 |
"load_balance_p": 2.0,
|
| 13 |
"local_rope_theta": 10000.0,
|
|
|
|
| 14 |
"mlp_width": 1366,
|
| 15 |
"model_type": "shram",
|
| 16 |
"mosrah_overallocation_factor": 2.0,
|
|
|
|
| 11 |
"inference_sequence_length": 1024,
|
| 12 |
"load_balance_p": 2.0,
|
| 13 |
"local_rope_theta": 10000.0,
|
| 14 |
+
"max_bid_rounds": 10,
|
| 15 |
"mlp_width": 1366,
|
| 16 |
"model_type": "shram",
|
| 17 |
"mosrah_overallocation_factor": 2.0,
|
configuration.py
CHANGED
|
@@ -88,6 +88,12 @@ class ShramConfig(PretrainedConfig):
|
|
| 88 |
frequencies into the load balance signal. Higher p weights aggregation
|
| 89 |
toward the worst-case batch item, making the correction signal more
|
| 90 |
sensitive to per-item allocation spikes. Must be positive. Default 2.0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
"""
|
| 92 |
|
| 93 |
model_type = "shram"
|
|
@@ -122,6 +128,7 @@ class ShramConfig(PretrainedConfig):
|
|
| 122 |
tie_word_embeddings: bool = False,
|
| 123 |
mosrah_overallocation_factor: float = 2.0,
|
| 124 |
load_balance_p: float = 2.0,
|
|
|
|
| 125 |
**kwargs
|
| 126 |
):
|
| 127 |
if head_dim % 2 != 0:
|
|
@@ -162,6 +169,11 @@ class ShramConfig(PretrainedConfig):
|
|
| 162 |
f"load_balance_p must be positive, got {load_balance_p}."
|
| 163 |
)
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
self.vocab_size = vocab_size
|
| 166 |
self.embedding_width = embedding_width
|
| 167 |
self.mlp_width = mlp_width
|
|
@@ -181,6 +193,7 @@ class ShramConfig(PretrainedConfig):
|
|
| 181 |
self.beta = beta
|
| 182 |
self.mosrah_overallocation_factor = mosrah_overallocation_factor
|
| 183 |
self.load_balance_p = load_balance_p
|
|
|
|
| 184 |
self.attention_dropout = attention_dropout
|
| 185 |
self.use_cache = use_cache
|
| 186 |
|
|
|
|
| 88 |
frequencies into the load balance signal. Higher p weights aggregation
|
| 89 |
toward the worst-case batch item, making the correction signal more
|
| 90 |
sensitive to per-item allocation spikes. Must be positive. Default 2.0.
|
| 91 |
+
max_bid_rounds: Maximum bidding rounds for the deferred-acceptance capacity
|
| 92 |
+
solver in ``balance_capacity``. 10 covers convergence at approximately
|
| 93 |
+
the 98th percentile of routing densities; the top 2% of extreme-density
|
| 94 |
+
cases are not expected under normal training. The bound exists as a
|
| 95 |
+
correctness guard β exhausting it raises ``RuntimeError``. Must be >= 1.
|
| 96 |
+
Default 10.
|
| 97 |
"""
|
| 98 |
|
| 99 |
model_type = "shram"
|
|
|
|
| 128 |
tie_word_embeddings: bool = False,
|
| 129 |
mosrah_overallocation_factor: float = 2.0,
|
| 130 |
load_balance_p: float = 2.0,
|
| 131 |
+
max_bid_rounds: int = 10,
|
| 132 |
**kwargs
|
| 133 |
):
|
| 134 |
if head_dim % 2 != 0:
|
|
|
|
| 169 |
f"load_balance_p must be positive, got {load_balance_p}."
|
| 170 |
)
|
| 171 |
|
| 172 |
+
if max_bid_rounds < 1:
|
| 173 |
+
raise ValueError(
|
| 174 |
+
f"max_bid_rounds must be at least 1, got {max_bid_rounds}."
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
self.vocab_size = vocab_size
|
| 178 |
self.embedding_width = embedding_width
|
| 179 |
self.mlp_width = mlp_width
|
|
|
|
| 193 |
self.beta = beta
|
| 194 |
self.mosrah_overallocation_factor = mosrah_overallocation_factor
|
| 195 |
self.load_balance_p = load_balance_p
|
| 196 |
+
self.max_bid_rounds = max_bid_rounds
|
| 197 |
self.attention_dropout = attention_dropout
|
| 198 |
self.use_cache = use_cache
|
| 199 |
|
huggingface.py
CHANGED
|
@@ -175,6 +175,12 @@ class ShramConfig(PretrainedConfig):
|
|
| 175 |
frequencies into the load balance signal. Higher p weights aggregation
|
| 176 |
toward the worst-case batch item, making the correction signal more
|
| 177 |
sensitive to per-item allocation spikes. Must be positive. Default 2.0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
"""
|
| 179 |
|
| 180 |
model_type = "shram"
|
|
@@ -209,6 +215,7 @@ class ShramConfig(PretrainedConfig):
|
|
| 209 |
tie_word_embeddings: bool = False,
|
| 210 |
mosrah_overallocation_factor: float = 2.0,
|
| 211 |
load_balance_p: float = 2.0,
|
|
|
|
| 212 |
**kwargs
|
| 213 |
):
|
| 214 |
if head_dim % 2 != 0:
|
|
@@ -249,6 +256,11 @@ class ShramConfig(PretrainedConfig):
|
|
| 249 |
f"load_balance_p must be positive, got {load_balance_p}."
|
| 250 |
)
|
| 251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
self.vocab_size = vocab_size
|
| 253 |
self.embedding_width = embedding_width
|
| 254 |
self.mlp_width = mlp_width
|
|
@@ -268,6 +280,7 @@ class ShramConfig(PretrainedConfig):
|
|
| 268 |
self.beta = beta
|
| 269 |
self.mosrah_overallocation_factor = mosrah_overallocation_factor
|
| 270 |
self.load_balance_p = load_balance_p
|
|
|
|
| 271 |
self.attention_dropout = attention_dropout
|
| 272 |
self.use_cache = use_cache
|
| 273 |
|
|
@@ -2458,7 +2471,7 @@ def pack_experts(
|
|
| 2458 |
tokens_per_expert = _count_tokens_per_expert(flattened_selected_heads, num_experts)
|
| 2459 |
max_count = tokens_per_expert.max().item()
|
| 2460 |
no_overflow = max_count <= packed_length
|
| 2461 |
-
_enforce_no_overflow(no_overflow)
|
| 2462 |
|
| 2463 |
# -----------------------------------------------------------------------
|
| 2464 |
# Construct the unpacking mask.
|
|
@@ -2576,7 +2589,7 @@ def unpack_experts(
|
|
| 2576 |
# Helpers
|
| 2577 |
# ---------------------------------------------------------------------------
|
| 2578 |
|
| 2579 |
-
def _enforce_no_overflow(condition: bool) -> None:
|
| 2580 |
"""Enforce that no expert bucket exceeds the preallocated packed length.
|
| 2581 |
|
| 2582 |
This check fires when the number of tokens assigned to any expert in any
|
|
@@ -2602,6 +2615,8 @@ def _enforce_no_overflow(condition: bool) -> None:
|
|
| 2602 |
"Expert packing overflow: at least one expert bucket contains more "
|
| 2603 |
"tokens than mosrah_packed_length allows. Increase "
|
| 2604 |
"mosrah_overallocation_factor in ShramConfig to resolve."
|
|
|
|
|
|
|
| 2605 |
)
|
| 2606 |
|
| 2607 |
|
|
@@ -2797,6 +2812,8 @@ class MoSRAHRouter(nn.Module):
|
|
| 2797 |
else:
|
| 2798 |
self.capacity = config.mosrah_packed_length
|
| 2799 |
|
|
|
|
|
|
|
| 2800 |
# W_r: routing projection, no bias (paper specifies xW_r, no additional term).
|
| 2801 |
self.routing_projection = nn.Linear(
|
| 2802 |
config.embedding_width, config.num_mosrah_heads, bias=False
|
|
@@ -2808,63 +2825,293 @@ class MoSRAHRouter(nn.Module):
|
|
| 2808 |
self.expert_bias = nn.Parameter(torch.zeros(config.num_mosrah_heads))
|
| 2809 |
|
| 2810 |
@staticmethod
|
| 2811 |
-
def
|
| 2812 |
-
|
| 2813 |
-
|
| 2814 |
-
|
|
|
|
| 2815 |
"""
|
| 2816 |
-
|
| 2817 |
-
|
| 2818 |
-
|
| 2819 |
-
|
| 2820 |
-
|
| 2821 |
-
|
| 2822 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2823 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2824 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2825 |
if used_capacity is None:
|
| 2826 |
-
|
| 2827 |
-
|
| 2828 |
-
# Looking up capacity limits only
|
| 2829 |
-
# matters if it is, in fact, possible
|
| 2830 |
-
# to exceed capacity limits.
|
| 2831 |
-
if logits.shape[-2] < capacity:
|
| 2832 |
-
return logits
|
| 2833 |
-
|
| 2834 |
-
# Look up the kthvalue and use that as
|
| 2835 |
-
# the threshold to mask when below.
|
| 2836 |
-
# Note we negate then negate again to sort
|
| 2837 |
-
# in ascending order.
|
| 2838 |
-
response = torch.kthvalue(-logits, capacity, dim=-2)
|
| 2839 |
-
threshold = -response.values
|
| 2840 |
-
threshold = threshold.unsqueeze(-2) #(B, 1, L)
|
| 2841 |
else:
|
| 2842 |
-
|
| 2843 |
-
|
| 2844 |
-
|
| 2845 |
-
|
| 2846 |
-
|
| 2847 |
-
|
| 2848 |
-
|
| 2849 |
-
|
| 2850 |
-
|
| 2851 |
-
|
| 2852 |
-
|
| 2853 |
-
|
| 2854 |
-
|
| 2855 |
-
|
| 2856 |
-
|
| 2857 |
-
|
| 2858 |
-
|
| 2859 |
-
|
| 2860 |
-
|
| 2861 |
-
ordered_logits = F.pad(ordered_logits, (0, 0, 0, 1), value=-1e8)
|
| 2862 |
-
|
| 2863 |
-
threshold = ordered_logits.gather(-2, index.unsqueeze(-2)) #(B, 1, L)
|
| 2864 |
-
|
| 2865 |
-
mask = threshold > logits
|
| 2866 |
-
logits = logits.masked_fill(mask, -1e8)
|
| 2867 |
-
return logits
|
| 2868 |
def forward(
|
| 2869 |
self,
|
| 2870 |
x: torch.Tensor,
|
|
@@ -2904,7 +3151,13 @@ class MoSRAHRouter(nn.Module):
|
|
| 2904 |
# selection. expert_bias is added to logits before softmax so that the bias
|
| 2905 |
# shifts selection probability without rescaling the unbiased distribution.
|
| 2906 |
biased_logits = logits + self.expert_bias
|
| 2907 |
-
biased_logits = self.balance_capacity(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2908 |
biased_routing_scores = F.softmax( # RΜ, (B, N, L)
|
| 2909 |
biased_logits, dim=-1
|
| 2910 |
)
|
|
|
|
| 175 |
frequencies into the load balance signal. Higher p weights aggregation
|
| 176 |
toward the worst-case batch item, making the correction signal more
|
| 177 |
sensitive to per-item allocation spikes. Must be positive. Default 2.0.
|
| 178 |
+
max_bid_rounds: Maximum bidding rounds for the deferred-acceptance capacity
|
| 179 |
+
solver in ``balance_capacity``. 10 covers convergence at approximately
|
| 180 |
+
the 98th percentile of routing densities; the top 2% of extreme-density
|
| 181 |
+
cases are not expected under normal training. The bound exists as a
|
| 182 |
+
correctness guard β exhausting it raises ``RuntimeError``. Must be >= 1.
|
| 183 |
+
Default 10.
|
| 184 |
"""
|
| 185 |
|
| 186 |
model_type = "shram"
|
|
|
|
| 215 |
tie_word_embeddings: bool = False,
|
| 216 |
mosrah_overallocation_factor: float = 2.0,
|
| 217 |
load_balance_p: float = 2.0,
|
| 218 |
+
max_bid_rounds: int = 10,
|
| 219 |
**kwargs
|
| 220 |
):
|
| 221 |
if head_dim % 2 != 0:
|
|
|
|
| 256 |
f"load_balance_p must be positive, got {load_balance_p}."
|
| 257 |
)
|
| 258 |
|
| 259 |
+
if max_bid_rounds < 1:
|
| 260 |
+
raise ValueError(
|
| 261 |
+
f"max_bid_rounds must be at least 1, got {max_bid_rounds}."
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
self.vocab_size = vocab_size
|
| 265 |
self.embedding_width = embedding_width
|
| 266 |
self.mlp_width = mlp_width
|
|
|
|
| 280 |
self.beta = beta
|
| 281 |
self.mosrah_overallocation_factor = mosrah_overallocation_factor
|
| 282 |
self.load_balance_p = load_balance_p
|
| 283 |
+
self.max_bid_rounds = max_bid_rounds
|
| 284 |
self.attention_dropout = attention_dropout
|
| 285 |
self.use_cache = use_cache
|
| 286 |
|
|
|
|
| 2471 |
tokens_per_expert = _count_tokens_per_expert(flattened_selected_heads, num_experts)
|
| 2472 |
max_count = tokens_per_expert.max().item()
|
| 2473 |
no_overflow = max_count <= packed_length
|
| 2474 |
+
_enforce_no_overflow(no_overflow, max_count, tokens_per_expert)
|
| 2475 |
|
| 2476 |
# -----------------------------------------------------------------------
|
| 2477 |
# Construct the unpacking mask.
|
|
|
|
| 2589 |
# Helpers
|
| 2590 |
# ---------------------------------------------------------------------------
|
| 2591 |
|
| 2592 |
+
def _enforce_no_overflow(condition: bool, tokens_per_expert, max_length) -> None:
|
| 2593 |
"""Enforce that no expert bucket exceeds the preallocated packed length.
|
| 2594 |
|
| 2595 |
This check fires when the number of tokens assigned to any expert in any
|
|
|
|
| 2615 |
"Expert packing overflow: at least one expert bucket contains more "
|
| 2616 |
"tokens than mosrah_packed_length allows. Increase "
|
| 2617 |
"mosrah_overallocation_factor in ShramConfig to resolve."
|
| 2618 |
+
f"head lengths were: \n {tokens_per_expert}"
|
| 2619 |
+
f"max length was: {max_length}"
|
| 2620 |
)
|
| 2621 |
|
| 2622 |
|
|
|
|
| 2812 |
else:
|
| 2813 |
self.capacity = config.mosrah_packed_length
|
| 2814 |
|
| 2815 |
+
self.max_bid_rounds = config.max_bid_rounds
|
| 2816 |
+
|
| 2817 |
# W_r: routing projection, no bias (paper specifies xW_r, no additional term).
|
| 2818 |
self.routing_projection = nn.Linear(
|
| 2819 |
config.embedding_width, config.num_mosrah_heads, bias=False
|
|
|
|
| 2825 |
self.expert_bias = nn.Parameter(torch.zeros(config.num_mosrah_heads))
|
| 2826 |
|
| 2827 |
@staticmethod
|
| 2828 |
+
def get_threshold(
|
| 2829 |
+
tensor: torch.Tensor,
|
| 2830 |
+
dim: int,
|
| 2831 |
+
n: int | torch.Tensor,
|
| 2832 |
+
) -> torch.Tensor:
|
| 2833 |
"""
|
| 2834 |
+
Returns the n-th largest value along dim, keepdim=True.
|
| 2835 |
+
|
| 2836 |
+
A value >= threshold ranks within the top n along dim. Boundary cases
|
| 2837 |
+
follow the monotone descending contract:
|
| 2838 |
+
|
| 2839 |
+
n == 0 -> +inf nothing qualifies
|
| 2840 |
+
n > dim_length -> -inf everything qualifies
|
| 2841 |
+
|
| 2842 |
+
:param tensor: Floating-point input, no NaN.
|
| 2843 |
+
:param dim: Dimension to reduce along.
|
| 2844 |
+
:param n: 1-indexed rank. Scalar int or tensor of ints broadcastable
|
| 2845 |
+
to tensor with dim removed.
|
| 2846 |
+
:return: Threshold with size 1 along dim, same dtype/device.
|
| 2847 |
"""
|
| 2848 |
+
# -------------------------------------------------------------------------
|
| 2849 |
+
# Algorithm overview
|
| 2850 |
+
# -------------------------------------------------------------------------
|
| 2851 |
+
#
|
| 2852 |
+
# Scalar n does not need a full sorted table. kthvalue selects the n-th
|
| 2853 |
+
# rank directly, and the two boundary sentinels are returned explicitly.
|
| 2854 |
+
#
|
| 2855 |
+
# Tensor n requires a full sorted table because each position along the
|
| 2856 |
+
# complementary dimensions may request a different rank. The table is
|
| 2857 |
+
# built once by sorting descending, then sentinel values are padded at
|
| 2858 |
+
# both ends so that boundary n values resolve correctly via gather:
|
| 2859 |
+
#
|
| 2860 |
+
# index 0 <- +inf sentinel (n == 0)
|
| 2861 |
+
# index 1..dim_length <- sorted values (valid ranks, 1-indexed)
|
| 2862 |
+
# index dim_length+1 <- -inf sentinel (n > dim_length)
|
| 2863 |
+
#
|
| 2864 |
+
# The critical invariant is that n is 1-indexed. This means valid ranks
|
| 2865 |
+
# map directly to their gather index without any offset, and index 0 is
|
| 2866 |
+
# naturally free for the +inf sentinel. n == 0 gathers +inf without
|
| 2867 |
+
# special-casing, and overflow n gathers -inf after clamping.
|
| 2868 |
+
#
|
| 2869 |
+
# F.pad specifies padding from the last dimension inward. Targeting an
|
| 2870 |
+
# arbitrary dim requires a positive index to compute how many trailing
|
| 2871 |
+
# dimensions to skip over in the pad spec.
|
| 2872 |
+
positive_dim = dim % tensor.ndim
|
| 2873 |
+
dim_length = tensor.shape[positive_dim]
|
| 2874 |
+
|
| 2875 |
+
if isinstance(n, int):
|
| 2876 |
+
# Scalar rank selection does not need a full sorted table. kthvalue
|
| 2877 |
+
# finds the k-th smallest; negating input and output flips the order
|
| 2878 |
+
# to give the k-th largest. Boundary sentinels follow the descending
|
| 2879 |
+
# contract: +inf sits above every real value (nothing qualifies),
|
| 2880 |
+
# -inf sits below every real value (everything qualifies).
|
| 2881 |
+
if n == 0:
|
| 2882 |
+
shape = list(tensor.shape)
|
| 2883 |
+
shape[positive_dim] = 1
|
| 2884 |
+
return tensor.new_full(shape, float('inf'))
|
| 2885 |
+
if n > dim_length:
|
| 2886 |
+
shape = list(tensor.shape)
|
| 2887 |
+
shape[positive_dim] = 1
|
| 2888 |
+
return tensor.new_full(shape, float('-inf'))
|
| 2889 |
+
return -torch.kthvalue(-tensor, n, dim=dim, keepdim=True).values
|
| 2890 |
+
|
| 2891 |
+
else:
|
| 2892 |
+
# Build the rank table once; each position gathers its own threshold.
|
| 2893 |
+
sorted_desc = torch.sort(tensor, dim=dim, descending=True).values
|
| 2894 |
+
|
| 2895 |
+
# Each trailing dimension after positive_dim contributes one (left,
|
| 2896 |
+
# right) zero-pair before the target padding entry in the F.pad spec.
|
| 2897 |
+
num_padding_skips = 2 * (tensor.ndim - positive_dim - 1)
|
| 2898 |
+
leading_pad = [0] * num_padding_skips + [1, 0]
|
| 2899 |
+
trailing_pad = [0] * num_padding_skips + [0, 1]
|
| 2900 |
+
|
| 2901 |
+
sorted_desc = F.pad(sorted_desc, leading_pad, value=float('inf'))
|
| 2902 |
+
sorted_desc = F.pad(sorted_desc, trailing_pad, value=float('-inf'))
|
| 2903 |
+
|
| 2904 |
+
# unsqueeze restores the reduced dimension so gather sees the same
|
| 2905 |
+
# rank as the padded table along dim.
|
| 2906 |
+
gather_index = n.clamp(0, dim_length + 1).long().unsqueeze(dim)
|
| 2907 |
+
return sorted_desc.gather(dim, gather_index)
|
| 2908 |
+
@staticmethod
|
| 2909 |
+
def _check_bidding_converged(converged: torch.Tensor, max_rounds: int) -> None:
|
| 2910 |
+
"""Raise if the bidding loop exhausted max_rounds without satisfying all tokens.
|
| 2911 |
+
|
| 2912 |
+
In compiled mode ``torch._check`` fires a C++ assertion
|
| 2913 |
+
(``capture_scalar_outputs=True`` is a precondition β see Unit 19.F.1).
|
| 2914 |
+
In eager mode raises ``RuntimeError`` directly.
|
| 2915 |
+
|
| 2916 |
+
Exhausting ``max_rounds`` indicates an extreme routing density case or an
|
| 2917 |
+
infeasible configuration where total capacity is insufficient for N * K
|
| 2918 |
+
demands. In normal training this should never occur; the default
|
| 2919 |
+
``max_bid_rounds=10`` covers approximately the 98th percentile of routing
|
| 2920 |
+
densities.
|
| 2921 |
+
|
| 2922 |
+
Args:
|
| 2923 |
+
converged: Scalar bool tensor β True if all tokens have >= K accepted experts.
|
| 2924 |
+
max_rounds: The iteration ceiling that was applied, for the error message.
|
| 2925 |
+
"""
|
| 2926 |
+
if torch.compiler.is_compiling():
|
| 2927 |
+
torch._check(converged)
|
| 2928 |
+
else:
|
| 2929 |
+
if not converged.item():
|
| 2930 |
+
raise RuntimeError(
|
| 2931 |
+
f"balance_capacity bidding did not converge within {max_rounds} rounds. "
|
| 2932 |
+
f"All tokens must have at least K accepted experts before the loop exits. "
|
| 2933 |
+
f"This indicates either an infeasible configuration (total remaining "
|
| 2934 |
+
f"capacity < N * K) or an extreme routing density. "
|
| 2935 |
+
f"Increase mosrah_overallocation_factor or max_bid_rounds."
|
| 2936 |
+
)
|
| 2937 |
+
|
| 2938 |
+
@staticmethod
|
| 2939 |
+
def _run_bidding(
|
| 2940 |
+
logits: torch.Tensor,
|
| 2941 |
+
remaining_capacity: int | torch.Tensor,
|
| 2942 |
+
min_choices: int,
|
| 2943 |
+
max_rounds: int,
|
| 2944 |
+
) -> torch.Tensor:
|
| 2945 |
+
"""Deferred-acceptance (Gale-Shapley) bidding solver for joint capacity enforcement.
|
| 2946 |
+
|
| 2947 |
+
Tokens propose experts in descending preference order; experts provisionally
|
| 2948 |
+
accept their top-``remaining_capacity`` proposed tokens each round. Proposals
|
| 2949 |
+
are monotone (never retracted). The loop continues until every token has at
|
| 2950 |
+
least ``min_choices`` accepted experts or ``max_rounds`` is exhausted.
|
| 2951 |
|
| 2952 |
+
Both the column bound (per-expert token count β€ remaining_capacity) and the
|
| 2953 |
+
row bound (per-token expert count β₯ min_choices) are satisfied simultaneously
|
| 2954 |
+
on the returned mask by construction.
|
| 2955 |
+
|
| 2956 |
+
Args:
|
| 2957 |
+
logits: Routing scores of shape (B, N, L).
|
| 2958 |
+
remaining_capacity: Per-expert token budget. Scalar int for training;
|
| 2959 |
+
(B, L) tensor for inference.
|
| 2960 |
+
min_choices: Minimum experts each token must have accepted (K).
|
| 2961 |
+
max_rounds: Iteration ceiling; raises via ``_check_bidding_converged``
|
| 2962 |
+
if exhausted.
|
| 2963 |
+
|
| 2964 |
+
Returns:
|
| 2965 |
+
accepted: (B, N, L) bool β True at positions accepted by the solver.
|
| 2966 |
+
"""
|
| 2967 |
+
# ββ initialise loop variables βββββββββββββββββββββββββββββββββββββββββ
|
| 2968 |
+
#
|
| 2969 |
+
# All three loop_vars must be tensors of fixed shape across iterations,
|
| 2970 |
+
# as required by torch.while_loop. logits and remaining_capacity are
|
| 2971 |
+
# captured read-only by the closures; they do not travel as loop_vars.
|
| 2972 |
+
proposals = torch.zeros_like(logits, dtype=torch.bool)
|
| 2973 |
+
acceptances = torch.zeros_like(logits, dtype=torch.bool)
|
| 2974 |
+
round_count = torch.zeros((), device=logits.device, dtype=torch.int64)
|
| 2975 |
+
max_rounds_t = torch.full((), max_rounds, device=logits.device, dtype=torch.int64)
|
| 2976 |
+
|
| 2977 |
+
def cond_fn(proposals, acceptances, round_count):
|
| 2978 |
+
all_satisfied = (acceptances.sum(dim=-1) >= min_choices).all()
|
| 2979 |
+
return (round_count < max_rounds_t) & ~all_satisfied
|
| 2980 |
+
|
| 2981 |
+
def body_fn(proposals, acceptances, round_count):
|
| 2982 |
+
# ββ token proposal step βββββββββββββββββββββββββββββββββββββββββββ
|
| 2983 |
+
#
|
| 2984 |
+
# Tokens with fewer than min_choices accepted experts propose their
|
| 2985 |
+
# next-best unproposed expert(s). The deficit determines how many new
|
| 2986 |
+
# proposals each token makes this round; already-satisfied tokens
|
| 2987 |
+
# propose nothing (deficit = 0 β bid_threshold = +inf β no new bids).
|
| 2988 |
+
accepted_per_token = acceptances.sum(dim=-1) # (B, N)
|
| 2989 |
+
choices_deficit = (min_choices - accepted_per_token).clamp_min(0)
|
| 2990 |
+
|
| 2991 |
+
unproposed_logits = logits.masked_fill(proposals, float('-inf'))
|
| 2992 |
+
bid_threshold = MoSRAHRouter.get_threshold(
|
| 2993 |
+
unproposed_logits, dim=-1, n=choices_deficit,
|
| 2994 |
+
)
|
| 2995 |
+
new_proposals = (
|
| 2996 |
+
(unproposed_logits >= bid_threshold)
|
| 2997 |
+
& ~proposals
|
| 2998 |
+
& (choices_deficit.unsqueeze(-1) > 0)
|
| 2999 |
+
)
|
| 3000 |
+
updated_proposals = proposals | new_proposals
|
| 3001 |
+
|
| 3002 |
+
# ββ expert acceptance step ββββββββββββββββββββββββββββββββββββββββ
|
| 3003 |
+
#
|
| 3004 |
+
# Each expert accepts its top-remaining_capacity proposed tokens.
|
| 3005 |
+
# Acceptances are recomputed from scratch each round so that a
|
| 3006 |
+
# stronger new proposal can displace a weaker prior one.
|
| 3007 |
+
proposed_logits = logits.masked_fill(~updated_proposals, float('-inf'))
|
| 3008 |
+
accept_threshold = MoSRAHRouter.get_threshold(
|
| 3009 |
+
proposed_logits, dim=-2, n=remaining_capacity,
|
| 3010 |
+
)
|
| 3011 |
+
updated_acceptances = updated_proposals & (proposed_logits >= accept_threshold)
|
| 3012 |
+
|
| 3013 |
+
return updated_proposals, updated_acceptances, round_count + 1
|
| 3014 |
+
|
| 3015 |
+
proposals, acceptances, _ = torch.while_loop(
|
| 3016 |
+
cond_fn, body_fn, (proposals, acceptances, round_count),
|
| 3017 |
+
)
|
| 3018 |
+
|
| 3019 |
+
converged = (acceptances.sum(dim=-1) >= min_choices).all()
|
| 3020 |
+
MoSRAHRouter._check_bidding_converged(converged, max_rounds)
|
| 3021 |
+
return acceptances
|
| 3022 |
+
|
| 3023 |
+
@classmethod
|
| 3024 |
+
def balance_capacity(
|
| 3025 |
+
cls,
|
| 3026 |
+
logits: torch.Tensor,
|
| 3027 |
+
used_capacity: torch.Tensor | None,
|
| 3028 |
+
capacity: int,
|
| 3029 |
+
min_choices: int,
|
| 3030 |
+
max_rounds: int,
|
| 3031 |
+
mask_value: float = -1e8,
|
| 3032 |
+
) -> torch.Tensor:
|
| 3033 |
+
"""Mask logits so both capacity constraints hold simultaneously on the output.
|
| 3034 |
+
|
| 3035 |
+
Two constraints must hold:
|
| 3036 |
+
- Column bound: per-expert unmasked token count β€ remaining_capacity.
|
| 3037 |
+
- Row bound: per-token unmasked expert count β₯ min_choices.
|
| 3038 |
+
|
| 3039 |
+
A training fast path and a column-capacity fast path are attempted before
|
| 3040 |
+
falling back to the bidding solver:
|
| 3041 |
+
|
| 3042 |
+
1. Training with N β€ capacity: return logits unchanged.
|
| 3043 |
+
2. Column-capacity fast path: if the most permissive column-bound-satisfying
|
| 3044 |
+
mask already gives every token at least min_choices choices, return it.
|
| 3045 |
+
3. Bidding fallback: deferred-acceptance solver guaranteeing both bounds.
|
| 3046 |
+
|
| 3047 |
+
Args:
|
| 3048 |
+
logits: Routing scores of shape (B, N, L).
|
| 3049 |
+
used_capacity: Tokens already accumulated per expert, shape (B, L).
|
| 3050 |
+
``None`` during training (full capacity available).
|
| 3051 |
+
capacity: Maximum tokens per expert (from config).
|
| 3052 |
+
min_choices: Minimum experts each token must retain (K).
|
| 3053 |
+
max_rounds: Bidding iteration ceiling (from config.max_bid_rounds).
|
| 3054 |
+
mask_value: Value written to masked positions. Default -1e8.
|
| 3055 |
+
|
| 3056 |
+
Returns:
|
| 3057 |
+
Logits with unavailable positions set to ``mask_value``, shape (B, N, L).
|
| 3058 |
+
"""
|
| 3059 |
+
# ββ Algorithm overview ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 3060 |
+
#
|
| 3061 |
+
# Problem: mask (B, N, L) logits so that both the column bound (each
|
| 3062 |
+
# expert receives at most remaining_capacity tokens) and the row bound
|
| 3063 |
+
# (each token retains at least min_choices expert choices) hold
|
| 3064 |
+
# simultaneously. Satisfying either constraint greedily can violate the
|
| 3065 |
+
# other, requiring a joint solver for the hard case.
|
| 3066 |
+
#
|
| 3067 |
+
# Approach: deferred-acceptance (Gale-Shapley) bidding. Each round,
|
| 3068 |
+
# tokens that still lack min_choices accepted experts propose their
|
| 3069 |
+
# next-best unproposed expert. Each expert then provisionally accepts its
|
| 3070 |
+
# top-remaining_capacity proposed tokens, potentially displacing weaker
|
| 3071 |
+
# prior acceptances. Proposals are monotone (never retracted). The loop
|
| 3072 |
+
# terminates when every token has min_choices accepted experts or
|
| 3073 |
+
# max_bid_rounds is exhausted (RuntimeError in the latter case).
|
| 3074 |
+
#
|
| 3075 |
+
# Two cheaper paths precede the solver:
|
| 3076 |
+
#
|
| 3077 |
+
# Training fast path β when N β€ capacity and all experts start empty,
|
| 3078 |
+
# no expert can overflow regardless of routing. No masking is needed.
|
| 3079 |
+
#
|
| 3080 |
+
# Column-capacity fast path β the most permissive mask satisfying the
|
| 3081 |
+
# column bound selects each expert's top-remaining_capacity tokens. If
|
| 3082 |
+
# that mask also satisfies the row bound, both constraints hold and the
|
| 3083 |
+
# solver is skipped entirely.
|
| 3084 |
+
|
| 3085 |
+
# Training fast path: N β€ capacity with empty experts β no overflow possible.
|
| 3086 |
+
if used_capacity is None and logits.shape[-2] <= capacity:
|
| 3087 |
+
return logits
|
| 3088 |
+
|
| 3089 |
+
# Compute per-expert remaining budget.
|
| 3090 |
+
# Training (N > capacity path): scalar β all experts start with full capacity.
|
| 3091 |
+
# Inference: subtract already-accumulated tokens; clamp prevents negatives
|
| 3092 |
+
# when rounding causes used_capacity to slightly exceed capacity.
|
| 3093 |
if used_capacity is None:
|
| 3094 |
+
remaining_capacity = capacity
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3095 |
else:
|
| 3096 |
+
remaining_capacity = (capacity - used_capacity).clamp(min=0) # (B, L)
|
| 3097 |
+
|
| 3098 |
+
# Column-capacity fast path: select each expert's top-remaining_capacity
|
| 3099 |
+
# tokens β the most permissive mask satisfying the column bound. If it
|
| 3100 |
+
# also satisfies the row bound, both constraints hold simultaneously.
|
| 3101 |
+
# Mask computation runs under no_grad: the boolean mask is a hard routing
|
| 3102 |
+
# decision and must not accumulate gradient memory through the solver.
|
| 3103 |
+
with torch.no_grad():
|
| 3104 |
+
col_threshold = cls.get_threshold(logits, dim=-2, n=remaining_capacity)
|
| 3105 |
+
col_capacity_mask = logits >= col_threshold # (B, N, L)
|
| 3106 |
+
if (col_capacity_mask.sum(dim=-1) >= min_choices).all():
|
| 3107 |
+
return logits.masked_fill(~col_capacity_mask, mask_value)
|
| 3108 |
+
|
| 3109 |
+
# Column-capacity mask violates the row bound: routing is concentrated
|
| 3110 |
+
# enough that per-expert capacity limits leave some tokens with fewer
|
| 3111 |
+
# than min_choices choices. The bidding solver handles this jointly.
|
| 3112 |
+
with torch.no_grad():
|
| 3113 |
+
accepted = cls._run_bidding(logits, remaining_capacity, min_choices, max_rounds)
|
| 3114 |
+
return logits.masked_fill(~accepted, mask_value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3115 |
def forward(
|
| 3116 |
self,
|
| 3117 |
x: torch.Tensor,
|
|
|
|
| 3151 |
# selection. expert_bias is added to logits before softmax so that the bias
|
| 3152 |
# shifts selection probability without rescaling the unbiased distribution.
|
| 3153 |
biased_logits = logits + self.expert_bias
|
| 3154 |
+
biased_logits = self.balance_capacity(
|
| 3155 |
+
biased_logits,
|
| 3156 |
+
used_capacity,
|
| 3157 |
+
self.capacity,
|
| 3158 |
+
self.num_selected_heads,
|
| 3159 |
+
self.max_bid_rounds,
|
| 3160 |
+
)
|
| 3161 |
biased_routing_scores = F.softmax( # RΜ, (B, N, L)
|
| 3162 |
biased_logits, dim=-1
|
| 3163 |
)
|