Text Generation
Transformers
Safetensors
taonet
trust-remote-code
sentencepiece
custom-architecture
custom_code
Instructions to use TaoTern/TaoNet-mini-A2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use TaoTern/TaoNet-mini-A2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="TaoTern/TaoNet-mini-A2", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("TaoTern/TaoNet-mini-A2", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use TaoTern/TaoNet-mini-A2 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "TaoTern/TaoNet-mini-A2" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "TaoTern/TaoNet-mini-A2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/TaoTern/TaoNet-mini-A2
- SGLang
How to use TaoTern/TaoNet-mini-A2 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 "TaoTern/TaoNet-mini-A2" \ --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": "TaoTern/TaoNet-mini-A2", "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 "TaoTern/TaoNet-mini-A2" \ --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": "TaoTern/TaoNet-mini-A2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use TaoTern/TaoNet-mini-A2 with Docker Model Runner:
docker model run hf.co/TaoTern/TaoNet-mini-A2
| """Multimodal wrapper architecture that prefixes visual embeddings into an LLM.""" | |
| from collections import OrderedDict | |
| from typing import Any, Iterable | |
| import torch | |
| import torch.nn as nn | |
| from taoTrain.config import TrainingModeEnum | |
| from taoTrain.core import BaseModel | |
| from .cnn_encoder import CNNEncoder | |
| from .registry import register_architecture, get_model | |
| class MultimodalWrapper(BaseModel): | |
| """Wrap a text-only LLM with a CNN vision tower and visual prefix projector.""" | |
| def __init__(self, config: Any): | |
| """Initialize the multimodal wrapper from a full VLM config.""" | |
| super().__init__(config) | |
| if getattr(config, "mode", None) not in {TrainingModeEnum.VLM, TrainingModeEnum.VLM_SFT}: | |
| raise ValueError("MultimodalWrapper expects a VLMConfig or VLMSFTConfig") | |
| self.train_config = config | |
| self.model_config = config.model | |
| self.vision_prefix_tokens = config.vision_prefix_tokens | |
| self.image_token = config.image_token | |
| llm_config = self.model_config.model_copy(deep=True) | |
| llm_architecture = llm_config.llm_architecture_type | |
| if llm_architecture is None: | |
| raise ValueError("model.llm_architecture_type must be set for multimodal_wrapper") | |
| llm_config.architecture_type = llm_architecture | |
| self.llm = get_model(llm_config) | |
| self.hidden_dim = llm_config.hidden_dim | |
| if self.model_config.vision_encoder_type != "cnn": | |
| raise ValueError(f"Unsupported vision encoder type: {self.model_config.vision_encoder_type}") | |
| self.vision_encoder = CNNEncoder( | |
| image_size=config.image_size, | |
| output_dim=self.model_config.vision_output_dim, | |
| channels=self.model_config.cnn_channels, | |
| kernel_size=self.model_config.cnn_kernel_size, | |
| ) | |
| self.vision_projector = nn.Linear( | |
| self.model_config.vision_output_dim, | |
| self.hidden_dim * self.vision_prefix_tokens, | |
| ) | |
| self._configure_trainable_parameters() | |
| def get_num_layers(self) -> int: | |
| """Return the number of underlying LLM blocks.""" | |
| if hasattr(self.llm, "get_num_layers"): | |
| return self.llm.get_num_layers() | |
| return getattr(self.llm.config, "num_layers", 0) | |
| def _embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor: | |
| """Lookup token embeddings from the wrapped LLM.""" | |
| if hasattr(self.llm, "token_embedding"): | |
| return self.llm.token_embedding(input_ids) | |
| if hasattr(self.llm, "embed_tokens"): | |
| return self.llm.embed_tokens(input_ids) | |
| raise AttributeError("Wrapped LLM does not expose a supported token embedding layer") | |
| def _encode_visual_prefix(self, pixel_values: torch.Tensor) -> torch.Tensor: | |
| """Encode images and expand them into a sequence of visual prefix embeddings.""" | |
| features = self.vision_encoder(pixel_values) | |
| projected = self.vision_projector(features) | |
| return projected.view(pixel_values.size(0), self.vision_prefix_tokens, self.hidden_dim) | |
| def _build_attention_mask(self, attention_mask: torch.Tensor) -> torch.Tensor: | |
| """Expand the single image placeholder into a visual prefix attention mask.""" | |
| prefix_mask = torch.ones( | |
| attention_mask.size(0), | |
| self.vision_prefix_tokens, | |
| dtype=attention_mask.dtype, | |
| device=attention_mask.device, | |
| ) | |
| return torch.cat([prefix_mask, attention_mask[:, 1:]], dim=1) | |
| def _build_labels(self, labels: torch.Tensor | None, device: torch.device) -> torch.Tensor | None: | |
| """Mask visual prefix positions out of the language-modeling loss.""" | |
| if labels is None: | |
| return None | |
| prefix_labels = torch.full( | |
| (labels.size(0), self.vision_prefix_tokens), | |
| -100, | |
| dtype=labels.dtype, | |
| device=device, | |
| ) | |
| return torch.cat([prefix_labels, labels[:, 1:]], dim=1) | |
| def _validate_image_placeholder(self, attention_mask: torch.Tensor) -> None: | |
| """Validate the dataset contract that the image placeholder is the first active token.""" | |
| first_token_active = attention_mask[:, 0] | |
| if not torch.all(first_token_active == 1): | |
| raise ValueError("Multimodal samples must place the <image> placeholder at the first non-padding position") | |
| def _configure_trainable_parameters(self) -> None: | |
| """Apply freezing rules for the wrapped LLM.""" | |
| if not self.train_config.freeze_llm: | |
| for param in self.llm.parameters(): | |
| param.requires_grad = True | |
| return | |
| for param in self.llm.parameters(): | |
| param.requires_grad = False | |
| num_layers = self.get_num_layers() | |
| unfreeze_last_n = self.train_config.unfreeze_last_n_layers | |
| if unfreeze_last_n > num_layers: | |
| raise ValueError( | |
| f"unfreeze_last_n_layers ({unfreeze_last_n}) cannot exceed LLM layers ({num_layers})" | |
| ) | |
| if unfreeze_last_n == 0: | |
| return | |
| llm_blocks = getattr(self.llm, "blocks", None) | |
| if llm_blocks is None: | |
| raise AttributeError("Wrapped LLM does not expose `blocks`, cannot selectively unfreeze trailing layers") | |
| for block in llm_blocks[-unfreeze_last_n:]: | |
| for param in block.parameters(): | |
| param.requires_grad = True | |
| for attr_name in ("final_norm", "output_head", "lm_head"): | |
| module = getattr(self.llm, attr_name, None) | |
| if module is not None: | |
| for param in module.parameters(): | |
| param.requires_grad = True | |
| def _split_decay_groups(named_parameters: Iterable[tuple[str, nn.Parameter]]) -> tuple[list[nn.Parameter], list[nn.Parameter]]: | |
| """Split trainable params into decay and no-decay groups.""" | |
| decay_params: list[nn.Parameter] = [] | |
| no_decay_params: list[nn.Parameter] = [] | |
| for name, param in named_parameters: | |
| if not param.requires_grad: | |
| continue | |
| if "bias" in name or "norm" in name: | |
| no_decay_params.append(param) | |
| else: | |
| decay_params.append(param) | |
| return decay_params, no_decay_params | |
| def get_optimizer_param_groups(self, config) -> list[dict[str, Any]] | None: | |
| """Provide separate parameter groups for the vision tower and unfrozen LLM subset.""" | |
| vision_named_params = list(self.vision_encoder.named_parameters()) + list(self.vision_projector.named_parameters()) | |
| llm_named_params = list(self.llm.named_parameters()) | |
| vision_decay, vision_no_decay = self._split_decay_groups(vision_named_params) | |
| llm_decay, llm_no_decay = self._split_decay_groups(llm_named_params) | |
| param_groups: list[dict[str, Any]] = [] | |
| if vision_decay: | |
| param_groups.append({ | |
| "params": vision_decay, | |
| "lr": config.vision_learning_rate, | |
| "weight_decay": config.optimizer.weight_decay, | |
| }) | |
| if vision_no_decay: | |
| param_groups.append({ | |
| "params": vision_no_decay, | |
| "lr": config.vision_learning_rate, | |
| "weight_decay": 0.0, | |
| }) | |
| if llm_decay: | |
| param_groups.append({ | |
| "params": llm_decay, | |
| "lr": config.llm_learning_rate, | |
| "weight_decay": config.optimizer.weight_decay, | |
| }) | |
| if llm_no_decay: | |
| param_groups.append({ | |
| "params": llm_no_decay, | |
| "lr": config.llm_learning_rate, | |
| "weight_decay": 0.0, | |
| }) | |
| return param_groups or None | |
| def load_state_dict(self, state_dict, strict: bool = True): | |
| """Load either full multimodal checkpoints or text-only LLM checkpoints.""" | |
| current_state = self.state_dict() | |
| remapped_state = OrderedDict() | |
| for key, value in state_dict.items(): | |
| if key in current_state: | |
| remapped_state[key] = value | |
| elif f"llm.{key}" in current_state: | |
| remapped_state[f"llm.{key}"] = value | |
| else: | |
| remapped_state[key] = value | |
| return super().load_state_dict(remapped_state, strict=strict) | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor | None = None, | |
| attention_mask: torch.Tensor | None = None, | |
| labels: torch.Tensor | None = None, | |
| inputs_embeds: torch.Tensor | None = None, | |
| pixel_values: torch.Tensor | None = None, | |
| ) -> dict[str, torch.Tensor]: | |
| """Inject visual prefix embeddings and delegate the language loss to the wrapped LLM.""" | |
| if inputs_embeds is not None: | |
| return self.llm( | |
| input_ids=None, | |
| attention_mask=attention_mask, | |
| labels=labels, | |
| inputs_embeds=inputs_embeds, | |
| ) | |
| if input_ids is None: | |
| raise ValueError("input_ids must be provided for multimodal forward passes") | |
| if pixel_values is None: | |
| raise ValueError("pixel_values must be provided for multimodal forward passes") | |
| if attention_mask is None: | |
| attention_mask = torch.ones_like(input_ids) | |
| self._validate_image_placeholder(attention_mask) | |
| visual_prefix = self._encode_visual_prefix(pixel_values) | |
| token_embeddings = self._embed_input_ids(input_ids) | |
| combined_embeddings = torch.cat([visual_prefix, token_embeddings[:, 1:, :]], dim=1) | |
| combined_attention_mask = self._build_attention_mask(attention_mask) | |
| combined_labels = self._build_labels(labels, combined_embeddings.device) | |
| if combined_embeddings.size(1) > self.llm.config.max_seq_length: | |
| raise ValueError( | |
| f"Expanded multimodal sequence length ({combined_embeddings.size(1)}) exceeds max_seq_length " | |
| f"({self.llm.config.max_seq_length})" | |
| ) | |
| return self.llm( | |
| input_ids=None, | |
| attention_mask=combined_attention_mask, | |
| labels=combined_labels, | |
| inputs_embeds=combined_embeddings, | |
| ) | |