Text Generation
Transformers
Safetensors
PyTorch
English
French
Spanish
lfm2
classification
inference-only
structured-generation
constrained-decoding
apple-silicon
conversational
Instructions to use notnotsamuel/LFM2.5-350M-RLCD with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use notnotsamuel/LFM2.5-350M-RLCD with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="notnotsamuel/LFM2.5-350M-RLCD") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("notnotsamuel/LFM2.5-350M-RLCD") model = AutoModelForCausalLM.from_pretrained("notnotsamuel/LFM2.5-350M-RLCD", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use notnotsamuel/LFM2.5-350M-RLCD with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "notnotsamuel/LFM2.5-350M-RLCD" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "notnotsamuel/LFM2.5-350M-RLCD", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/notnotsamuel/LFM2.5-350M-RLCD
- SGLang
How to use notnotsamuel/LFM2.5-350M-RLCD 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 "notnotsamuel/LFM2.5-350M-RLCD" \ --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": "notnotsamuel/LFM2.5-350M-RLCD", "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 "notnotsamuel/LFM2.5-350M-RLCD" \ --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": "notnotsamuel/LFM2.5-350M-RLCD", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use notnotsamuel/LFM2.5-350M-RLCD with Docker Model Runner:
docker model run hf.co/notnotsamuel/LFM2.5-350M-RLCD
| """Shared prefill + hybrid cache branching + full candidate likelihood scoring.""" | |
| import copy | |
| import json | |
| import torch | |
| import jsonschema | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| MODEL_ID = "LiquidAI/LFM2.5-350M" | |
| REVISION = "9e6c6ccf47cd318696e137d381a7ded8fe4df09f" | |
| def validate_schema(schema): | |
| jsonschema.Draft202012Validator.check_schema(schema) | |
| if schema.get("type") != "object" or schema.get("additionalProperties") is not False: | |
| raise ValueError("Only closed, flat object schemas are supported") | |
| fields = schema.get("properties", {}) | |
| if not fields or set(schema.get("required", [])) != set(fields): | |
| raise ValueError("All fields must be required") | |
| if set(schema) - {"type", "properties", "required", "additionalProperties"}: | |
| raise ValueError("Unsupported object constraints") | |
| for spec in fields.values(): | |
| if set(spec) - {"type", "enum", "description"}: | |
| raise ValueError("Unsupported field constraints") | |
| if spec.get("type") == "boolean" and "enum" not in spec: | |
| continue | |
| values = spec.get("enum", []) | |
| if spec.get("type") != "string" or not values or any(type(v) is not str for v in values): | |
| raise ValueError("Fields must be booleans or nonempty string enums") | |
| if len(set(values)) != len(values): | |
| raise ValueError("Duplicate candidates") | |
| def fork_cache(cache, count): | |
| """Copy all state and reorder batch rows, including convolution history. | |
| Generic batch_repeat_interleave is not implemented for LFM2 convolution | |
| layers in the pinned Transformers release. reorder_cache handles both. | |
| index_select allocates independent storage; never broadcast mutable views. | |
| """ | |
| cloned = copy.deepcopy(cache) | |
| device = next(layer.keys.device for layer in cache.layers if hasattr(layer, "keys")) | |
| cloned.reorder_cache(torch.zeros(count, dtype=torch.long, device=device)) | |
| return cloned | |
| class Engine: | |
| def __init__(self, device="mps", dtype="float16"): | |
| self.device = device | |
| self.dtype = dtype | |
| self.tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, revision=REVISION) | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, revision=REVISION, dtype=getattr(torch, dtype), | |
| attn_implementation="eager", | |
| ).to(device).eval() | |
| self.model.requires_grad_(False) | |
| def sync(self): | |
| if self.device == "mps": | |
| torch.mps.synchronize() | |
| elif self.device.startswith("cuda"): | |
| torch.cuda.synchronize() | |
| def encode(self, text): | |
| return self.tokenizer.encode(text, add_special_tokens=False) | |
| def prompt(self, context, schema): | |
| validate_schema(schema) | |
| messages = [ | |
| {"role": "system", "content": "Extract the attributes from the text. Return only a JSON object matching this schema. Use the exact allowed values. No explanation or markdown.\n" + json.dumps(schema, ensure_ascii=False)}, | |
| {"role": "user", "content": context}, | |
| ] | |
| return self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + "{\n" | |
| def tensor(self, tokens): | |
| return torch.tensor(tokens, dtype=torch.long, device=self.device) | |
| def constrained(self, context, schema): | |
| prefix = self.encode(self.prompt(context, schema)) | |
| cache = self.model(self.tensor([prefix]), use_cache=True, logits_to_keep=1).past_key_values | |
| branches, metadata = [], [] | |
| for name, spec in schema["properties"].items(): | |
| candidates = [True, False] if spec["type"] == "boolean" else spec["enum"] | |
| # Explicit token boundary before the value; identical in cached and | |
| # uncached reference evaluation. Full JSON value includes quotes. | |
| suffix = self.encode(" " + json.dumps(name, ensure_ascii=False) + ": ") | |
| for candidate in candidates: | |
| value = self.encode(json.dumps(candidate, ensure_ascii=False) + "\n") | |
| branches.append(suffix + value) | |
| metadata.append((name, candidate, len(suffix), value)) | |
| width = max(map(len, branches)) | |
| ids = self.tensor([b + [self.tokenizer.pad_token_id] * (width - len(b)) for b in branches]) | |
| mask = self.tensor([[1] * (len(prefix) + len(b)) + [0] * (width - len(b)) for b in branches]) | |
| out = self.model(ids, past_key_values=fork_cache(cache, len(branches)), attention_mask=mask, use_cache=True) | |
| scores = [] | |
| # Full likelihood, not first-token proxy. No length normalization. | |
| for row, (_, _, start, value) in enumerate(metadata): | |
| logp = out.logits[row, start - 1:start + len(value) - 1].float().log_softmax(-1) | |
| score = logp.gather(1, self.tensor(value)[:, None]).sum() | |
| scores.append(score) | |
| scores = torch.stack(scores).cpu().tolist() | |
| selected, telemetry = {}, {} | |
| for name in schema["properties"]: | |
| options = [(m[1], s) for m, s in zip(metadata, scores) if m[0] == name] | |
| selected[name] = max(options, key=lambda x: x[1])[0] | |
| telemetry[name] = [{"value": v, "log_likelihood": s} for v, s in options] | |
| return {"text": json.dumps(selected, ensure_ascii=False, allow_nan=False), | |
| "scores": telemetry, "prompt_tokens": len(prefix), "branches": len(branches), | |
| "branch_tokens_padded": len(branches) * width, "forward_calls": 2} | |
| def autoregressive(self, context, schema, max_new_tokens=192): | |
| prefix = self.encode(self.prompt(context, schema)) | |
| ids = self.tensor([prefix]) | |
| out = self.model.generate(ids, attention_mask=torch.ones_like(ids), | |
| do_sample=False, max_new_tokens=max_new_tokens, | |
| pad_token_id=self.tokenizer.pad_token_id, | |
| eos_token_id=self.tokenizer.eos_token_id) | |
| continuation = out[0, len(prefix):].tolist() | |
| return {"text": "{\n" + self.tokenizer.decode(continuation, skip_special_tokens=True), | |
| "prompt_tokens": len(prefix), "generated_tokens": len(continuation), | |
| "hit_token_limit": len(continuation) == max_new_tokens and continuation[-1] != self.tokenizer.eos_token_id} | |