Text Generation
GGUF
English
email
triage
ollama
full-fine-tune
unsloth
cipher
edge
voice-intent
conversational
Instructions to use srock44/cipher-nano with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use srock44/cipher-nano with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf srock44/cipher-nano:Q4_K_M # Run inference directly in the terminal: llama cli -hf srock44/cipher-nano:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf srock44/cipher-nano:Q4_K_M # Run inference directly in the terminal: llama cli -hf srock44/cipher-nano:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf srock44/cipher-nano:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf srock44/cipher-nano:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf srock44/cipher-nano:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf srock44/cipher-nano:Q4_K_M
Use Docker
docker model run hf.co/srock44/cipher-nano:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use srock44/cipher-nano with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "srock44/cipher-nano" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "srock44/cipher-nano", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/srock44/cipher-nano:Q4_K_M
- Ollama
How to use srock44/cipher-nano with Ollama:
ollama run hf.co/srock44/cipher-nano:Q4_K_M
- Unsloth Studio
How to use srock44/cipher-nano with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for srock44/cipher-nano to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for srock44/cipher-nano to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for srock44/cipher-nano to start chatting
- Docker Model Runner
How to use srock44/cipher-nano with Docker Model Runner:
docker model run hf.co/srock44/cipher-nano:Q4_K_M
- Lemonade
How to use srock44/cipher-nano with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull srock44/cipher-nano:Q4_K_M
Run and chat with the model
lemonade run user.cipher-nano-Q4_K_M
List all available models
lemonade list
- Atomic Chat
| """ | |
| Full fine-tune (not LoRA) of h2oai/h2o-danube3-500m-chat for email triage. | |
| VARIANT EXPERIMENT -- candidate replacement for cipher-nano. SmolLM2 (49K | |
| vocab, 135M/360M) shrinks to the right disk size but plateaus at weak | |
| category/importance accuracy after multiple tuning attempts (LoRA vs full-FT, | |
| epoch sweeps, data reshaping) -- a base-pretraining-quality ceiling, not a | |
| tuning problem (see DEPLOYMENT.md). Qwen2.5-0.5B has the opposite problem: | |
| strong base quality but a 151,936-token vocabulary that floors its disk size | |
| around 340-400MB regardless of quantization, so it can't shrink into nano's | |
| target range either. | |
| Danube3-500M is a plain LlamaForCausalLM with a 32,000-token vocabulary -- | |
| much smaller than Qwen/Gemma, comparable to SmolLM2 -- while coming from a | |
| more conventional larger-scale pretraining recipe (h2oai's Danube series). | |
| Worth testing whether it breaks the small-vocab-means-weak-base pattern. | |
| Same data/format/eval as the other nano candidates -- only the base model | |
| differs. | |
| Outputs: | |
| outputs/danube3-500m-full/model/ - full fine-tuned HF model | |
| Usage: | |
| python train/train_danube3_500m_full.py | |
| python train/train_danube3_500m_full.py --epochs 3 --output_dir ./my_run | |
| """ | |
| import argparse | |
| import inspect | |
| import re | |
| from pathlib import Path | |
| def parse_args(): | |
| parser = argparse.ArgumentParser(description="Full fine-tune Danube3-500M for email triage") | |
| parser.add_argument("--model_name", default="h2oai/h2o-danube3-500m-chat", help="Base HF model") | |
| parser.add_argument("--train_file", default="train.jsonl", help="Training JSONL") | |
| parser.add_argument("--val_file", default="val.jsonl", help="Validation JSONL") | |
| parser.add_argument("--output_dir", default="outputs/danube3-500m-full", help="Root output directory") | |
| parser.add_argument("--max_seq_length", type=int, default=2048) | |
| parser.add_argument("--epochs", type=int, default=3) | |
| parser.add_argument("--lr", type=float, default=5e-5) | |
| parser.add_argument("--per_device_batch", type=int, default=2) | |
| parser.add_argument("--gradient_accumulation", type=int, default=4) | |
| parser.add_argument("--warmup_ratio", type=float, default=0.1) | |
| parser.add_argument("--seed", type=int, default=3407) | |
| parser.add_argument("--packing", action="store_true", default=False, help="Pack multiple short examples per sequence (default on)") | |
| parser.add_argument("--no-packing", dest="packing", action="store_false") | |
| return parser.parse_args() | |
| def main(args): | |
| from datasets import disable_caching, load_dataset | |
| from trl import SFTConfig, SFTTrainer | |
| from unsloth import FastLanguageModel, is_bfloat16_supported | |
| disable_caching() | |
| out_root = Path(args.output_dir) | |
| model_dir = out_root / "model" | |
| out_root.mkdir(parents=True, exist_ok=True) | |
| print(f"Loading {args.model_name} for FULL fine-tune (no LoRA, no quantization) ...") | |
| model, tokenizer = FastLanguageModel.from_pretrained( | |
| model_name=args.model_name, | |
| max_seq_length=args.max_seq_length, | |
| dtype=None, | |
| load_in_4bit=False, | |
| full_finetuning=True, | |
| ) | |
| print(f"Loading datasets: {args.train_file}, {args.val_file}") | |
| train_ds = load_dataset("json", data_files=args.train_file, split="train") | |
| val_ds = load_dataset("json", data_files=args.val_file, split="train") | |
| def format_chat(example): | |
| # Danube3-500m-chat uses its own native format -- <|prompt|>...eos | |
| # for user turns, <|answer|>...eos for assistant turns, strictly | |
| # alternating, no system role (confirmed against the tokenizer's own | |
| # chat_template and vocab: ChatML tokens aren't even present). | |
| # Fold the system prompt into the first user turn's content. | |
| msgs = example["messages"] | |
| system_content = "" | |
| if msgs and msgs[0]["role"] == "system": | |
| system_content = msgs[0]["content"] + "\n\n" | |
| msgs = msgs[1:] | |
| parts = [] | |
| first_user = True | |
| for msg in msgs: | |
| content = msg["content"] | |
| if msg["role"] == "user" and first_user: | |
| content = system_content + content | |
| first_user = False | |
| if msg["role"] == "user": | |
| parts.append(f"<|prompt|>{content.strip()}{tokenizer.eos_token}") | |
| else: | |
| parts.append(f"<|answer|>{content.strip()}{tokenizer.eos_token}") | |
| text = "".join(parts) | |
| return {"text": text} | |
| train_ds = train_ds.map(format_chat, remove_columns=train_ds.column_names) | |
| val_ds = val_ds.map(format_chat, remove_columns=val_ds.column_names) | |
| print(f"Train examples: {len(train_ds)} Validation examples: {len(val_ds)}") | |
| config_params = inspect.signature(SFTConfig).parameters | |
| training_kwargs = dict( | |
| output_dir=str(model_dir), | |
| num_train_epochs=args.epochs, | |
| per_device_train_batch_size=args.per_device_batch, | |
| per_device_eval_batch_size=args.per_device_batch, | |
| gradient_accumulation_steps=args.gradient_accumulation, | |
| learning_rate=args.lr, | |
| warmup_ratio=args.warmup_ratio, | |
| lr_scheduler_type="cosine", | |
| optim="adamw_8bit", | |
| eval_steps=100, | |
| save_strategy="steps", | |
| save_steps=100, | |
| logging_steps=10, | |
| seed=args.seed, | |
| fp16=not is_bfloat16_supported(), | |
| bf16=is_bfloat16_supported(), | |
| load_best_model_at_end=True, | |
| metric_for_best_model="eval_loss", | |
| greater_is_better=False, | |
| report_to="none", | |
| dataset_text_field="text", | |
| packing=args.packing, | |
| ) | |
| if "eval_strategy" in config_params: | |
| training_kwargs["eval_strategy"] = "steps" | |
| else: | |
| training_kwargs["evaluation_strategy"] = "steps" | |
| if "max_length" in config_params: | |
| training_kwargs["max_length"] = args.max_seq_length | |
| else: | |
| training_kwargs["max_seq_length"] = args.max_seq_length | |
| training_args = SFTConfig(**training_kwargs) | |
| trainer_kwargs = dict( | |
| model=model, | |
| train_dataset=train_ds, | |
| eval_dataset=val_ds, | |
| args=training_args, | |
| ) | |
| trainer_params = inspect.signature(SFTTrainer).parameters | |
| if "processing_class" in trainer_params: | |
| trainer_kwargs["processing_class"] = tokenizer | |
| else: | |
| trainer_kwargs["tokenizer"] = tokenizer | |
| _orig_convert_tokens_to_ids = tokenizer.convert_tokens_to_ids | |
| _sentinel_re = re.compile(r"^<([A-Z]+)_TOKEN>$") | |
| def _convert_tokens_to_ids_patched(token): | |
| match = _sentinel_re.match(token) if isinstance(token, str) else None | |
| if match: | |
| real_id = getattr(tokenizer, f"{match.group(1).lower()}_token_id", None) | |
| if real_id is not None: | |
| return real_id | |
| return _orig_convert_tokens_to_ids(token) | |
| _orig_prepare_dataset = SFTTrainer._prepare_dataset | |
| def _prepare_dataset_patched(self, dataset, processing_class, ds_args, *rest, **kw): | |
| ds_args.dataset_num_proc = None | |
| return _orig_prepare_dataset(self, dataset, processing_class, ds_args, *rest, **kw) | |
| SFTTrainer._prepare_dataset = _prepare_dataset_patched | |
| tokenizer.convert_tokens_to_ids = _convert_tokens_to_ids_patched | |
| try: | |
| trainer = SFTTrainer(**trainer_kwargs) | |
| finally: | |
| tokenizer.convert_tokens_to_ids = _orig_convert_tokens_to_ids | |
| SFTTrainer._prepare_dataset = _orig_prepare_dataset | |
| print("Starting training...") | |
| trainer.train() | |
| print(f"Saving full fine-tuned model to {model_dir}") | |
| model.save_pretrained(model_dir) | |
| tokenizer.save_pretrained(model_dir) | |
| print("Done.") | |
| if __name__ == "__main__": | |
| args = parse_args() | |
| main(args) | |