Instructions to use PanocularAI/PanoLM-380M with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use PanocularAI/PanoLM-380M with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="PanocularAI/PanoLM-380M", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("PanocularAI/PanoLM-380M", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use PanocularAI/PanoLM-380M with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "PanocularAI/PanoLM-380M" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "PanocularAI/PanoLM-380M", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/PanocularAI/PanoLM-380M
- SGLang
How to use PanocularAI/PanoLM-380M 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 "PanocularAI/PanoLM-380M" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "PanocularAI/PanoLM-380M", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'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 "PanocularAI/PanoLM-380M" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "PanocularAI/PanoLM-380M", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use PanocularAI/PanoLM-380M with Docker Model Runner:
docker model run hf.co/PanocularAI/PanoLM-380M
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |
| # All rights reserved. | |
| # | |
| # This source code is licensed under the BSD-style license found in the | |
| # LICENSE file in the root directory of this source tree. | |
| """HuggingFace-compatible PanoLM-KDA model (Kimi Delta Attention). | |
| Standalone, KDA-locked variant of the PanoLM HF wrapper. | |
| Layer tree (matches the torchtitan ``PanoLMStateDictAdapter`` output verbatim): | |
| PanoLMForCausalLM | |
| └── model | |
| └── text PanoLMTextModel | |
| ├── tok_embeddings nn.Embedding | |
| ├── lower_bounds nn.Parameter (n_layers, hidden_size) — only when use_lower_bound | |
| ├── layers.{i} PanoLMDecoderLayer | |
| │ ├── attn_norm RMSNorm | |
| │ ├── attn FLAKimiDeltaAttention w/ optional BitLinear | |
| │ ├── mlp_norm RMSNorm | |
| │ └── mlp PanoLMMLP (BitLinear or nn.Linear) | |
| ├── norm RMSNorm | |
| └── output (Fused)BitLinear or nn.Linear # causal LM head | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from fla.layers.kda import KimiDeltaAttention as FLAKimiDeltaAttention | |
| from fla.modules import RMSNorm as FLARMSNorm | |
| from fla.modules.fused_bitlinear import BitLinear, FusedBitLinear | |
| from torch.nn import RMSNorm as TorchRMSNorm | |
| from transformers import PreTrainedModel | |
| from transformers.generation import GenerationMixin | |
| from transformers.modeling_outputs import CausalLMOutputWithPast | |
| from .configuration_panolm import PanoLMConfig | |
| def _replace_linears(module: nn.Module, fuse_bitlinear: bool) -> None: | |
| """Recursively replace ``nn.Linear`` with (Fused)BitLinear in-place.""" | |
| for name, child in module.named_children(): | |
| if isinstance(child, nn.Linear): | |
| cls = FusedBitLinear if fuse_bitlinear else BitLinear | |
| setattr( | |
| module, | |
| name, | |
| cls( | |
| in_features=child.in_features, | |
| out_features=child.out_features, | |
| bias=child.bias is not None, | |
| ), | |
| ) | |
| else: | |
| _replace_linears(child, fuse_bitlinear) | |
| class _TorchRMSNormGatedSigmoid(TorchRMSNorm): | |
| """Sigmoid-gated RMSNorm — matches KDA's o_norm (FusedRMSNormGated activation='sigmoid').""" | |
| def __init__(self, normalized_shape, eps=None, elementwise_affine=True): | |
| super().__init__( | |
| normalized_shape, eps=eps, elementwise_affine=elementwise_affine | |
| ) | |
| self.register_buffer("bias", None) | |
| def forward(self, x: torch.Tensor, g: torch.Tensor) -> torch.Tensor: | |
| return super().forward(x) * g.sigmoid() | |
| def _make_norm(hidden_size: int, eps: float, fuse_norm: bool) -> nn.Module: | |
| return ( | |
| FLARMSNorm(hidden_size, eps=eps) | |
| if fuse_norm | |
| else TorchRMSNorm(hidden_size, eps=eps) | |
| ) | |
| def _linear_cls(use_bitlinear: bool, fuse_bitlinear: bool) -> type[nn.Linear]: | |
| """Pick the projection layer type used by the MLP and LM head.""" | |
| if not use_bitlinear: | |
| return nn.Linear | |
| return FusedBitLinear if fuse_bitlinear else BitLinear | |
| class PanoLMMLP(nn.Module): | |
| """MLP that fuses gate/up projections when ``fuse_bitlinear=True``.""" | |
| def __init__( | |
| self, | |
| hidden_size: int, | |
| hidden_dim: int, | |
| use_bitlinear: bool, | |
| fuse_bitlinear: bool, | |
| ): | |
| super().__init__() | |
| cls = _linear_cls(use_bitlinear, fuse_bitlinear) | |
| if fuse_bitlinear: | |
| self.gate_proj = cls(hidden_size, 2 * hidden_dim, bias=False) | |
| self.up_proj = None | |
| else: | |
| self.gate_proj = cls(hidden_size, hidden_dim, bias=False) | |
| self.up_proj = cls(hidden_size, hidden_dim, bias=False) | |
| self.down_proj = cls(hidden_dim, hidden_size, bias=False) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if self.up_proj is not None: | |
| gate, y = self.gate_proj(x), self.up_proj(x) | |
| else: | |
| y = self.gate_proj(x) | |
| gate, y = torch.tensor_split(y, 2, -1) | |
| return self.down_proj(F.silu(gate) * y) | |
| def _build_attn(config: PanoLMConfig, layer_idx: int) -> nn.Module: | |
| """Build the fla KimiDeltaAttention module. | |
| The submodule names produced by fla (``q/k/v_proj``, ``q/k/v_conv1d``, | |
| ``f_proj``, ``g_proj``, ``b_proj``, ``o_proj``, ``o_norm``, ``A_log``, | |
| ``dt_bias``) match the keys the PanoLM adapter expects under | |
| ``model.text.layers.{i}.attn.*``. | |
| """ | |
| attn = FLAKimiDeltaAttention( | |
| hidden_size=config.hidden_size, | |
| expand_v=config.expand_v, | |
| head_dim=config.head_dim, | |
| num_heads=config.num_heads, | |
| num_v_heads=config.num_v_heads, | |
| mode=config.attn_mode, | |
| use_short_conv=config.use_short_conv, | |
| allow_neg_eigval=config.allow_neg_eigval, | |
| safe_gate=config.safe_gate, | |
| lower_bound=config.lower_bound, | |
| conv_size=config.conv_size, | |
| conv_bias=config.conv_bias, | |
| layer_idx=layer_idx, | |
| norm_eps=config.rms_norm_eps, | |
| ) | |
| if not config.fuse_norm: | |
| attn.o_norm = _TorchRMSNormGatedSigmoid( | |
| attn.o_norm.hidden_size, | |
| eps=config.rms_norm_eps, | |
| elementwise_affine=attn.o_norm.elementwise_affine, | |
| ) | |
| if config.use_bitlinear: | |
| _replace_linears(attn, config.fuse_bitlinear) | |
| return attn | |
| class PanoLMDecoderLayer(nn.Module): | |
| def __init__(self, config: PanoLMConfig, layer_idx: int): | |
| super().__init__() | |
| self.attn_norm = _make_norm( | |
| config.hidden_size, config.rms_norm_eps, config.fuse_norm | |
| ) | |
| self.attn = _build_attn(config, layer_idx) | |
| self.mlp_norm = _make_norm( | |
| config.hidden_size, config.rms_norm_eps, config.fuse_norm | |
| ) | |
| hidden_dim = config.mlp_hidden_dim | |
| if hidden_dim is None: | |
| raise ValueError( | |
| "PanoLMConfig.mlp_hidden_dim must be set (computed at upload time)." | |
| ) | |
| self.mlp = PanoLMMLP( | |
| config.hidden_size, | |
| hidden_dim, | |
| config.use_bitlinear, | |
| config.fuse_bitlinear, | |
| ) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| past_key_values=None, | |
| use_cache: bool = False, | |
| attention_mask: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| h = self.attn_norm(x) | |
| # KDA does not consume a hierarchical lower-bound; its channel-wise | |
| # vector decay subsumes that role. | |
| h, _new_past, _ = self.attn( | |
| hidden_states=h, | |
| past_key_values=past_key_values, | |
| use_cache=use_cache, | |
| attention_mask=attention_mask, | |
| ) | |
| x = x + h | |
| return x + self.mlp(self.mlp_norm(x)) | |
| class PanoLMTextModel(nn.Module): | |
| """The full text stack including the LM head. Matches the ``model.text.*`` HF prefix.""" | |
| def __init__(self, config: PanoLMConfig): | |
| super().__init__() | |
| extended_vocab = config.vocab_size + config.num_reserved_token_slots | |
| self.tok_embeddings = nn.Embedding(extended_vocab, config.hidden_size) | |
| if config.use_lower_bound: | |
| # Carried for state-dict round-trip; KDA does not consume it. | |
| self.lower_bounds = nn.Parameter( | |
| torch.zeros(config.num_hidden_layers, config.hidden_size) | |
| ) | |
| self.layers = nn.ModuleList( | |
| [PanoLMDecoderLayer(config, i) for i in range(config.num_hidden_layers)] | |
| ) | |
| self.norm = _make_norm( | |
| config.hidden_size, config.rms_norm_eps, config.fuse_norm | |
| ) | |
| out_cls = _linear_cls(config.use_bitlinear, config.fuse_bitlinear) | |
| self.output = out_cls(config.hidden_size, extended_vocab, bias=False) | |
| self.config = config | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor | None = None, | |
| inputs_embeds: torch.Tensor | None = None, | |
| attention_mask: torch.Tensor | None = None, | |
| past_key_values=None, | |
| use_cache: bool = False, | |
| ) -> torch.Tensor: | |
| if inputs_embeds is None: | |
| inputs_embeds = self.tok_embeddings(input_ids) | |
| h = inputs_embeds | |
| for layer in self.layers: | |
| h = layer( | |
| h, | |
| past_key_values=past_key_values, | |
| use_cache=use_cache, | |
| attention_mask=attention_mask, | |
| ) | |
| return self.output(self.norm(h)) | |
| class _ModelTextWrapper(nn.Module): | |
| """Container exposing ``.text`` so state-dict keys carry the ``model.text.*`` prefix.""" | |
| def __init__(self, config: PanoLMConfig): | |
| super().__init__() | |
| self.text = PanoLMTextModel(config) | |
| def forward(self, *args, **kwargs): | |
| return self.text(*args, **kwargs) | |
| class PanoLMForCausalLM(PreTrainedModel, GenerationMixin): | |
| """PanoLM-KDA with a causal LM head, HF-compatible.""" | |
| config_class = PanoLMConfig | |
| base_model_prefix = "model" | |
| supports_gradient_checkpointing = False | |
| def __init__(self, config: PanoLMConfig): | |
| super().__init__(config) | |
| self.model = _ModelTextWrapper(config) | |
| self.post_init() | |
| def get_input_embeddings(self) -> nn.Module: | |
| return self.model.text.tok_embeddings | |
| def set_input_embeddings(self, value: nn.Module) -> None: | |
| self.model.text.tok_embeddings = value | |
| def get_output_embeddings(self) -> nn.Module: | |
| return self.model.text.output | |
| def set_output_embeddings(self, new_embeddings: nn.Module) -> None: | |
| self.model.text.output = new_embeddings | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor | None = None, | |
| attention_mask: torch.Tensor | None = None, | |
| inputs_embeds: torch.Tensor | None = None, | |
| past_key_values=None, | |
| labels: torch.LongTensor | None = None, | |
| use_cache: bool | None = None, | |
| return_dict: bool | None = None, | |
| **kwargs, | |
| ) -> CausalLMOutputWithPast: | |
| logits = self.model( | |
| input_ids=input_ids, | |
| inputs_embeds=inputs_embeds, | |
| attention_mask=attention_mask, | |
| past_key_values=past_key_values, | |
| use_cache=bool(use_cache), | |
| ) | |
| loss = None | |
| if labels is not None: | |
| shift_logits = logits[..., :-1, :].contiguous() | |
| shift_labels = labels[..., 1:].contiguous() | |
| loss = F.cross_entropy( | |
| shift_logits.view(-1, shift_logits.size(-1)), | |
| shift_labels.view(-1), | |
| ) | |
| return CausalLMOutputWithPast( | |
| loss=loss, | |
| logits=logits, | |
| past_key_values=past_key_values, | |
| ) | |
| def _init_weights(self, module: nn.Module) -> None: | |
| # Wrapper is loaded from converted weights; init is a no-op beyond | |
| # what each submodule does on construction. | |
| pass | |