Instructions to use handshake-ai-research/cue with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use handshake-ai-research/cue with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="handshake-ai-research/cue", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("handshake-ai-research/cue", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download decoder_model.py from handshake-ai-research/cue: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/handshake-ai-research/cue/resolve/main/decoder_model.py
- Command line
-
hf download hf://handshake-ai-research/cue/decoder_model.py
-
curl -L -o decoder_model.py https://huggingface.co/handshake-ai-research/cue/resolve/main/decoder_model.py
11.2 kB
| """Persona-manual decoder with Flamingo-style gated cross-attention (inference).""" | |
| from __future__ import annotations | |
| from typing import Any | |
| import torch | |
| import torch.nn as nn | |
| from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer | |
| from .decoder_flamingo import GatedXAttnDense | |
| DEFAULT_DECODER = "Qwen/Qwen3-0.6B-Base" | |
| class PersonaMemory(nn.Module): | |
| """Expand bottleneck to a fixed set of persona memory tokens.""" | |
| def __init__(self, bottleneck_dim: int, hidden_dim: int, num_tokens: int = 16) -> None: | |
| super().__init__() | |
| self.num_tokens = num_tokens | |
| self.proj = nn.Sequential( | |
| nn.Linear(bottleneck_dim, hidden_dim), | |
| nn.GELU(), | |
| nn.Linear(hidden_dim, num_tokens * hidden_dim), | |
| ) | |
| def forward(self, bottleneck: torch.Tensor) -> torch.Tensor: | |
| out = self.proj(bottleneck) | |
| return out.view(bottleneck.size(0), self.num_tokens, -1) | |
| class CueDecoder(nn.Module): | |
| def __init__( | |
| self, | |
| *, | |
| lm: nn.Module, | |
| tokenizer: Any, | |
| memory: PersonaMemory, | |
| gated_blocks: nn.ModuleList, | |
| slot_embeddings: nn.Embedding | None, | |
| slot_projection: nn.Module | None, | |
| general_command_slots: int, | |
| user_specific_command_slots: int, | |
| style_command_slots: int, | |
| insert_every: int, | |
| config: dict[str, Any], | |
| ) -> None: | |
| super().__init__() | |
| self.lm = lm | |
| self.tokenizer = tokenizer | |
| self.memory = memory | |
| self.gated_blocks = gated_blocks | |
| self.slot_embeddings = slot_embeddings | |
| self.slot_projection = slot_projection | |
| self.general_command_slots = int(general_command_slots) | |
| self.user_specific_command_slots = int(user_specific_command_slots) | |
| self.style_command_slots = int(style_command_slots) | |
| self.insert_every = insert_every | |
| self.config = config | |
| self._persona_cache: torch.Tensor | None = None | |
| self._register_hooks() | |
| def _register_hooks(self) -> None: | |
| """Wrap selected decoder layers with gated XAttn-Dense.""" | |
| layers = self._get_layers() | |
| self._hook_handles = [] | |
| block_idx = 0 | |
| for i, layer in enumerate(layers): | |
| if (i + 1) % self.insert_every != 0: | |
| continue | |
| if block_idx >= len(self.gated_blocks): | |
| break | |
| gated = self.gated_blocks[block_idx] | |
| block_idx += 1 | |
| def _make_hook(block: GatedXAttnDense): | |
| def hook(_module, _inputs, output): | |
| persona = self._persona_cache | |
| if persona is None: | |
| return output | |
| if isinstance(output, tuple): | |
| hidden = output[0] | |
| new_hidden = block(hidden, persona) | |
| return (new_hidden,) + output[1:] | |
| return block(output, persona) | |
| return hook | |
| self._hook_handles.append(layer.register_forward_hook(_make_hook(gated))) | |
| def _get_layers(self) -> nn.ModuleList: | |
| if hasattr(self.lm, "model") and hasattr(self.lm.model, "layers"): | |
| return self.lm.model.layers | |
| if hasattr(self.lm, "transformer") and hasattr(self.lm.transformer, "h"): | |
| return self.lm.transformer.h | |
| raise ValueError("Unsupported decoder architecture: cannot find layers") | |
| def command_slot_id(self, head: str, slot: int) -> int: | |
| if head == "general": | |
| if not 0 <= slot < self.general_command_slots: | |
| raise ValueError(f"general slot {slot} out of range") | |
| return slot | |
| if head == "specific": | |
| if not 0 <= slot < self.user_specific_command_slots: | |
| raise ValueError(f"user-specific slot {slot} out of range") | |
| return self.general_command_slots + slot | |
| if head == "style": | |
| if not 0 <= slot < self.style_command_slots: | |
| raise ValueError(f"style slot {slot} out of range") | |
| return ( | |
| self.general_command_slots + self.user_specific_command_slots + slot | |
| ) | |
| raise ValueError(f"unknown command head {head!r}") | |
| def set_persona( | |
| self, | |
| bottleneck: torch.Tensor, | |
| slot_ids: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| # Memory stays fp32; cast encoder bottleneck in for a stable projection. | |
| dtype = next(self.memory.parameters()).dtype | |
| persona = self.memory(bottleneck.to(dtype=dtype)) | |
| if slot_ids is not None: | |
| if self.slot_embeddings is None or self.slot_projection is None: | |
| raise ValueError("slot_ids provided but decoder has no slot embeddings") | |
| slot = self.slot_projection(self.slot_embeddings(slot_ids).to(dtype=dtype)) | |
| persona = torch.cat([slot.unsqueeze(1), persona], dim=1) | |
| self._persona_cache = persona | |
| return self._persona_cache | |
| def clear_persona(self) -> None: | |
| self._persona_cache = None | |
| def generate( | |
| self, | |
| *, | |
| bottleneck: torch.Tensor, | |
| sentinel_ids: torch.Tensor, | |
| slot_ids: torch.Tensor | None = None, | |
| max_new_tokens: int = 1024, | |
| temperature: float = 0.0, | |
| top_p: float = 1.0, | |
| ) -> list[str]: | |
| self.set_persona(bottleneck, slot_ids=slot_ids) | |
| embed = self.lm.get_input_embeddings() | |
| batch = bottleneck.size(0) | |
| sentinel = embed(sentinel_ids).unsqueeze(0).expand(batch, -1, -1) | |
| attn = torch.ones( | |
| batch, sentinel.size(1), device=bottleneck.device, dtype=torch.long | |
| ) | |
| generated = self.lm.generate( | |
| inputs_embeds=sentinel, | |
| attention_mask=attn, | |
| max_new_tokens=max_new_tokens, | |
| do_sample=temperature > 0, | |
| temperature=temperature if temperature > 0 else None, | |
| top_p=top_p if temperature > 0 else None, | |
| pad_token_id=( | |
| self.tokenizer.pad_token_id | |
| if self.tokenizer.pad_token_id is not None | |
| else self.tokenizer.eos_token_id | |
| ), | |
| eos_token_id=self.tokenizer.eos_token_id, | |
| ) | |
| return [ | |
| self.tokenizer.decode(seq, skip_special_tokens=True).strip() | |
| for seq in generated | |
| ] | |
| def build( | |
| cls, | |
| *, | |
| model_name: str = DEFAULT_DECODER, | |
| bottleneck_dim: int = 1024, | |
| num_persona_tokens: int = 16, | |
| insert_every: int = 1, | |
| num_heads: int = 8, | |
| general_command_slots: int = 0, | |
| user_specific_command_slots: int = 0, | |
| style_command_slots: int = 0, | |
| slot_embedding_dim: int = 64, | |
| dtype: torch.dtype = torch.bfloat16, | |
| pretrained_lm: bool = True, | |
| ) -> CueDecoder: | |
| tokenizer = AutoTokenizer.from_pretrained( | |
| model_name, trust_remote_code=True, clean_up_tokenization_spaces=False | |
| ) | |
| tokenizer.padding_side = "right" | |
| if tokenizer.pad_token is None: | |
| tokenizer.pad_token = tokenizer.eos_token | |
| if pretrained_lm: | |
| # low_cpu_mem_usage=False forces real weights on CPU (no accelerate meta init). | |
| # With meta init the tied lm_head is an empty meta tensor and a later .to(device) | |
| # fails with "Cannot copy out of meta tensor". | |
| try: | |
| lm = AutoModelForCausalLM.from_pretrained( | |
| model_name, dtype=dtype, trust_remote_code=True, low_cpu_mem_usage=False | |
| ) | |
| except TypeError: | |
| lm = AutoModelForCausalLM.from_pretrained( | |
| model_name, | |
| torch_dtype=dtype, | |
| trust_remote_code=True, | |
| low_cpu_mem_usage=False, | |
| ) | |
| else: | |
| # A complete CUE checkpoint replaces every LM weight, so only the architecture | |
| # config is needed. Skips a redundant base-weight download. | |
| lm_config = AutoConfig.from_pretrained(model_name, trust_remote_code=True) | |
| lm = AutoModelForCausalLM.from_config(lm_config, trust_remote_code=True).to( | |
| dtype=dtype | |
| ) | |
| # Ensure tied weights (lm_head <-> embed_tokens) are materialized, not left on meta. | |
| if hasattr(lm, "tie_weights"): | |
| lm.tie_weights() | |
| lm.generation_config.pad_token_id = tokenizer.pad_token_id | |
| hidden = lm.get_input_embeddings().embedding_dim | |
| layers = ( | |
| lm.model.layers | |
| if hasattr(lm, "model") and hasattr(lm.model, "layers") | |
| else lm.transformer.h | |
| ) | |
| n_blocks = max(1, len(layers) // max(1, insert_every)) | |
| # Keep conditioning modules in fp32; GatedXAttnDense casts activations internally. | |
| gated = nn.ModuleList( | |
| [GatedXAttnDense(hidden, num_heads=num_heads) for _ in range(n_blocks)] | |
| ) | |
| memory = PersonaMemory(bottleneck_dim, hidden, num_persona_tokens) | |
| num_slots = ( | |
| max(0, int(general_command_slots)) | |
| + max(0, int(user_specific_command_slots)) | |
| + max(0, int(style_command_slots)) | |
| ) | |
| slot_embeddings = ( | |
| nn.Embedding(num_slots, max(1, int(slot_embedding_dim))) if num_slots else None | |
| ) | |
| slot_projection = ( | |
| nn.Sequential( | |
| nn.Linear(max(1, int(slot_embedding_dim)), hidden), | |
| nn.LayerNorm(hidden), | |
| ) | |
| if num_slots | |
| else None | |
| ) | |
| config = { | |
| "model_name": model_name, | |
| "dtype": str(dtype).removeprefix("torch."), | |
| "bottleneck_dim": bottleneck_dim, | |
| "num_persona_tokens": num_persona_tokens, | |
| "insert_every": insert_every, | |
| "num_heads": num_heads, | |
| "general_command_slots": int(general_command_slots), | |
| "user_specific_command_slots": int(user_specific_command_slots), | |
| "style_command_slots": int(style_command_slots), | |
| "slot_embedding_dim": int(slot_embedding_dim), | |
| "injection": "flamingo_gated_xattn_dense", | |
| } | |
| return cls( | |
| lm=lm, | |
| tokenizer=tokenizer, | |
| memory=memory, | |
| gated_blocks=gated, | |
| slot_embeddings=slot_embeddings, | |
| slot_projection=slot_projection, | |
| general_command_slots=general_command_slots, | |
| user_specific_command_slots=user_specific_command_slots, | |
| style_command_slots=style_command_slots, | |
| insert_every=insert_every, | |
| config=config, | |
| ) | |
| def gate_magnitudes(self) -> dict[str, float]: | |
| xs = [float(torch.tanh(b.alpha_xattn).detach().cpu()) for b in self.gated_blocks] | |
| ds = [float(torch.tanh(b.alpha_dense).detach().cpu()) for b in self.gated_blocks] | |
| return { | |
| "xattn_gate_mean": sum(xs) / max(1, len(xs)), | |
| "dense_gate_mean": sum(ds) / max(1, len(ds)), | |
| "xattn_gate_max": max(xs) if xs else 0.0, | |
| "dense_gate_max": max(ds) if ds else 0.0, | |
| } | |