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)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("smithblack-0/SHRAM-dev", trust_remote_code=True, device_map="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- config.json +1 -1
- huggingface.py +163 -67
config.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
| 21 |
"rope_mode": "main_sequence",
|
| 22 |
"tie_word_embeddings": false,
|
| 23 |
"training_sequence_length": 1024,
|
| 24 |
-
"transformers_version": "5.
|
| 25 |
"use_cache": true,
|
| 26 |
"use_residual_gate": true,
|
| 27 |
"vocab_size": 50277,
|
|
|
|
| 21 |
"rope_mode": "main_sequence",
|
| 22 |
"tie_word_embeddings": false,
|
| 23 |
"training_sequence_length": 1024,
|
| 24 |
+
"transformers_version": "5.14.1",
|
| 25 |
"use_cache": true,
|
| 26 |
"use_residual_gate": true,
|
| 27 |
"vocab_size": 50277,
|
huggingface.py
CHANGED
|
@@ -44,6 +44,7 @@ from torch import nn
|
|
| 44 |
from torch.nn.attention.flex_attention import create_block_mask
|
| 45 |
from torch.nn.attention.flex_attention import flex_attention
|
| 46 |
import torch.nn.functional as F
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
|
|
@@ -671,15 +672,14 @@ class MoSRAHCache(CacheLayerMixin):
|
|
| 671 |
"Use get_heads_lengths() for per-head occupancy."
|
| 672 |
)
|
| 673 |
|
| 674 |
-
def
|
| 675 |
-
"""Return the static per-(batch, head) slot capacity of this cache.
|
| 676 |
-
|
| 677 |
-
Equal to mosrah_cache_length as supplied at construction, which is derived
|
| 678 |
-
from config.mosrah_cache_length. Required by the HuggingFace static cache
|
| 679 |
-
contract; generation machinery uses this to size attention masks.
|
| 680 |
-
"""
|
| 681 |
return self.mosrah_cache_length
|
| 682 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 683 |
def get_mask_sizes( # type: ignore[override]
|
| 684 |
self,
|
| 685 |
cache_position: torch.Tensor,
|
|
@@ -987,9 +987,13 @@ class RouterCache(CacheLayerMixin):
|
|
| 987 |
"""Not supported β RouterCache tracks block position, not sequence length."""
|
| 988 |
raise NotImplementedError("RouterCache does not track sequence length.")
|
| 989 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 990 |
def get_max_cache_shape(self) -> int:
|
| 991 |
-
"""
|
| 992 |
-
|
| 993 |
|
| 994 |
def get_mask_sizes( # type: ignore[override]
|
| 995 |
self,
|
|
@@ -1251,9 +1255,14 @@ class LocalSlidingWindowLayerCache(CacheLayerMixin):
|
|
| 1251 |
"""
|
| 1252 |
return int(self._total_processed)
|
| 1253 |
|
| 1254 |
-
def
|
|
|
|
| 1255 |
return self.sliding_window
|
| 1256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1257 |
def get_mask_sizes( # type: ignore[override]
|
| 1258 |
self,
|
| 1259 |
cache_position: torch.Tensor,
|
|
@@ -1515,16 +1524,18 @@ class ShramLayerCache(CacheLayerMixin):
|
|
| 1515 |
"Update sliding_window_cache or mosrah_cache directly."
|
| 1516 |
)
|
| 1517 |
|
| 1518 |
-
def
|
| 1519 |
"""Return the maximum sequence length this layer cache can serve.
|
| 1520 |
|
| 1521 |
The authoritative upper bound is ``config.inference_sequence_length``, which
|
| 1522 |
governs the full accumulated token history the model is configured to handle.
|
| 1523 |
-
HuggingFace's static-cache machinery reads this value to determine whether the
|
| 1524 |
-
cache is compileable and to size generation loops.
|
| 1525 |
"""
|
| 1526 |
return self._inference_sequence_length
|
| 1527 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1528 |
def get_mask_sizes( # type: ignore[override]
|
| 1529 |
self,
|
| 1530 |
cache_position: torch.Tensor,
|
|
@@ -2378,6 +2389,54 @@ accumulated cached state returned by that cache.
|
|
| 2378 |
|
| 2379 |
|
| 2380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2381 |
|
| 2382 |
|
| 2383 |
|
|
@@ -2527,9 +2586,13 @@ class BottleneckedEnsembleAttention(nn.Module):
|
|
| 2527 |
return torch.einsum("bltu,lud->bltd", attended_states, self.o_proj)
|
| 2528 |
|
| 2529 |
def _reset_parameters(self) -> None:
|
| 2530 |
-
"""Initialize
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2531 |
for weight in (self.q_proj, self.k_proj, self.v_proj, self.o_proj):
|
| 2532 |
-
|
| 2533 |
|
| 2534 |
def _validate_tensor_shape(self, packed_embeddings: torch.Tensor) -> None:
|
| 2535 |
"""Validate the local packed-embedding shape contract required by BEA."""
|
|
@@ -2979,8 +3042,10 @@ input hidden state x, the router produces two outputs used downstream:
|
|
| 2979 |
- selected_heads (I): which K of the L available expert heads each token
|
| 2980 |
routes to, determined by a block-balanced causal solver.
|
| 2981 |
- routing_probs (P): the weights used for the weighted output reduction,
|
| 2982 |
-
gathered from the
|
| 2983 |
-
renormalized to sum to
|
|
|
|
|
|
|
| 2984 |
|
| 2985 |
Routing uses a single learnable projection:
|
| 2986 |
|
|
@@ -3009,6 +3074,11 @@ Paper ref: Appendix A.Routing.
|
|
| 3009 |
|
| 3010 |
|
| 3011 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3012 |
class MoSRAHRouter(nn.Module):
|
| 3013 |
"""Token-choice router for MoSRAH sparse attention.
|
| 3014 |
|
|
@@ -3017,8 +3087,8 @@ class MoSRAHRouter(nn.Module):
|
|
| 3017 |
consecutive tokens every expert is used exactly once, giving perfect load
|
| 3018 |
balance by construction.
|
| 3019 |
|
| 3020 |
-
routing_weight is nn.Parameter rather than nn.Linear
|
| 3021 |
-
|
| 3022 |
|
| 3023 |
Attributes:
|
| 3024 |
routing_weight: Shape (L, embedding_width). Maps input hidden states to
|
|
@@ -3034,16 +3104,16 @@ class MoSRAHRouter(nn.Module):
|
|
| 3034 |
|
| 3035 |
def __init__(self, config: ShramConfig) -> None:
|
| 3036 |
super().__init__()
|
| 3037 |
-
self.num_mosrah_heads
|
| 3038 |
self.num_selected_heads = config.num_selected_heads
|
| 3039 |
-
self.block_length
|
|
|
|
| 3040 |
|
| 3041 |
# Routing projection: maps input (B, N, d) to per-head routing scores (B, N, L).
|
| 3042 |
-
# nn.Parameter ensures HuggingFace _init_weights does not override kaiming init.
|
| 3043 |
self.routing_weight = nn.Parameter(
|
| 3044 |
torch.empty(config.num_mosrah_heads, config.embedding_width)
|
| 3045 |
)
|
| 3046 |
-
|
| 3047 |
|
| 3048 |
def forward(
|
| 3049 |
self,
|
|
@@ -3062,16 +3132,17 @@ class MoSRAHRouter(nn.Module):
|
|
| 3062 |
Returns:
|
| 3063 |
selected_heads: Head indices I of shape (batch, seq_len, num_selected_heads).
|
| 3064 |
Each token's K selected head indices from the block-balanced solver.
|
| 3065 |
-
routing_probs: Routing
|
| 3066 |
-
num_selected_heads).
|
| 3067 |
-
|
|
|
|
| 3068 |
router_diagnostics: Dict of routing scalars:
|
| 3069 |
- ``regret_loss``: gradient-carrying mean regret, mean of
|
| 3070 |
max(p_max_active β p_chosen, 0) over live (B, num_blocks, L)
|
| 3071 |
entries. In [0, 1]. Zero when every expert is assigned at its
|
| 3072 |
peak-preference token within the block.
|
| 3073 |
- ``logit_regret``: detached logit-space regret; same formula
|
| 3074 |
-
applied to routing logits rather than
|
| 3075 |
In [0, β). Monitoring only.
|
| 3076 |
- ``logit_std``: detached mean per-token std of routing logits.
|
| 3077 |
"""
|
|
@@ -3101,13 +3172,18 @@ class MoSRAHRouter(nn.Module):
|
|
| 3101 |
|
| 3102 |
# ββ Phase: pre-balance scoring βββββββββββββββββββββββββββββββββββββββββ
|
| 3103 |
#
|
| 3104 |
-
#
|
| 3105 |
-
#
|
| 3106 |
-
#
|
| 3107 |
-
#
|
| 3108 |
routing_logits = self._compute_routing_logits(x) # (B, N, L)
|
| 3109 |
-
|
| 3110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3111 |
|
| 3112 |
# ββ Phase: block-balanced causal selection βββββββββββββββββββββββββββββ
|
| 3113 |
#
|
|
@@ -3136,9 +3212,9 @@ class MoSRAHRouter(nn.Module):
|
|
| 3136 |
# token per "block" there is no alternative assignment with higher
|
| 3137 |
# preference.
|
| 3138 |
used_in_block = router_cache.get_used_in_block() # (B, L)
|
| 3139 |
-
step_logits
|
| 3140 |
-
available
|
| 3141 |
-
step_heads
|
| 3142 |
|
| 3143 |
router_cache.update_decode(step_heads)
|
| 3144 |
|
|
@@ -3152,8 +3228,8 @@ class MoSRAHRouter(nn.Module):
|
|
| 3152 |
# preceding them have already had their pick each step. The pad is
|
| 3153 |
# discarded after the solver.
|
| 3154 |
num_blocks = (N + W - 1) // W
|
| 3155 |
-
N_pad
|
| 3156 |
-
pad_len
|
| 3157 |
|
| 3158 |
if pad_len > 0:
|
| 3159 |
padded_logits = torch.cat(
|
|
@@ -3167,14 +3243,20 @@ class MoSRAHRouter(nn.Module):
|
|
| 3167 |
# used_in_block tracks which experts have been claimed within each block.
|
| 3168 |
# No gradient here β expert availability is a hard structural constraint,
|
| 3169 |
# not a differentiable quantity. Gradient flows through routing_probs.
|
| 3170 |
-
used_in_block
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3171 |
step_heads_list = []
|
| 3172 |
|
| 3173 |
for step in range(W):
|
| 3174 |
step_logits = blocked_logits[:, :, step, :] # (B, blk, L)
|
| 3175 |
|
| 3176 |
# Claimed experts receive -inf so top-K never selects them.
|
| 3177 |
-
available
|
| 3178 |
step_heads = available.topk(K, dim=-1).indices # (B, blk, K)
|
| 3179 |
step_heads_list.append(step_heads)
|
| 3180 |
|
|
@@ -3183,7 +3265,7 @@ class MoSRAHRouter(nn.Module):
|
|
| 3183 |
|
| 3184 |
# Stack W steps and reshape to (B, N_pad, K), then unpad.
|
| 3185 |
selected_heads_blocked = torch.stack(step_heads_list, dim=2) # (B, blk, W, K)
|
| 3186 |
-
selected_heads
|
| 3187 |
|
| 3188 |
if router_cache is not None:
|
| 3189 |
# Prefill: persist the partial last-block state so decode steps
|
|
@@ -3203,27 +3285,38 @@ class MoSRAHRouter(nn.Module):
|
|
| 3203 |
# blocks, and a single decode step is not a complete block. Backward is
|
| 3204 |
# never called during inference so the zero is a correct no-op.
|
| 3205 |
if router_cache is not None and N == 1:
|
| 3206 |
-
regret_loss
|
| 3207 |
-
logit_regret =
|
| 3208 |
else:
|
| 3209 |
regret_loss, logit_regret = self._compute_regret(
|
| 3210 |
-
|
| 3211 |
-
|
| 3212 |
selected_heads_blocked,
|
| 3213 |
active_mask,
|
| 3214 |
)
|
| 3215 |
|
| 3216 |
-
# ββ Phase: routing
|
| 3217 |
#
|
| 3218 |
-
#
|
| 3219 |
-
#
|
| 3220 |
-
|
| 3221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3222 |
|
| 3223 |
router_diagnostics = {
|
| 3224 |
-
"regret_loss":
|
| 3225 |
"logit_regret": logit_regret,
|
| 3226 |
-
"logit_std":
|
| 3227 |
}
|
| 3228 |
return selected_heads, routing_probs, router_diagnostics
|
| 3229 |
|
|
@@ -3260,9 +3353,9 @@ class MoSRAHRouter(nn.Module):
|
|
| 3260 |
detached β it is a monitoring scalar only, in [0, β).
|
| 3261 |
|
| 3262 |
Args:
|
| 3263 |
-
routing_scores:
|
| 3264 |
Gradient flows through this tensor into regret_loss.
|
| 3265 |
-
routing_logits:
|
| 3266 |
Used only for the detached logit_regret.
|
| 3267 |
selected_heads_blocked: Expert assignments from the block solver,
|
| 3268 |
shape (B, num_blocks, W, K). Block geometry
|
|
@@ -3274,8 +3367,8 @@ class MoSRAHRouter(nn.Module):
|
|
| 3274 |
logit_regret: Detached scalar in [0, β).
|
| 3275 |
"""
|
| 3276 |
B, num_blocks, W, _K = selected_heads_blocked.shape
|
| 3277 |
-
L
|
| 3278 |
-
N
|
| 3279 |
N_pad = num_blocks * W
|
| 3280 |
|
| 3281 |
# ββ Reshape into block form βββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -3311,23 +3404,25 @@ class MoSRAHRouter(nn.Module):
|
|
| 3311 |
assignment_mask.scatter_(dim=-1, index=selected_heads_blocked, value=1.0)
|
| 3312 |
# (B, nb, W, L)
|
| 3313 |
|
| 3314 |
-
# ββ
|
| 3315 |
#
|
| 3316 |
# p_chosen: routing score at the assigned token, gated by active_float
|
| 3317 |
# so dead assignments contribute 0 β the expert accrues full regret
|
| 3318 |
# against the active maximum rather than no penalty.
|
| 3319 |
# p_max: peak routing score over active tokens; dead tokens zeroed before
|
| 3320 |
-
# max (safe because
|
| 3321 |
-
p_chosen = (
|
| 3322 |
-
|
| 3323 |
-
|
| 3324 |
-
|
|
|
|
|
|
|
| 3325 |
|
| 3326 |
-
regret = (p_max - p_chosen).clamp(min=0.0)
|
| 3327 |
|
| 3328 |
# Mean over live (B, num_blocks, L) entries. Clamped to 1 for the
|
| 3329 |
# all-dead edge case where the numerator is already 0.
|
| 3330 |
-
num_live
|
| 3331 |
regret_loss = (
|
| 3332 |
block_active.float().unsqueeze(-1) * regret
|
| 3333 |
).sum() / num_live.mul(L).clamp(min=1.0)
|
|
@@ -3343,12 +3438,13 @@ class MoSRAHRouter(nn.Module):
|
|
| 3343 |
).sum(dim=2) # (B, nb, L)
|
| 3344 |
|
| 3345 |
logit_max = logits_blocked.masked_fill(
|
| 3346 |
-
~active_blocked.unsqueeze(-1), float(
|
| 3347 |
-
).max(dim=2).values
|
| 3348 |
logit_max = logit_max.masked_fill(~block_active.unsqueeze(-1), 0.0)
|
| 3349 |
|
| 3350 |
logit_regret = (
|
| 3351 |
-
block_active.float().unsqueeze(-1)
|
|
|
|
| 3352 |
).sum() / num_live.mul(L).clamp(min=1.0)
|
| 3353 |
logit_regret = logit_regret.detach()
|
| 3354 |
|
|
|
|
| 44 |
from torch.nn.attention.flex_attention import create_block_mask
|
| 45 |
from torch.nn.attention.flex_attention import flex_attention
|
| 46 |
import torch.nn.functional as F
|
| 47 |
+
from entmax import entmax_bisect
|
| 48 |
|
| 49 |
|
| 50 |
|
|
|
|
| 672 |
"Use get_heads_lengths() for per-head occupancy."
|
| 673 |
)
|
| 674 |
|
| 675 |
+
def get_max_length(self) -> int:
|
| 676 |
+
"""Return the static per-(batch, head) slot capacity of this cache."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
return self.mosrah_cache_length
|
| 678 |
|
| 679 |
+
def get_max_cache_shape(self) -> int: # type: ignore[override]
|
| 680 |
+
"""Compatibility alias for the deprecated cache-shape interface."""
|
| 681 |
+
return self.get_max_length()
|
| 682 |
+
|
| 683 |
def get_mask_sizes( # type: ignore[override]
|
| 684 |
self,
|
| 685 |
cache_position: torch.Tensor,
|
|
|
|
| 987 |
"""Not supported β RouterCache tracks block position, not sequence length."""
|
| 988 |
raise NotImplementedError("RouterCache does not track sequence length.")
|
| 989 |
|
| 990 |
+
def get_max_length(self) -> int:
|
| 991 |
+
"""Return an undefined maximum because no token sequence is stored."""
|
| 992 |
+
return -1
|
| 993 |
+
|
| 994 |
def get_max_cache_shape(self) -> int:
|
| 995 |
+
"""Compatibility alias for the deprecated cache-shape interface."""
|
| 996 |
+
return self.get_max_length()
|
| 997 |
|
| 998 |
def get_mask_sizes( # type: ignore[override]
|
| 999 |
self,
|
|
|
|
| 1255 |
"""
|
| 1256 |
return int(self._total_processed)
|
| 1257 |
|
| 1258 |
+
def get_max_length(self) -> int:
|
| 1259 |
+
"""Return the retained local sliding-window capacity."""
|
| 1260 |
return self.sliding_window
|
| 1261 |
|
| 1262 |
+
def get_max_cache_shape(self) -> int:
|
| 1263 |
+
"""Compatibility alias for the deprecated cache-shape interface."""
|
| 1264 |
+
return self.get_max_length()
|
| 1265 |
+
|
| 1266 |
def get_mask_sizes( # type: ignore[override]
|
| 1267 |
self,
|
| 1268 |
cache_position: torch.Tensor,
|
|
|
|
| 1524 |
"Update sliding_window_cache or mosrah_cache directly."
|
| 1525 |
)
|
| 1526 |
|
| 1527 |
+
def get_max_length(self) -> int:
|
| 1528 |
"""Return the maximum sequence length this layer cache can serve.
|
| 1529 |
|
| 1530 |
The authoritative upper bound is ``config.inference_sequence_length``, which
|
| 1531 |
governs the full accumulated token history the model is configured to handle.
|
|
|
|
|
|
|
| 1532 |
"""
|
| 1533 |
return self._inference_sequence_length
|
| 1534 |
|
| 1535 |
+
def get_max_cache_shape(self) -> int: # type: ignore[override]
|
| 1536 |
+
"""Compatibility alias for the deprecated cache-shape interface."""
|
| 1537 |
+
return self.get_max_length()
|
| 1538 |
+
|
| 1539 |
def get_mask_sizes( # type: ignore[override]
|
| 1540 |
self,
|
| 1541 |
cache_position: torch.Tensor,
|
|
|
|
| 2389 |
|
| 2390 |
|
| 2391 |
|
| 2392 |
+
# -----------
|
| 2393 |
+
# Inlined from: initialization.py
|
| 2394 |
+
# -----------
|
| 2395 |
+
"""Initialization policy for SHRAM raw projection parameters.
|
| 2396 |
+
|
| 2397 |
+
Hugging Face initializes ordinary ``nn.Linear`` and ``nn.Embedding`` modules.
|
| 2398 |
+
SHRAM also stores two custom raw-parameter forms that Hugging Face cannot infer:
|
| 2399 |
+
|
| 2400 |
+
- a three-dimensional bank of independent expert linear maps
|
| 2401 |
+
- a two-dimensional router projection
|
| 2402 |
+
|
| 2403 |
+
The expert-bank leading dimension is ownership/storage, not fan geometry. Each
|
| 2404 |
+
expert matrix must therefore receive Xavier initialization independently. The
|
| 2405 |
+
router follows the proven BALANCE initialization scale instead. Tensor rank is
|
| 2406 |
+
used here as the explicit representation contract for these two owners, not as
|
| 2407 |
+
a general initialization heuristic. Adding another raw projection shape must
|
| 2408 |
+
therefore extend this contract deliberately rather than inheriting a fallback.
|
| 2409 |
+
"""
|
| 2410 |
+
|
| 2411 |
+
|
| 2412 |
+
|
| 2413 |
+
|
| 2414 |
+
|
| 2415 |
+
ROUTER_INIT_STD = 0.02
|
| 2416 |
+
|
| 2417 |
+
|
| 2418 |
+
@torch.no_grad()
|
| 2419 |
+
def initialize_projection_parameter(parameter: nn.Parameter) -> None:
|
| 2420 |
+
"""Initialize one of SHRAM's supported raw projection representations.
|
| 2421 |
+
|
| 2422 |
+
A rank-three tensor is an independent bank of linear matrices and is
|
| 2423 |
+
initialized matrix-by-matrix with Xavier uniform. A rank-two tensor is the
|
| 2424 |
+
router projection and receives the BALANCE-normal initialization.
|
| 2425 |
+
"""
|
| 2426 |
+
if parameter.ndim == 3:
|
| 2427 |
+
for matrix in parameter.unbind(dim=0):
|
| 2428 |
+
nn.init.xavier_uniform_(matrix)
|
| 2429 |
+
return
|
| 2430 |
+
|
| 2431 |
+
if parameter.ndim == 2:
|
| 2432 |
+
nn.init.normal_(parameter, mean=0.0, std=ROUTER_INIT_STD)
|
| 2433 |
+
return
|
| 2434 |
+
|
| 2435 |
+
raise ValueError(
|
| 2436 |
+
"SHRAM raw projection parameters must be rank two (router) or rank "
|
| 2437 |
+
f"three (independent expert bank), got shape {tuple(parameter.shape)}."
|
| 2438 |
+
)
|
| 2439 |
+
|
| 2440 |
|
| 2441 |
|
| 2442 |
|
|
|
|
| 2586 |
return torch.einsum("bltu,lud->bltd", attended_states, self.o_proj)
|
| 2587 |
|
| 2588 |
def _reset_parameters(self) -> None:
|
| 2589 |
+
"""Initialize projection banks independently of storage rank.
|
| 2590 |
+
|
| 2591 |
+
These tensors store independent per-head linear maps. Their leading
|
| 2592 |
+
head dimension is storage organization rather than fan geometry.
|
| 2593 |
+
"""
|
| 2594 |
for weight in (self.q_proj, self.k_proj, self.v_proj, self.o_proj):
|
| 2595 |
+
initialize_projection_parameter(weight)
|
| 2596 |
|
| 2597 |
def _validate_tensor_shape(self, packed_embeddings: torch.Tensor) -> None:
|
| 2598 |
"""Validate the local packed-embedding shape contract required by BEA."""
|
|
|
|
| 3042 |
- selected_heads (I): which K of the L available expert heads each token
|
| 3043 |
routes to, determined by a block-balanced causal solver.
|
| 3044 |
- routing_probs (P): the weights used for the weighted output reduction,
|
| 3045 |
+
gathered from the Entmax routing distribution at the selected indices.
|
| 3046 |
+
Tokens with positive selected mass are renormalized to sum to one; tokens
|
| 3047 |
+
whose mechanically assigned experts all have zero Entmax mass receive an
|
| 3048 |
+
all-zero mixture and therefore no sparse-path contribution.
|
| 3049 |
|
| 3050 |
Routing uses a single learnable projection:
|
| 3051 |
|
|
|
|
| 3074 |
|
| 3075 |
|
| 3076 |
|
| 3077 |
+
|
| 3078 |
+
|
| 3079 |
+
ENTMAX_ALPHA = 1.2
|
| 3080 |
+
|
| 3081 |
+
|
| 3082 |
class MoSRAHRouter(nn.Module):
|
| 3083 |
"""Token-choice router for MoSRAH sparse attention.
|
| 3084 |
|
|
|
|
| 3087 |
consecutive tokens every expert is used exactly once, giving perfect load
|
| 3088 |
balance by construction.
|
| 3089 |
|
| 3090 |
+
routing_weight is an ``nn.Parameter`` rather than ``nn.Linear`` because the
|
| 3091 |
+
router owns a custom raw projection boundary and its initialization.
|
| 3092 |
|
| 3093 |
Attributes:
|
| 3094 |
routing_weight: Shape (L, embedding_width). Maps input hidden states to
|
|
|
|
| 3104 |
|
| 3105 |
def __init__(self, config: ShramConfig) -> None:
|
| 3106 |
super().__init__()
|
| 3107 |
+
self.num_mosrah_heads = config.num_mosrah_heads
|
| 3108 |
self.num_selected_heads = config.num_selected_heads
|
| 3109 |
+
self.block_length = config.block_length
|
| 3110 |
+
self.entmax_alpha = ENTMAX_ALPHA
|
| 3111 |
|
| 3112 |
# Routing projection: maps input (B, N, d) to per-head routing scores (B, N, L).
|
|
|
|
| 3113 |
self.routing_weight = nn.Parameter(
|
| 3114 |
torch.empty(config.num_mosrah_heads, config.embedding_width)
|
| 3115 |
)
|
| 3116 |
+
initialize_projection_parameter(self.routing_weight)
|
| 3117 |
|
| 3118 |
def forward(
|
| 3119 |
self,
|
|
|
|
| 3132 |
Returns:
|
| 3133 |
selected_heads: Head indices I of shape (batch, seq_len, num_selected_heads).
|
| 3134 |
Each token's K selected head indices from the block-balanced solver.
|
| 3135 |
+
routing_probs: Routing weights P of shape (batch, seq_len,
|
| 3136 |
+
num_selected_heads). Selected positive Entmax mass is normalized
|
| 3137 |
+
to one. An all-zero selected support remains all-zero, allowing
|
| 3138 |
+
the token to make no sparse-path contribution.
|
| 3139 |
router_diagnostics: Dict of routing scalars:
|
| 3140 |
- ``regret_loss``: gradient-carrying mean regret, mean of
|
| 3141 |
max(p_max_active β p_chosen, 0) over live (B, num_blocks, L)
|
| 3142 |
entries. In [0, 1]. Zero when every expert is assigned at its
|
| 3143 |
peak-preference token within the block.
|
| 3144 |
- ``logit_regret``: detached logit-space regret; same formula
|
| 3145 |
+
applied to routing logits rather than Entmax probabilities.
|
| 3146 |
In [0, β). Monitoring only.
|
| 3147 |
- ``logit_std``: detached mean per-token std of routing logits.
|
| 3148 |
"""
|
|
|
|
| 3172 |
|
| 3173 |
# ββ Phase: pre-balance scoring βββββββββββββββββββββββββββββββββββββββββ
|
| 3174 |
#
|
| 3175 |
+
# Establish the clean routing distribution before any -inf masking.
|
| 3176 |
+
# Entmax and selected-weight normalization are computed in fp32 by
|
| 3177 |
+
# numeric policy. Only the final route weights return to model dtype.
|
| 3178 |
+
# The regret objective also retains the fp32 distribution.
|
| 3179 |
routing_logits = self._compute_routing_logits(x) # (B, N, L)
|
| 3180 |
+
routing_logits_fp32 = routing_logits.float()
|
| 3181 |
+
logit_std = routing_logits_fp32.std(dim=-1).mean().detach()
|
| 3182 |
+
routing_scores_fp32 = entmax_bisect(
|
| 3183 |
+
routing_logits_fp32,
|
| 3184 |
+
alpha=self.entmax_alpha,
|
| 3185 |
+
dim=-1,
|
| 3186 |
+
) # (B, N, L), fp32
|
| 3187 |
|
| 3188 |
# ββ Phase: block-balanced causal selection βββββββββββββββββββββββββββββ
|
| 3189 |
#
|
|
|
|
| 3212 |
# token per "block" there is no alternative assignment with higher
|
| 3213 |
# preference.
|
| 3214 |
used_in_block = router_cache.get_used_in_block() # (B, L)
|
| 3215 |
+
step_logits = routing_logits[:, 0, :] # (B, L)
|
| 3216 |
+
available = step_logits.masked_fill(used_in_block, float("-inf"))
|
| 3217 |
+
step_heads = available.topk(K, dim=-1).indices # (B, K)
|
| 3218 |
|
| 3219 |
router_cache.update_decode(step_heads)
|
| 3220 |
|
|
|
|
| 3228 |
# preceding them have already had their pick each step. The pad is
|
| 3229 |
# discarded after the solver.
|
| 3230 |
num_blocks = (N + W - 1) // W
|
| 3231 |
+
N_pad = num_blocks * W
|
| 3232 |
+
pad_len = N_pad - N
|
| 3233 |
|
| 3234 |
if pad_len > 0:
|
| 3235 |
padded_logits = torch.cat(
|
|
|
|
| 3243 |
# used_in_block tracks which experts have been claimed within each block.
|
| 3244 |
# No gradient here β expert availability is a hard structural constraint,
|
| 3245 |
# not a differentiable quantity. Gradient flows through routing_probs.
|
| 3246 |
+
used_in_block = torch.zeros(
|
| 3247 |
+
B,
|
| 3248 |
+
num_blocks,
|
| 3249 |
+
L,
|
| 3250 |
+
dtype=torch.bool,
|
| 3251 |
+
device=x.device,
|
| 3252 |
+
)
|
| 3253 |
step_heads_list = []
|
| 3254 |
|
| 3255 |
for step in range(W):
|
| 3256 |
step_logits = blocked_logits[:, :, step, :] # (B, blk, L)
|
| 3257 |
|
| 3258 |
# Claimed experts receive -inf so top-K never selects them.
|
| 3259 |
+
available = step_logits.masked_fill(used_in_block, float("-inf"))
|
| 3260 |
step_heads = available.topk(K, dim=-1).indices # (B, blk, K)
|
| 3261 |
step_heads_list.append(step_heads)
|
| 3262 |
|
|
|
|
| 3265 |
|
| 3266 |
# Stack W steps and reshape to (B, N_pad, K), then unpad.
|
| 3267 |
selected_heads_blocked = torch.stack(step_heads_list, dim=2) # (B, blk, W, K)
|
| 3268 |
+
selected_heads = selected_heads_blocked.view(B, N_pad, K)[:, :N, :] # (B, N, K)
|
| 3269 |
|
| 3270 |
if router_cache is not None:
|
| 3271 |
# Prefill: persist the partial last-block state so decode steps
|
|
|
|
| 3285 |
# blocks, and a single decode step is not a complete block. Backward is
|
| 3286 |
# never called during inference so the zero is a correct no-op.
|
| 3287 |
if router_cache is not None and N == 1:
|
| 3288 |
+
regret_loss = routing_logits_fp32.new_zeros(())
|
| 3289 |
+
logit_regret = routing_logits_fp32.new_zeros(()).detach()
|
| 3290 |
else:
|
| 3291 |
regret_loss, logit_regret = self._compute_regret(
|
| 3292 |
+
routing_scores_fp32,
|
| 3293 |
+
routing_logits_fp32,
|
| 3294 |
selected_heads_blocked,
|
| 3295 |
active_mask,
|
| 3296 |
)
|
| 3297 |
|
| 3298 |
+
# ββ Phase: routing weights ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 3299 |
#
|
| 3300 |
+
# Gather from the clean fp32 Entmax distribution. If mechanical balance
|
| 3301 |
+
# assigns only experts outside a token's sparse Entmax support, the token
|
| 3302 |
+
# intentionally receives an all-zero mixture and contributes nothing on
|
| 3303 |
+
# the sparse path. Positive selected mass is normalized in fp32.
|
| 3304 |
+
gathered_fp32 = routing_scores_fp32.gather( # (B, N, K)
|
| 3305 |
+
dim=-1,
|
| 3306 |
+
index=selected_heads,
|
| 3307 |
+
)
|
| 3308 |
+
gathered_sum = gathered_fp32.sum(dim=-1, keepdim=True)
|
| 3309 |
+
normalization_denominator = gathered_sum.clamp_min(
|
| 3310 |
+
torch.finfo(gathered_fp32.dtype).tiny
|
| 3311 |
+
)
|
| 3312 |
+
routing_probs = (
|
| 3313 |
+
gathered_fp32 / normalization_denominator
|
| 3314 |
+
).to(dtype=routing_logits.dtype) # (B, N, K)
|
| 3315 |
|
| 3316 |
router_diagnostics = {
|
| 3317 |
+
"regret_loss": regret_loss,
|
| 3318 |
"logit_regret": logit_regret,
|
| 3319 |
+
"logit_std": logit_std,
|
| 3320 |
}
|
| 3321 |
return selected_heads, routing_probs, router_diagnostics
|
| 3322 |
|
|
|
|
| 3353 |
detached β it is a monitoring scalar only, in [0, β).
|
| 3354 |
|
| 3355 |
Args:
|
| 3356 |
+
routing_scores: Entmax routing probabilities, shape (B, N, L).
|
| 3357 |
Gradient flows through this tensor into regret_loss.
|
| 3358 |
+
routing_logits: Router logits in fp32, shape (B, N, L).
|
| 3359 |
Used only for the detached logit_regret.
|
| 3360 |
selected_heads_blocked: Expert assignments from the block solver,
|
| 3361 |
shape (B, num_blocks, W, K). Block geometry
|
|
|
|
| 3367 |
logit_regret: Detached scalar in [0, β).
|
| 3368 |
"""
|
| 3369 |
B, num_blocks, W, _K = selected_heads_blocked.shape
|
| 3370 |
+
L = routing_scores.shape[-1]
|
| 3371 |
+
N = routing_scores.shape[1]
|
| 3372 |
N_pad = num_blocks * W
|
| 3373 |
|
| 3374 |
# ββ Reshape into block form βββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 3404 |
assignment_mask.scatter_(dim=-1, index=selected_heads_blocked, value=1.0)
|
| 3405 |
# (B, nb, W, L)
|
| 3406 |
|
| 3407 |
+
# ββ Probability regret (gradient flows through routing_scores) βββββββ
|
| 3408 |
#
|
| 3409 |
# p_chosen: routing score at the assigned token, gated by active_float
|
| 3410 |
# so dead assignments contribute 0 β the expert accrues full regret
|
| 3411 |
# against the active maximum rather than no penalty.
|
| 3412 |
# p_max: peak routing score over active tokens; dead tokens zeroed before
|
| 3413 |
+
# max (safe because Entmax outputs are non-negative).
|
| 3414 |
+
p_chosen = (
|
| 3415 |
+
assignment_mask * active_float.unsqueeze(-1) * scores_blocked
|
| 3416 |
+
).sum(dim=2) # (B, nb, L)
|
| 3417 |
+
p_max = (
|
| 3418 |
+
active_float.unsqueeze(-1) * scores_blocked
|
| 3419 |
+
).max(dim=2).values # (B, nb, L)
|
| 3420 |
|
| 3421 |
+
regret = (p_max - p_chosen).clamp(min=0.0) # (B, nb, L)
|
| 3422 |
|
| 3423 |
# Mean over live (B, num_blocks, L) entries. Clamped to 1 for the
|
| 3424 |
# all-dead edge case where the numerator is already 0.
|
| 3425 |
+
num_live = block_active.float().sum() # scalar
|
| 3426 |
regret_loss = (
|
| 3427 |
block_active.float().unsqueeze(-1) * regret
|
| 3428 |
).sum() / num_live.mul(L).clamp(min=1.0)
|
|
|
|
| 3438 |
).sum(dim=2) # (B, nb, L)
|
| 3439 |
|
| 3440 |
logit_max = logits_blocked.masked_fill(
|
| 3441 |
+
~active_blocked.unsqueeze(-1), float("-inf")
|
| 3442 |
+
).max(dim=2).values # (B, nb, L)
|
| 3443 |
logit_max = logit_max.masked_fill(~block_active.unsqueeze(-1), 0.0)
|
| 3444 |
|
| 3445 |
logit_regret = (
|
| 3446 |
+
block_active.float().unsqueeze(-1)
|
| 3447 |
+
* (logit_max - logit_chosen).clamp(min=0.0)
|
| 3448 |
).sum() / num_live.mul(L).clamp(min=1.0)
|
| 3449 |
logit_regret = logit_regret.detach()
|
| 3450 |
|