Text Generation
Transformers
Safetensors
Vietnamese
SAI
custom-code
vietnamese
causal-lm
custom_code
Instructions to use thongbuind/SAI_375M with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use thongbuind/SAI_375M with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="thongbuind/SAI_375M", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("thongbuind/SAI_375M", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use thongbuind/SAI_375M with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "thongbuind/SAI_375M" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "thongbuind/SAI_375M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/thongbuind/SAI_375M
- SGLang
How to use thongbuind/SAI_375M 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 "thongbuind/SAI_375M" \ --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": "thongbuind/SAI_375M", "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 "thongbuind/SAI_375M" \ --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": "thongbuind/SAI_375M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use thongbuind/SAI_375M with Docker Model Runner:
docker model run hf.co/thongbuind/SAI_375M
Download generate.py from thongbuind/SAI_375M: direct link, hf CLI and curl.
- Browser
- Download file 18 kB
-
https://huggingface.co/thongbuind/SAI_375M/resolve/main/generate.py
- Command line
-
hf download hf://thongbuind/SAI_375M/generate.py
-
curl -L -o generate.py https://huggingface.co/thongbuind/SAI_375M/resolve/main/generate.py
18 kB
| from collections.abc import Callable, Sequence | |
| from dataclasses import dataclass, field | |
| import torch | |
| VERSION = "dev3.0.0" | |
| TokenConstraint = Callable[[list[int]], Sequence[int] | None] | |
| class GenerationConfig: | |
| strategy: str = "auto" | |
| max_new_tokens: int = 500 | |
| beam_size: int = 5 | |
| stable_steps: int | None = 8 | |
| temperature: float = 0.8 | |
| top_k: int = 0 | |
| top_p: float = 0.95 | |
| repetition_penalty: float = 1.2 | |
| no_repeat_ngram_size: int = 3 | |
| length_penalty: float = 1.0 | |
| lowercase: bool = True | |
| seed: int | None = None | |
| stop_sequences: list[list[int]] = field(default_factory=list) | |
| class GenerationResult: | |
| text: str | |
| token_ids: list[int] | |
| finish_reason: str | |
| prompt_tokens: int | |
| completion_tokens: int | |
| decode_steps: int | |
| completed_beams: int | |
| def _special_ids(tokenizer): | |
| ids = { | |
| "bos": tokenizer.piece_to_id("[BOS]"), | |
| "eos": tokenizer.piece_to_id("[EOS]"), | |
| "im_start": tokenizer.piece_to_id("<|im_start|>"), | |
| "im_end": tokenizer.piece_to_id("<|im_end|>"), | |
| } | |
| if any(token_id < 0 for token_id in ids.values()): | |
| raise ValueError(f"Tokenizer is missing required special tokens: {ids}") | |
| return ids | |
| def _normalise_messages(user_input, system=None, instruction=None, history=None, messages=None): | |
| if system is not None and instruction is not None: | |
| raise ValueError("Use either system or instruction, not both") | |
| system = system if system is not None else instruction | |
| if messages is not None: | |
| if user_input not in (None, "") or system is not None or history: | |
| raise ValueError("Use either messages or user_input/instruction/history, not both") | |
| source = list(messages) | |
| else: | |
| source = [] | |
| if system: | |
| source.append({"role": "system", "content": system}) | |
| for turn in history or []: | |
| if "role" in turn: | |
| source.append(turn) | |
| continue | |
| if turn.get("user"): | |
| source.append({"role": "user", "content": turn["user"]}) | |
| if turn.get("model"): | |
| source.append({"role": "model", "content": turn["model"]}) | |
| if user_input is not None: | |
| source.append({"role": "user", "content": user_input}) | |
| result = [] | |
| role_map = {"assistant": "model", "model": "model", "user": "user", "system": "system"} | |
| for message in source: | |
| role = role_map.get(str(message.get("role", "")).lower()) | |
| content = str(message.get("content", "")).strip() | |
| if role is None or not content: | |
| raise ValueError(f"Invalid chat message: {message!r}") | |
| result.append({"role": role, "content": content}) | |
| if not result or result[-1]["role"] != "user": | |
| raise ValueError("The final input message must have role='user'") | |
| return result | |
| def _build_input(user_input, tokenizer, *, max_seq_len, config, system=None, instruction=None, history=None, messages=None): | |
| special = _special_ids(tokenizer) | |
| normalised = _normalise_messages(user_input, system, instruction, history, messages) | |
| def encode_message(message): | |
| text = f"{message['role']}\n{message['content']}" | |
| if config.lowercase: | |
| text = text.lower() | |
| return [special["im_start"]] + tokenizer.encode(text, out_type=int) + [special["im_end"]] | |
| segments = [(message["role"], encode_message(message)) for message in normalised] | |
| assistant_prefix = [special["im_start"]] + tokenizer.encode("model\n", out_type=int) | |
| prompt_budget = max_seq_len - config.max_new_tokens | |
| required = 1 + len(assistant_prefix) | |
| if prompt_budget < required: | |
| raise ValueError("max_new_tokens leaves no room for a prompt") | |
| system_segments = [segment for role, segment in segments if role == "system"] | |
| latest = segments[-1][1] | |
| fixed_len = required + len(latest) + sum(map(len, system_segments)) | |
| if fixed_len > prompt_budget: | |
| raise ValueError( | |
| f"System + latest user message need {fixed_len} tokens, " | |
| f"but prompt budget is {prompt_budget}" | |
| ) | |
| middle = [(role, segment) for role, segment in segments[:-1] if role != "system"] | |
| turn_groups = [] | |
| index = 0 | |
| while index < len(middle): | |
| role, segment = middle[index] | |
| group = [segment] | |
| if role == "user" and index + 1 < len(middle) and middle[index + 1][0] == "model": | |
| group.append(middle[index + 1][1]) | |
| index += 1 | |
| turn_groups.append(group) | |
| index += 1 | |
| kept_groups = [] | |
| used = fixed_len | |
| for group in reversed(turn_groups): | |
| group_len = sum(map(len, group)) | |
| if used + group_len <= prompt_budget: | |
| kept_groups.append(group) | |
| used += group_len | |
| kept_groups.reverse() | |
| input_ids = [special["bos"]] | |
| for segment in system_segments: | |
| input_ids.extend(segment) | |
| for group in kept_groups: | |
| for segment in group: | |
| input_ids.extend(segment) | |
| input_ids.extend(latest) | |
| input_ids.extend(assistant_prefix) | |
| return input_ids, special | |
| def validate_generation_config(config): | |
| """Báo ValueError nếu giá trị của ``config`` nằm ngoài miền hợp lệ; gọi được trước khi nạp model.""" | |
| if config.strategy not in {"auto", "beam", "greedy", "sample"}: | |
| raise ValueError("strategy must be auto, beam, greedy, or sample") | |
| if config.max_new_tokens < 1: | |
| raise ValueError("max_new_tokens must be >= 1") | |
| if config.beam_size < 1: | |
| raise ValueError("beam_size must be >= 1") | |
| if config.stable_steps is not None and config.stable_steps < 1: | |
| raise ValueError("stable_steps must be >= 1 or None") | |
| if config.temperature <= 0: | |
| raise ValueError("temperature must be > 0") | |
| if config.top_k < 0: | |
| raise ValueError("top_k must be >= 0") | |
| if not 0 < config.top_p <= 1: | |
| raise ValueError("top_p must be in (0, 1]") | |
| if config.repetition_penalty <= 0: | |
| raise ValueError("repetition_penalty must be > 0") | |
| if config.no_repeat_ngram_size < 0: | |
| raise ValueError("no_repeat_ngram_size must be >= 0") | |
| if config.length_penalty < 0: | |
| raise ValueError("length_penalty must be >= 0") | |
| def _apply_repetition_penalty(logits, generated, penalty): | |
| if penalty == 1.0 or not generated: | |
| return logits | |
| logits = logits.clone() | |
| token_ids = torch.tensor(list(set(generated)), dtype=torch.long, device=logits.device) | |
| selected = logits.index_select(-1, token_ids) | |
| selected = torch.where(selected < 0, selected * penalty, selected / penalty) | |
| logits.scatter_(-1, token_ids, selected) | |
| return logits | |
| def _banned_ngram_tokens(generated, n): | |
| if n <= 0 or len(generated) < n - 1: | |
| return set() | |
| if n == 1: | |
| return set(generated) | |
| prefix = tuple(generated[-(n - 1):]) | |
| return { | |
| generated[index + n - 1] | |
| for index in range(len(generated) - n + 1) | |
| if tuple(generated[index:index + n - 1]) == prefix | |
| } | |
| def _is_stopped(generated, stop_ids, stop_sequences): | |
| if generated and generated[-1] in stop_ids: | |
| return True | |
| return any( | |
| sequence and len(generated) >= len(sequence) and generated[-len(sequence):] == sequence | |
| for sequence in stop_sequences | |
| ) | |
| def _decode(generated, tokenizer, stop_ids, stop_sequences): | |
| output = list(generated) | |
| for sequence in stop_sequences: | |
| if sequence and len(output) >= len(sequence) and output[-len(sequence):] == sequence: | |
| del output[-len(sequence):] | |
| break | |
| while output and output[-1] in stop_ids: | |
| output.pop() | |
| return tokenizer.decode(output) | |
| def _beam_score(candidate, length_penalty): | |
| length = max(len(candidate["generated"]), 1) | |
| return candidate["log_prob"] / (length ** length_penalty) | |
| def _process_logits(logits, generated, config, constraint=None): | |
| logits = _apply_repetition_penalty(logits, generated, config.repetition_penalty) | |
| banned = _banned_ngram_tokens(generated, config.no_repeat_ngram_size) | |
| if banned: | |
| logits = logits.clone() | |
| logits[list(banned)] = -torch.inf | |
| if constraint is not None: | |
| allowed = constraint(generated) | |
| if allowed is not None: | |
| allowed = list(allowed) | |
| if not allowed: | |
| raise ValueError("Token constraint returned an empty allowed set") | |
| constrained = torch.full_like(logits, -torch.inf) | |
| constrained[allowed] = logits[allowed] | |
| logits = constrained | |
| return logits | |
| def _filter_sampling_logits(logits, config): | |
| logits = logits / config.temperature | |
| if config.top_k > 0: | |
| threshold = torch.topk(logits, min(config.top_k, logits.numel())).values[-1] | |
| logits = logits.masked_fill(logits < threshold, -torch.inf) | |
| if config.top_p < 1: | |
| sorted_logits, sorted_indices = torch.sort(logits, descending=True) | |
| probabilities = torch.softmax(sorted_logits, dim=-1) | |
| remove = probabilities.cumsum(dim=-1) > config.top_p | |
| remove[1:] = remove[:-1].clone() | |
| remove[0] = False | |
| logits = logits.clone() | |
| logits[sorted_indices[remove]] = -torch.inf | |
| return logits | |
| def _single_path_generate(model, input_ids, tokenizer, special, config, strategy, constraint): | |
| device = model.lm_head.weight.device | |
| stop_ids = {special["eos"], special["im_end"]} | |
| stop_sequences = [list(sequence) for sequence in config.stop_sequences] | |
| prompt_len = len(input_ids) | |
| cache = model.init_cache(1, min(model.max_seq_len, prompt_len + config.max_new_tokens), device) | |
| prompt = torch.tensor([input_ids], dtype=torch.long, device=device) | |
| generator = None | |
| if config.seed is not None: | |
| generator = torch.Generator(device=device).manual_seed(config.seed) | |
| with torch.inference_mode(): | |
| logits, _ = model.prefill(prompt, kv_cache=cache) | |
| generated = [] | |
| finish_reason = "length" | |
| decode_steps = 0 | |
| for step in range(config.max_new_tokens): | |
| row = _process_logits(logits[0], generated, config, constraint) | |
| if strategy == "greedy": | |
| token = int(torch.argmax(row).item()) | |
| else: | |
| row = _filter_sampling_logits(row, config) | |
| token = int(torch.multinomial(torch.softmax(row, dim=-1), 1, generator=generator).item()) | |
| generated.append(token) | |
| if _is_stopped(generated, stop_ids, stop_sequences): | |
| finish_reason = "stop" | |
| break | |
| if step + 1 < config.max_new_tokens: | |
| last = torch.tensor([[token]], dtype=torch.long, device=device) | |
| logits = model.decode_step(last, cache, prompt_len + step) | |
| decode_steps += 1 | |
| return GenerationResult( | |
| text=_decode(generated, tokenizer, stop_ids, stop_sequences), | |
| token_ids=generated, | |
| finish_reason=finish_reason, | |
| prompt_tokens=prompt_len, | |
| completion_tokens=len(generated), | |
| decode_steps=decode_steps, | |
| completed_beams=0, | |
| ) | |
| def _beam_generate(model, input_ids, tokenizer, special, config): | |
| device = model.lm_head.weight.device | |
| stop_ids = {special["eos"], special["im_end"]} | |
| stop_sequences = [list(sequence) for sequence in config.stop_sequences] | |
| prompt_len = len(input_ids) | |
| cache = model.init_cache( | |
| config.beam_size, | |
| min(model.max_seq_len, prompt_len + config.max_new_tokens), | |
| device, | |
| ) | |
| prompt = torch.tensor([input_ids], dtype=torch.long, device=device) | |
| with torch.inference_mode(): | |
| first_logits, present = model.prefill(prompt, kv_cache=None) | |
| for layer, (key, value) in enumerate(present): | |
| cache[layer][0][:, :, :prompt_len].copy_(key.expand(config.beam_size, -1, -1, -1)) | |
| cache[layer][1][:, :, :prompt_len].copy_(value.expand(config.beam_size, -1, -1, -1)) | |
| first_log_probs = torch.log_softmax(first_logits[0], dim=-1) | |
| top_log_probs, top_tokens = torch.topk( | |
| first_log_probs, | |
| min(config.beam_size, first_log_probs.numel()), | |
| ) | |
| alive, completed = [], [] | |
| for log_prob, token in zip(top_log_probs.tolist(), top_tokens.tolist()): | |
| candidate = {"generated": [int(token)], "log_prob": float(log_prob), "source": 0} | |
| target = completed if _is_stopped(candidate["generated"], stop_ids, stop_sequences) else alive | |
| target.append(candidate) | |
| cache_len = prompt_len | |
| decode_steps = 0 | |
| stable_count = 0 | |
| last_best_completed = None | |
| finish_reason = "length" | |
| while alive and max(len(item["generated"]) for item in alive) < config.max_new_tokens: | |
| last_tokens = torch.tensor( | |
| [[candidate["generated"][-1]] for candidate in alive], | |
| dtype=torch.long, | |
| device=device, | |
| ) | |
| logits = model.decode_step(last_tokens, cache, cache_len) | |
| cache_len += 1 | |
| decode_steps += 1 | |
| candidates = [] | |
| for beam_index, candidate in enumerate(alive): | |
| row = _process_logits(logits[beam_index], candidate["generated"], config) | |
| log_probs = torch.log_softmax(row, dim=-1) | |
| values, tokens = torch.topk(log_probs, min(config.beam_size, log_probs.numel())) | |
| for value, token in zip(values.tolist(), tokens.tolist()): | |
| candidates.append({ | |
| "generated": candidate["generated"] + [int(token)], | |
| "log_prob": candidate["log_prob"] + float(value), | |
| "source": beam_index, | |
| }) | |
| candidates.sort( | |
| key=lambda item: _beam_score(item, config.length_penalty), | |
| reverse=True, | |
| ) | |
| next_alive = [] | |
| for candidate in candidates: | |
| if _is_stopped(candidate["generated"], stop_ids, stop_sequences): | |
| completed.append(candidate) | |
| elif len(next_alive) < config.beam_size: | |
| next_alive.append(candidate) | |
| if len(next_alive) >= config.beam_size and len(completed) >= config.beam_size: | |
| break | |
| if completed: | |
| best_completed_score = max( | |
| _beam_score(item, config.length_penalty) for item in completed | |
| ) | |
| if last_best_completed is None or best_completed_score > last_best_completed + 1e-12: | |
| last_best_completed = best_completed_score | |
| stable_count = 0 | |
| else: | |
| stable_count += 1 | |
| if ( | |
| config.stable_steps is not None | |
| and len(completed) >= config.beam_size | |
| and stable_count >= config.stable_steps | |
| ): | |
| best_alive_score = max( | |
| (_beam_score(item, config.length_penalty) for item in next_alive), | |
| default=float("-inf"), | |
| ) | |
| if best_completed_score >= best_alive_score: | |
| finish_reason = "stable_beam" | |
| alive = next_alive | |
| break | |
| if not next_alive: | |
| finish_reason = "all_beams_finished" | |
| break | |
| sources_list = [candidate["source"] for candidate in next_alive] | |
| if sources_list != list(range(len(sources_list))): | |
| sources = torch.tensor(sources_list, dtype=torch.long, device=device) | |
| for key, value in cache: | |
| selected_key = key.index_select(0, sources)[:, :, :cache_len].clone() | |
| selected_value = value.index_select(0, sources)[:, :, :cache_len].clone() | |
| key[:len(next_alive), :, :cache_len].copy_(selected_key) | |
| value[:len(next_alive), :, :cache_len].copy_(selected_value) | |
| alive = next_alive | |
| pool = completed + alive | |
| if not pool: | |
| return GenerationResult("", [], "empty", prompt_len, 0, decode_steps, len(completed)) | |
| best = max(pool, key=lambda item: _beam_score(item, config.length_penalty)) | |
| return GenerationResult( | |
| text=_decode(best["generated"], tokenizer, stop_ids, stop_sequences), | |
| token_ids=list(best["generated"]), | |
| finish_reason=finish_reason, | |
| prompt_tokens=prompt_len, | |
| completion_tokens=len(best["generated"]), | |
| decode_steps=decode_steps, | |
| completed_beams=len(completed), | |
| ) | |
| def generate(model, user_input=None, tokenizer=None, *, messages=None, system=None, instruction=None, history=None, config=None, constraint: TokenConstraint | None = None, return_result=False): | |
| """Generate a response with beam search, greedy decoding, or sampling.""" | |
| if tokenizer is None: | |
| raise ValueError("tokenizer is required") | |
| config = config or GenerationConfig() | |
| validate_generation_config(config) | |
| strategy = config.strategy | |
| if strategy == "auto": | |
| strategy = "greedy" if constraint is not None else "beam" | |
| if strategy == "beam" and constraint is not None: | |
| raise ValueError("Beam strategy does not support token constraints") | |
| input_ids, special = _build_input( | |
| user_input, | |
| tokenizer, | |
| max_seq_len=model.max_seq_len, | |
| config=config, | |
| system=system, | |
| instruction=instruction, | |
| history=history, | |
| messages=messages, | |
| ) | |
| if strategy == "beam": | |
| result = _beam_generate(model, input_ids, tokenizer, special, config) | |
| else: | |
| result = _single_path_generate( | |
| model, | |
| input_ids, | |
| tokenizer, | |
| special, | |
| config, | |
| strategy, | |
| constraint, | |
| ) | |
| return result if return_result else result.text | |