Instructions to use Timegravity/tamil-lm-2b-base with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Timegravity/tamil-lm-2b-base with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="Timegravity/tamil-lm-2b-base") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("Timegravity/tamil-lm-2b-base") model = AutoModelForMultimodalLM.from_pretrained("Timegravity/tamil-lm-2b-base", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.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=256) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Timegravity/tamil-lm-2b-base with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Timegravity/tamil-lm-2b-base" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Timegravity/tamil-lm-2b-base", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/Timegravity/tamil-lm-2b-base
- SGLang
How to use Timegravity/tamil-lm-2b-base 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 "Timegravity/tamil-lm-2b-base" \ --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": "Timegravity/tamil-lm-2b-base", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'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 "Timegravity/tamil-lm-2b-base" \ --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": "Timegravity/tamil-lm-2b-base", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use Timegravity/tamil-lm-2b-base with Docker Model Runner:
docker model run hf.co/Timegravity/tamil-lm-2b-base
Upload tools/family_safe.py with huggingface_hub
Browse files- tools/family_safe.py +189 -0
tools/family_safe.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Family-safe hashed lexicon backstop (ruling 2026-09-08). The plain lexicon is private; this module
|
| 2 |
+
ships only salted SHA-256 hashes (data/lexicon.hashed) and checks model output against them.
|
| 3 |
+
|
| 4 |
+
Normalisation: lowercase; leetspeak digits/symbols back to letters; Tamil script to canonical roman via
|
| 5 |
+
retrieval.roman.to_roman; letters only (no doubled-letter collapse, so butt is not but; doubled forms are enumerated
|
| 6 |
+
as variants). Tamil script stays in a separate namespace and is never transliterated for matching. check() tests every token on its own
|
| 7 |
+
(never joined with its neighbours), every 2-word window against two-word entries only, and runs of three or more
|
| 8 |
+
single-character tokens joined among themselves (spaced-out words). Hash lines carry a "1:" (single-word) or "2:"
|
| 9 |
+
(two-word) prefix; entries of three or more words are not part of the backstop (the classifier handles them).
|
| 10 |
+
|
| 11 |
+
FAMILY_SAFE=1 by default; a developer sets FAMILY_SAFE=0 to disable.
|
| 12 |
+
python family_safe.py --build data/private/lexicon/lexicon.jsonl (regenerates data/lexicon.hashed)
|
| 13 |
+
python family_safe.py --selftest
|
| 14 |
+
"""
|
| 15 |
+
import hashlib, json, os, re, sys, time
|
| 16 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 17 |
+
from retrieval.roman import to_roman
|
| 18 |
+
|
| 19 |
+
HASHED = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "lexicon.hashed")
|
| 20 |
+
SEVERITY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "lexicon.severity") # "<hash> <severity>" per line, same salt
|
| 21 |
+
SEVERE = ("slur", "sexual")
|
| 22 |
+
SALT_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "lexicon.salt")
|
| 23 |
+
_LEET = str.maketrans({"0": "o", "1": "i", "3": "e", "4": "a", "5": "s", "7": "t", "@": "a", "$": "s", "!": "i"})
|
| 24 |
+
_SET = None; _SET2 = None; _SALT = None; _SEV = None
|
| 25 |
+
|
| 26 |
+
def _salt():
|
| 27 |
+
global _SALT
|
| 28 |
+
if _SALT is None:
|
| 29 |
+
_SALT = open(SALT_FILE).read().strip() if os.path.exists(SALT_FILE) else ""
|
| 30 |
+
return _SALT
|
| 31 |
+
|
| 32 |
+
def normalise(s):
|
| 33 |
+
"""Two namespaces so Tamil words never collide with English/roman entries through transliteration:
|
| 34 |
+
Tamil script -> "ta:" + Tamil letters only (repeated signs collapsed); everything else -> "rom:" + lowercase,
|
| 35 |
+
leetspeak reversed, letters only, repeated letters collapsed. Spelling equivalences (th/dh, zh/l, long
|
| 36 |
+
vowels) come from the enumerated variants, not from a lossy skeleton."""
|
| 37 |
+
s = (s or "").lower().translate(_LEET)
|
| 38 |
+
if re.search(r"[-]", s):
|
| 39 |
+
s = re.sub(r"[^-]+", "", s)
|
| 40 |
+
return "ta:" + s
|
| 41 |
+
s = re.sub(r"[^a-z]+", "", s)
|
| 42 |
+
return "rom:" + s # no doubled-letter collapse: butt must not equal but; doubled forms are enumerated as variants
|
| 43 |
+
|
| 44 |
+
_SET3 = set() # context-only entries, consulted by check_context() only
|
| 45 |
+
|
| 46 |
+
def _h(norm):
|
| 47 |
+
return hashlib.sha256((_salt() + ":" + norm).encode("utf-8")).hexdigest()
|
| 48 |
+
|
| 49 |
+
def load(path=HASHED):
|
| 50 |
+
global _SET, _SET2, _SET3, _SEV
|
| 51 |
+
_SET = set(); _SET2 = set(); _SET3 = set(); _SEV = {}
|
| 52 |
+
if os.path.exists(path):
|
| 53 |
+
for l in open(path, encoding="utf-8"):
|
| 54 |
+
l = l.strip()
|
| 55 |
+
if not l or l.startswith("#"): continue
|
| 56 |
+
if l.startswith("2:"): _SET2.add(l[2:])
|
| 57 |
+
if l.startswith("3:"): _SET3.add(l[2:])
|
| 58 |
+
elif l.startswith("1:"): _SET.add(l[2:])
|
| 59 |
+
else: _SET.add(l) # v1 file without prefixes: single-word semantics
|
| 60 |
+
sev_path = path.replace(".hashed", ".severity") if path.endswith(".hashed") else SEVERITY
|
| 61 |
+
if os.path.exists(sev_path):
|
| 62 |
+
for l in open(sev_path, encoding="utf-8"):
|
| 63 |
+
p = l.strip().split()
|
| 64 |
+
if len(p) == 2 and not l.startswith("#"): _SEV[p[0]] = p[1]
|
| 65 |
+
return _SET
|
| 66 |
+
|
| 67 |
+
def severity_of(norm):
|
| 68 |
+
"""Severity of a normalised form (slur | sexual | profanity | mild), or None when unknown."""
|
| 69 |
+
if _SET is None: load()
|
| 70 |
+
return (_SEV or {}).get(_h(norm))
|
| 71 |
+
|
| 72 |
+
def normalise_words(words):
|
| 73 |
+
"""Normalised form of a two-word entry: each word normalised on its own, joined by one space."""
|
| 74 |
+
return " ".join(normalise(w) for w in words)
|
| 75 |
+
|
| 76 |
+
def check_context(text):
|
| 77 |
+
"""Like check() but against the context-only entries (all severities): used only when the request asks to
|
| 78 |
+
spell, repeat, translate or explain a word, where reproducing it is the point of the request."""
|
| 79 |
+
global _SET, _SET2
|
| 80 |
+
saved = (_SET, _SET2)
|
| 81 |
+
try:
|
| 82 |
+
_SET, _SET2 = _SET3, set()
|
| 83 |
+
return [dict(h, severity="context") for h in check(text)]
|
| 84 |
+
finally:
|
| 85 |
+
_SET, _SET2 = saved
|
| 86 |
+
|
| 87 |
+
def is_enabled():
|
| 88 |
+
return os.environ.get("FAMILY_SAFE", "1") != "0"
|
| 89 |
+
|
| 90 |
+
def _tokens(text):
|
| 91 |
+
"""Whitespace and punctuation split tokens; an apostrophe splits too, so "Who 're" is who / re, never whore."""
|
| 92 |
+
return re.findall(r"[a-zA-Z0-9@$!]+|[-]+", text or "")
|
| 93 |
+
|
| 94 |
+
def check(text, min_len=3):
|
| 95 |
+
"""Return the offending spans (deduplicated) or []; empty when disabled or no lexicon loaded.
|
| 96 |
+
Single tokens are tested on their own (never concatenated with neighbours); 2-word windows are tested only
|
| 97 |
+
against two-word entries; runs of three or more single-character tokens are joined among themselves."""
|
| 98 |
+
if not is_enabled(): return []
|
| 99 |
+
if _SET is None: load()
|
| 100 |
+
if not _SET and not _SET2: return []
|
| 101 |
+
toks = _tokens(text)
|
| 102 |
+
hits = []
|
| 103 |
+
seen = set()
|
| 104 |
+
def test(norm, span, kind, table):
|
| 105 |
+
if len(norm) - norm.index(":") - 1 < min_len: return
|
| 106 |
+
h = _h(norm)
|
| 107 |
+
if h in table and norm not in seen:
|
| 108 |
+
seen.add(norm); hits.append({"span": span, "kind": kind, "severity": (_SEV or {}).get(h)})
|
| 109 |
+
for tk in toks:
|
| 110 |
+
test(normalise(tk), tk, "token", _SET)
|
| 111 |
+
if _SET2:
|
| 112 |
+
for i in range(len(toks) - 1):
|
| 113 |
+
test(normalise_words((toks[i], toks[i + 1])), toks[i] + " " + toks[i + 1], "bigram", _SET2)
|
| 114 |
+
run = []
|
| 115 |
+
for tk in toks + [""]:
|
| 116 |
+
if len(tk) == 1: run.append(tk)
|
| 117 |
+
else:
|
| 118 |
+
if len(run) >= 3: test(normalise("".join(run)), " ".join(run), "spaced", _SET)
|
| 119 |
+
run = []
|
| 120 |
+
return hits
|
| 121 |
+
|
| 122 |
+
def fallback_text(lang="ta"):
|
| 123 |
+
return {"ta": "மன்னிக்கவும், அதை நான் சொல்ல முடியாது. வேறு எதிலாவது உதவட்டுமா?",
|
| 124 |
+
"tanglish": "Sorry, adha naan solla mudiyaadhu. Vera edhavadhu help pannattuma?",
|
| 125 |
+
"en": "Sorry, I cannot say that. Can I help with something else?"}.get(lang if lang in ("ta", "tanglish", "en") else "en")
|
| 126 |
+
|
| 127 |
+
RANK = {"slur": 3, "sexual": 2, "profanity": 1, "mild": 0}
|
| 128 |
+
|
| 129 |
+
def build(lexicon_path, out=HASHED):
|
| 130 |
+
if not os.path.exists(SALT_FILE):
|
| 131 |
+
open(SALT_FILE, "w").write(os.urandom(16).hex() + "\n")
|
| 132 |
+
hs1 = set(); hs2 = set(); hs3 = set(); n_entries = 0; sev = {}; skipped3 = 0
|
| 133 |
+
def keep(s, h):
|
| 134 |
+
if RANK.get(s, 0) >= RANK.get(sev.get(h, "mild"), -1): sev[h] = s # worst severity wins for shared forms
|
| 135 |
+
for l in open(lexicon_path, encoding="utf-8"):
|
| 136 |
+
e = json.loads(l); n_entries += 1
|
| 137 |
+
ctx_only = not e.get("standalone", True) # context-dependent entries: a separate "3:" set consulted only when the request asks to spell / repeat / translate / explain the word
|
| 138 |
+
s = e.get("severity", "mild")
|
| 139 |
+
for v in [e["term"]] + e.get("variants", []):
|
| 140 |
+
for alt in str(v).split("|"):
|
| 141 |
+
forms = [alt.split()]
|
| 142 |
+
if any(c in alt for c in "-_"):
|
| 143 |
+
forms = [alt.replace("-", " ").replace("_", " ").split(), [alt.replace("-", "").replace("_", "")]]
|
| 144 |
+
for words in forms:
|
| 145 |
+
if not words: continue
|
| 146 |
+
if ctx_only:
|
| 147 |
+
if all(len(w) == 1 for w in words) and len(words) >= 3: words = ["".join(words)]
|
| 148 |
+
n = normalise(words[0]) if len(words) == 1 else (normalise_words(words) if len(words) == 2 else None)
|
| 149 |
+
if n and len(n[n.index(":") + 1:].replace(" ", "")) >= 3: hs3.add(_h(n))
|
| 150 |
+
continue
|
| 151 |
+
if all(len(w) == 1 for w in words) and len(words) >= 3:
|
| 152 |
+
words = ["".join(words)] # spaced-out spelling: the joined single word
|
| 153 |
+
if len(words) == 1:
|
| 154 |
+
n = normalise(words[0]); body = n[n.index(":") + 1:]
|
| 155 |
+
if (n.startswith("ta:") and len(body) >= 3) or (n.startswith("rom:") and len(body) >= 4) or (n.startswith("rom:") and len(body) == 3 and s in ("slur", "sexual", "profanity")):
|
| 156 |
+
h = _h(n); hs1.add(h); keep(s, h)
|
| 157 |
+
elif len(words) == 2:
|
| 158 |
+
n = normalise_words(words)
|
| 159 |
+
parts = [p[p.index(":") + 1:] for p in n.split(" ")]
|
| 160 |
+
if all(len(p) >= 2 for p in parts) or (any(c in alt for c in "-_") and all(len(p) >= 1 for p in parts) and sum(len(p) for p in parts) >= 4):
|
| 161 |
+
h = _h(n); hs2.add(h); keep(s, h)
|
| 162 |
+
else:
|
| 163 |
+
skipped3 += 1
|
| 164 |
+
with open(out, "w", encoding="utf-8") as f:
|
| 165 |
+
f.write(f"# family-safe lexicon hashes v3 {time.strftime('%Y-%m-%d')} n1={len(hs1)} n2={len(hs2)} n3={len(hs3)} (1: single word, 2: two words, 3: context-only, repeat/spell/translate requests only)\n")
|
| 166 |
+
for h in sorted(hs1): f.write("1:" + h + "\n")
|
| 167 |
+
for h in sorted(hs2): f.write("2:" + h + "\n")
|
| 168 |
+
for h in sorted(hs3): f.write("3:" + h + "\n")
|
| 169 |
+
sev_out = out.replace(".hashed", ".severity") if out.endswith(".hashed") else SEVERITY
|
| 170 |
+
with open(sev_out, "w", encoding="utf-8") as f:
|
| 171 |
+
f.write(f"# family-safe lexicon severities v2 {time.strftime('%Y-%m-%d')} n={len(sev)} (hash severity; same salt as lexicon.hashed)\n")
|
| 172 |
+
for h in sorted(sev): f.write(f"{h} {sev[h]}\n")
|
| 173 |
+
print(f"wrote {out}: {len(hs1)} single-word + {len(hs2)} two-word hashes from {n_entries} entries ({skipped3} forms of 3+ words skipped); {sev_out}: {len(sev)} severities")
|
| 174 |
+
|
| 175 |
+
def selftest():
|
| 176 |
+
load()
|
| 177 |
+
known = ["pundai", "punda", "PUNDAI", "p u n d a i", "thevidiya", "தேவிடியா", "thevdiya", "gaandu", "gandu", "mayiru", "thayoli", "koodhi", "sunni", "otha", "loosu", "பொட்ட", "f u c k", "fuck", "sh1t", "shaved pussy"]
|
| 178 |
+
benign = ["வணக்கம், இன்று நான் உங்களுக்கு எப்படி உதவ முடியும்?", "திருக்குறள் 42 என்ன?", "ரசம் செய்முறை சொல்லுங்கள்", "Linux la oru process ah kill panradhu epdi?", "பொங்கல் பண்டிகை பற்றி சொல்லுங்கள்",
|
| 179 |
+
"naalai kaalai sandhippom", "enakku Tamil la konjam thaan theriyum", "How does paracetamol work?", "The cat sat on the mat.", "தமிழ் இலக்கியம் மிகவும் பழமையானது", "kovai la mazhai peyudhu", "அகர முதல எழுத்தெல்லாம் ஆதி பகவன் முதற்றே உலகு",
|
| 180 |
+
"pandu vilaiyaadu", "school ku leave letter", "amma appa thambi", "sandhosham", "punnagai", "kanmani", "Can you translate this to Tamil?", "பாரதியார் பாடல்கள் 155",
|
| 181 |
+
"Who're you?", "who 're you", "Who 're good indeed, on earth begirt by ocean's gruesome tide", "Mr S Mutt wrote the novel", "S mutt", "the class assembles at nine", "cum laude", "a s s e m b l y line"]
|
| 182 |
+
fp = [b for b in benign if check(b)]; fn = [k for k in known if not check(k)]
|
| 183 |
+
print(f"selftest: known hit {len(known)-len(fn)}/{len(known)} (missed {fn}); benign clean {len(benign)-len(fp)}/{len(benign)} (false positives {fp})")
|
| 184 |
+
return not fn and not fp
|
| 185 |
+
|
| 186 |
+
if __name__ == "__main__":
|
| 187 |
+
if len(sys.argv) > 2 and sys.argv[1] == "--build": build(sys.argv[2])
|
| 188 |
+
elif len(sys.argv) > 1 and sys.argv[1] == "--selftest": sys.exit(0 if selftest() else 1)
|
| 189 |
+
else: print(__doc__)
|