Skip to content

Commit ad1860d

Browse files
justinchubyCopilot
andcommitted
Add Phi-4 Flash SambaY support
Implement the pinned Phi-4 mini flash reasoning architecture with source-faithful Mamba, differential attention, YOCO state topology, dynamic cache ABI, exact scoped CUDA parity coverage, and explicit runtime/cache limitations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 489bb9f5-59db-436a-a63b-eb8164e879e3 Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
1 parent 8c38c06 commit ad1860d

25 files changed

Lines changed: 2323 additions & 11 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: "L3: Phi-4 Flash Pinned CUDA Parity"
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
paths:
7+
- "src/mobius/_configs/_base.py"
8+
- "src/mobius/_configs/__init__.py"
9+
- "src/mobius/_builder.py"
10+
- "src/mobius/_model_package.py"
11+
- "src/mobius/_registry.py"
12+
- "src/mobius/components/__init__.py"
13+
- "src/mobius/components/_differential_attention.py"
14+
- "src/mobius/components/_mamba_block.py"
15+
- "src/mobius/components/_common.py"
16+
- "src/mobius/functions/linear_attention.py"
17+
- "src/mobius/models/__init__.py"
18+
- "src/mobius/models/phi4flash.py"
19+
- "src/mobius/tasks/__init__.py"
20+
- "src/mobius/tasks/_base.py"
21+
- "src/mobius/tasks/_phi4flash.py"
22+
- "tests/phi4flash_integration_test.py"
23+
- "requirements/ci/phi4flash.txt"
24+
- ".github/workflows/phi4flash_parity.yml"
25+
26+
permissions:
27+
contents: read
28+
29+
jobs:
30+
pinned-source-parity:
31+
runs-on:
32+
- self-hosted
33+
- "1ES.Pool=onnxruntime-ep-mobius-github-linux-a10"
34+
- "JobId=phi4flash-parity-${{ github.run_id }}-${{ github.run_attempt }}"
35+
timeout-minutes: 45
36+
steps:
37+
- uses: actions/checkout@v7
38+
- uses: actions/setup-python@v7
39+
with:
40+
python-version: "3.12"
41+
- name: Install exact Phi-4 Flash reference stack
42+
run: |
43+
pip install -r requirements/ci/requirements.txt
44+
pip install torch==2.6.0 --index-url https://download.pytorch.org/whl/cu124
45+
pip install -r requirements/ci/phi4flash.txt
46+
pip install huggingface_hub ml_dtypes numpy onnx_ir onnx-shape-inference onnxscript rfc8785 safetensors tqdm
47+
pip install onnxruntime-easy pytest-timeout
48+
pip install --no-deps -e .
49+
pip install --pre --no-deps --index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/ onnxruntime-gpu
50+
- name: Run pinned remote-source L3 parity
51+
env:
52+
MOBIUS_PHI4FLASH_REFERENCE: "1"
53+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
54+
run: pytest tests/phi4flash_integration_test.py -m integration -v --tb=short

docs/model-catalog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Models combining Mamba/SSM layers with transformer attention layers.
107107
|---|---|---|---|
108108
| `jamba` | `JambaCausalLMModel` | `hybrid-text-generation` | `ai21labs/Jamba-v0.1` |
109109
| `bamba` | `BambaCausalLMModel` | `hybrid-text-generation` | `ibm-fms/Bamba-9B` |
110+
| `phi4flash` | `Phi4FlashCausalLMModel` | `phi4flash-text-generation` | `microsoft/Phi-4-mini-flash-reasoning` |
110111

111112
## Vision-Language (Multimodal)
112113

requirements/ci/phi4flash.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Exact CUDA reference stack for microsoft/Phi-4-mini-flash-reasoning
2+
# at 1dff8163d28ec880ca2411c474ddc0a927792810. This is deliberately scoped
3+
# to the Phi-4 Flash parity workflow: it must not constrain public extras.
4+
torch==2.6.0
5+
transformers==4.46.1
6+
accelerate==1.4.0
7+
flash-attn==2.7.4.post1
8+
mamba-ssm==2.2.4
9+
causal-conv1d==1.5.0.post8

src/mobius/__main__.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ def _resolve_static_cache_task(model_type: str) -> ModelTask:
181181
"--static-cache cannot represent Falcon-H1's per-layer K, V, "
182182
"convolution, and SSM states"
183183
)
184+
if model_type == "phi4flash":
185+
raise SystemExit(
186+
"Error: --static-cache cannot represent Phi-4 Flash's heterogeneous SambaY "
187+
"convolution, SSM, local-KV, and shared-global-KV state."
188+
)
184189
if model_type == "glm_moe_dsa":
185190
raise ValueError(
186191
"--static-cache cannot represent GLM-DSA's per-layer-varying packed DSA cache"
@@ -462,6 +467,11 @@ def _resolve_static_cache_task(model_type: str) -> ModelTask:
462467
raise
463468
model_type = hf_config.model_type
464469
parent_config = hf_config
470+
if export_paged_attention and model_type == "phi4flash":
471+
raise SystemExit(
472+
"Error: --features paged-attention cannot represent Phi-4 Flash's "
473+
"heterogeneous SambaY state."
474+
)
465475
from mobius.integrations.transformers._builder import (
466476
_is_qwen4_exp_composite,
467477
_reject_unsupported_affine_qwen4,
@@ -634,6 +644,14 @@ def _save_package(
634644
) -> None:
635645
"""Save a ModelPackage to disk, applying optimizations and runtime configs."""
636646
runtime = getattr(args, "runtime", None)
647+
if (
648+
runtime == "ort-genai"
649+
and getattr(getattr(pkg, "config", None), "model_type", None) == "phi4flash"
650+
):
651+
raise SystemExit(
652+
"Error: ORT GenAI cannot represent Phi-4 Flash's heterogeneous dynamic SambaY "
653+
"cache ABI. Use the custom ONNX Runtime Session cache contract instead."
654+
)
637655

638656
components = (lambda name: name == component_filter) if component_filter else None
639657
for name, model in pkg.items():
@@ -684,15 +702,18 @@ def _save_package(
684702
pkg,
685703
getattr(args, "revision", None),
686704
)
687-
artifacts = write_ort_genai_config(
688-
pkg,
689-
output_dir,
690-
hf_model_id=hf_model_id,
691-
ep=ep,
692-
local_config_dir=local_config_dir,
693-
trust_remote_code=getattr(args, "trust_remote_code", False),
694-
revision=runtime_revision,
695-
)
705+
try:
706+
artifacts = write_ort_genai_config(
707+
pkg,
708+
output_dir,
709+
hf_model_id=hf_model_id,
710+
ep=ep,
711+
local_config_dir=local_config_dir,
712+
trust_remote_code=getattr(args, "trust_remote_code", False),
713+
revision=runtime_revision,
714+
)
715+
except ValueError as error:
716+
raise SystemExit(f"Error: {error}") from error
696717
for name, path in artifacts.items():
697718
print(f" {name}: {path}")
698719
elif runtime == "onnx-genai":

src/mobius/_configs/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
NemotronHConfig,
6666
NemotronParseConfig,
6767
ParakeetCTCConfig,
68+
Phi4FlashConfig,
6869
Plamo2Config,
6970
Qwen4ExpConfig,
7071
Qwen35MtpConfig,
@@ -161,6 +162,7 @@
161162
"NemotronParseConfig",
162163
"NemotronHConfig",
163164
"ParakeetCTCConfig",
165+
"Phi4FlashConfig",
164166
"Plamo2Config",
165167
"QuantizationConfig",
166168
"Qwen4ExpConfig",

src/mobius/_configs/_base.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3767,6 +3767,148 @@ def from_transformers(cls, config, parent_config=None) -> BambaConfig:
37673767
)
37683768

37693769

3770+
@dataclasses.dataclass
3771+
class Phi4FlashConfig(ArchitectureConfig):
3772+
"""Configuration for Phi-4 Flash's SambaY hybrid decoder.
3773+
3774+
The first half alternates Mamba-1 and local differential GQA. Layer 16
3775+
produces transient Mamba memory, layer 17 produces global shared KV, and
3776+
the final half consumes those two shared values through cross-Mamba and
3777+
cross-differential-attention layers. It deliberately has no RoPE despite
3778+
the inactive ``rope_theta`` field in the remote configuration.
3779+
"""
3780+
3781+
layer_norm_eps: float = 1e-5
3782+
attention_dropout: float = 0.0
3783+
mamba_d_state: int = 16
3784+
mamba_d_conv: int = 4
3785+
mamba_expand: int = 2
3786+
mamba_dt_rank: int = 160
3787+
mamba_conv_bias: bool = True
3788+
mamba_proj_bias: bool = False
3789+
mb_per_layer: int = 2
3790+
local_attention_window: int = 512
3791+
3792+
def __post_init__(self) -> None:
3793+
if self.num_hidden_layers % 4:
3794+
raise ValueError("Phi4FlashConfig num_hidden_layers must be divisible by four")
3795+
if self.mb_per_layer != 2:
3796+
raise ValueError(
3797+
"Phi4FlashConfig supports the SambaY mb_per_layer=2 schedule only"
3798+
)
3799+
if self.export_paged_attention:
3800+
raise ValueError(
3801+
"Phi4FlashConfig cannot use paged attention: SambaY has heterogeneous "
3802+
"recurrent, local-KV, and shared-global-KV state"
3803+
)
3804+
if self.local_attention_window <= 0:
3805+
raise ValueError("Phi4FlashConfig local_attention_window must be positive")
3806+
if self.mamba_d_state <= 0 or self.mamba_d_conv <= 1 or self.mamba_expand <= 0:
3807+
raise ValueError(
3808+
"Phi4FlashConfig requires positive Mamba state/expansion and a convolution width above one"
3809+
)
3810+
if self.mamba_dt_rank <= 0:
3811+
raise ValueError("Phi4FlashConfig mamba_dt_rank must be positive")
3812+
if self.hidden_size % self.num_attention_heads:
3813+
raise ValueError(
3814+
"Phi4FlashConfig hidden_size must be divisible by num_attention_heads"
3815+
)
3816+
if self.head_dim != self.hidden_size // self.num_attention_heads:
3817+
raise ValueError(
3818+
"Phi4FlashConfig head_dim must equal hidden_size / num_attention_heads"
3819+
)
3820+
if self.num_attention_heads % 2 or self.num_key_value_heads % 2:
3821+
raise ValueError(
3822+
"Phi4FlashConfig differential attention requires even Q and KV head counts"
3823+
)
3824+
if self.num_attention_heads % self.num_key_value_heads:
3825+
raise ValueError(
3826+
"Phi4FlashConfig num_attention_heads must be divisible by num_key_value_heads"
3827+
)
3828+
expected = self._derive_layer_types(self.num_hidden_layers)
3829+
if self.layer_types is None:
3830+
self.layer_types = expected
3831+
elif self.layer_types != expected:
3832+
raise ValueError(
3833+
"Phi4FlashConfig layer_types is derived from the fixed SambaY schedule; "
3834+
f"expected {expected}, got {self.layer_types}"
3835+
)
3836+
# `rope_theta` is configuration residue. The source has no RoPE call.
3837+
self.rope_type = None
3838+
self.rope_theta = None
3839+
self.rope_scaling = None
3840+
self.partial_rotary_factor = None
3841+
3842+
@staticmethod
3843+
def _derive_layer_types(num_hidden_layers: int) -> list[str]:
3844+
midpoint = num_hidden_layers // 2
3845+
global_attention = midpoint + 1
3846+
layer_types = []
3847+
for index in range(num_hidden_layers):
3848+
if index < midpoint:
3849+
layer_types.append(
3850+
"mamba" if index % 2 == 0 else "local_differential_attention"
3851+
)
3852+
elif index == midpoint:
3853+
layer_types.append("shared_memory_mamba")
3854+
elif index == global_attention:
3855+
layer_types.append("global_differential_attention")
3856+
else:
3857+
layer_types.append(
3858+
"cross_mamba" if index % 2 == 0 else "cross_differential_attention"
3859+
)
3860+
return layer_types
3861+
3862+
@property
3863+
def cache_slot_count(self) -> int:
3864+
"""The source cache owns layers 0 through the global attention layer."""
3865+
return self.num_hidden_layers // 2 + 2
3866+
3867+
@classmethod
3868+
def from_transformers(cls, config, parent_config=None) -> Phi4FlashConfig:
3869+
base = ArchitectureConfig.from_transformers(config, parent_config)
3870+
# The pinned remote config exposes its checkpoint precision as
3871+
# ``torch_dtype`` rather than the modern ``dtype`` property.
3872+
checkpoint_dtype = _resolve_dtype_value(getattr(config, "torch_dtype", None))
3873+
local_window = getattr(config, "sliding_window", 512)
3874+
if isinstance(local_window, (list, tuple)):
3875+
local_window = next((value for value in local_window if value is not None), 512)
3876+
dt_rank = getattr(config, "mamba_dt_rank", "auto")
3877+
if dt_rank == "auto":
3878+
dt_rank = math.ceil(base.hidden_size / 16)
3879+
excluded = {
3880+
"layer_types",
3881+
"sliding_window",
3882+
"rope_type",
3883+
"rope_theta",
3884+
"rope_scaling",
3885+
"partial_rotary_factor",
3886+
"dtype",
3887+
}
3888+
base_fields = {
3889+
key: value for key, value in _shallow_fields(base).items() if key not in excluded
3890+
}
3891+
return cls(
3892+
**base_fields,
3893+
dtype=checkpoint_dtype or base.dtype,
3894+
layer_types=cls._derive_layer_types(base.num_hidden_layers),
3895+
layer_norm_eps=getattr(config, "layer_norm_eps", 1e-5),
3896+
attention_dropout=getattr(config, "attention_dropout", 0.0),
3897+
mamba_d_state=getattr(config, "mamba_d_state", 16),
3898+
mamba_d_conv=getattr(config, "mamba_d_conv", 4),
3899+
mamba_expand=getattr(config, "mamba_expand", 2),
3900+
mamba_dt_rank=int(dt_rank),
3901+
mamba_conv_bias=getattr(config, "mamba_conv_bias", True),
3902+
mamba_proj_bias=getattr(config, "mamba_proj_bias", False),
3903+
mb_per_layer=getattr(config, "mb_per_layer", 2),
3904+
local_attention_window=int(local_window),
3905+
rope_type=None,
3906+
rope_theta=None,
3907+
rope_scaling=None,
3908+
partial_rotary_factor=None,
3909+
)
3910+
3911+
37703912
@dataclasses.dataclass
37713913
class FalconH1Config(ArchitectureConfig):
37723914
"""Configuration for Falcon-H1 parallel Attention + Mamba2 decoder layers."""

src/mobius/_registry.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
MuseGlimmerConfig,
4545
NemotronParseConfig,
4646
ParakeetCTCConfig,
47+
Phi4FlashConfig,
4748
Plamo2Config,
4849
Qwen4ExpConfig,
4950
SenseNovaU1Config,
@@ -128,6 +129,7 @@
128129
Phi3MoECausalLMModel,
129130
Phi3SmallCausalLMModel,
130131
Phi3VModel,
132+
Phi4FlashCausalLMModel,
131133
Phi4MMMultiModalModel,
132134
Phi4SigLIPModel,
133135
PhiCausalLMModel,
@@ -628,6 +630,22 @@ def _detect_fallback_registration(hf_config) -> ModelRegistration | None:
628630
"phi": ModelRegistration(PhiCausalLMModel),
629631
"phi3": ModelRegistration(Phi3CausalLMModel),
630632
"phi3small": ModelRegistration(Phi3SmallCausalLMModel),
633+
"phi4flash": ModelRegistration(
634+
Phi4FlashCausalLMModel,
635+
task="phi4flash-text-generation",
636+
config_class=Phi4FlashConfig,
637+
test_model_id="microsoft/Phi-4-mini-flash-reasoning",
638+
test_revision="1dff8163d28ec880ca2411c474ddc0a927792810",
639+
family="phi4flash",
640+
variant="SambaY-SSM+differential-attention",
641+
),
642+
"Phi4FlashForCausalLM": ModelRegistration(
643+
Phi4FlashCausalLMModel,
644+
task="phi4flash-text-generation",
645+
config_class=Phi4FlashConfig,
646+
family="phi4flash",
647+
variant="SambaY-SSM+differential-attention",
648+
),
631649
"qwen": ModelRegistration(QwenCausalLMModel),
632650
"qwen3": ModelRegistration(Qwen3CausalLMModel),
633651
"qwen3_5_text": ModelRegistration(Qwen35CausalLMModel),

src/mobius/components/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
"DeepSeekOCRCLIPEncoder",
3030
"DeepSeekOCRFullImageEncoder",
3131
"DeepSeekOCRVisionEncoder",
32+
"DifferentialGQAAttention",
3233
"DiffusionFFN",
3334
"DiffusionSelfAttention",
3435
"Dots3NoteAudioProjector",
3536
"Dots3NoteAudioEncoder",
3637
"DotsOCRProjector",
3738
"DotsVisionEncoder",
3839
"Embedding",
40+
"FloatSwiGLU",
3941
"FusedQKVAttention",
4042
"EncoderAttention",
4143
"EncoderDecoderAttention",
@@ -44,6 +46,7 @@
4446
"FusedGateUpMLP",
4547
"GatedDeltaNet",
4648
"GatedMLP",
49+
"GatedMemoryMixer",
4750
"GatedRMSNorm",
4851
"KimiDeltaAttention",
4952
"KimiMLAAttention",
@@ -118,6 +121,7 @@
118121
"ScaleFreeRMSNorm",
119122
"SelectiveScan",
120123
"SequenceMambaBlock",
124+
"StatefulMambaBlock",
121125
"SequenceSelectiveScan",
122126
"SiLU",
123127
"Siglip2NaFlexVisionEmbeddings",
@@ -238,6 +242,7 @@
238242
create_decoder_layer,
239243
)
240244
from mobius.components._deepseek_mla import DeepSeekMLA as DeepSeekMLA
245+
from mobius.components._differential_attention import DifferentialGQAAttention
241246
from mobius.components._diffusion import (
242247
AdaLayerNormOutput,
243248
AdaLayerNormZero,
@@ -282,9 +287,14 @@
282287
)
283288
from mobius.components._llama4_vision import Llama4VisionTower
284289
from mobius.components._lora import LoRALinear
290+
from mobius.components._mamba_block import (
291+
FloatSwiGLU,
292+
GatedMemoryMixer,
293+
SequenceMambaBlock,
294+
StatefulMambaBlock,
295+
)
285296
from mobius.components._mamba_block import Mamba2Block as Mamba2Block
286297
from mobius.components._mamba_block import MambaBlock as MambaBlock
287-
from mobius.components._mamba_block import SequenceMambaBlock
288298
from mobius.components._mimo_minimax_vision import (
289299
DualTemporalPatchEmbedding as MiMoDualTemporalPatchEmbedding,
290300
)

0 commit comments

Comments
 (0)