Text Generation
Transformers
English
structured-generation
parallel-decoding
constrained-decoding
classification
json
Instructions to use shreyansh26/Qwen-2.5-1B-RLCD with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use shreyansh26/Qwen-2.5-1B-RLCD with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="shreyansh26/Qwen-2.5-1B-RLCD")# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("shreyansh26/Qwen-2.5-1B-RLCD", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use shreyansh26/Qwen-2.5-1B-RLCD with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "shreyansh26/Qwen-2.5-1B-RLCD" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "shreyansh26/Qwen-2.5-1B-RLCD", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/shreyansh26/Qwen-2.5-1B-RLCD
- SGLang
How to use shreyansh26/Qwen-2.5-1B-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 "shreyansh26/Qwen-2.5-1B-RLCD" \ --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": "shreyansh26/Qwen-2.5-1B-RLCD", "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 "shreyansh26/Qwen-2.5-1B-RLCD" \ --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": "shreyansh26/Qwen-2.5-1B-RLCD", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use shreyansh26/Qwen-2.5-1B-RLCD with Docker Model Runner:
docker model run hf.co/shreyansh26/Qwen-2.5-1B-RLCD
Download tests/test_engine.py from shreyansh26/Qwen-2.5-1B-RLCD: direct link, hf CLI and curl.
- Browser
- Download file 17.3 kB
-
https://huggingface.co/shreyansh26/Qwen-2.5-1B-RLCD/resolve/main/tests/test_engine.py
- Command line
-
hf download hf://shreyansh26/Qwen-2.5-1B-RLCD/tests/test_engine.py
-
curl -L -o test_engine.py https://huggingface.co/shreyansh26/Qwen-2.5-1B-RLCD/resolve/main/tests/test_engine.py
17.3 kB
| import json | |
| from concurrent.futures import ThreadPoolExecutor | |
| import pytest | |
| import torch | |
| from transformers import BatchEncoding, Qwen2Config, Qwen2ForCausalLM | |
| from core import engine | |
| from core.schema import StructuredSchema | |
| class Tokenizer: | |
| pad_token_id = 0 | |
| eos_token_id = 1 | |
| def encode(self, text, **kwargs): | |
| return [ord(c) for c in text] | |
| def decode(self, ids, **kwargs): | |
| return "".join(chr(i) for i in ids) | |
| def __call__(self, text, **kwargs): | |
| ids = torch.tensor([self.encode(text)]) | |
| return BatchEncoding({"input_ids": ids, "attention_mask": torch.ones_like(ids)}) | |
| def tiny_engine(monkeypatch): | |
| torch.manual_seed(7) | |
| model = Qwen2ForCausalLM(Qwen2Config(vocab_size=128, hidden_size=32, | |
| intermediate_size=32, num_hidden_layers=1, num_attention_heads=2, | |
| num_key_value_heads=1, max_position_embeddings=4096, | |
| eos_token_id=1, pad_token_id=0)).eval() | |
| tokenizer = Tokenizer() | |
| monkeypatch.setattr(engine, "get_engine", lambda: (model, tokenizer)) | |
| return model, tokenizer | |
| def test_invalid_schema(spec): | |
| with pytest.raises(ValueError): | |
| StructuredSchema(spec) | |
| def test_cached_scores_match_full_forward(tiny_engine): | |
| model, tokenizer = tiny_engine | |
| prefix = tokenizer("Context", return_tensors="pt") | |
| candidates = [tokenizer.encode(s) for s in ['a', 'ab"', 'abc"', 'z"'] * 5] | |
| with torch.inference_mode(): | |
| output = model(**prefix, use_cache=True) | |
| scores, passes = engine._candidate_scores(model, output.past_key_values, | |
| output.logits[0, -1], candidates, 0) | |
| for i, tokens in enumerate(candidates): | |
| ids = torch.tensor([prefix.input_ids[0].tolist() + tokens]) | |
| logits = model(ids, use_cache=False).logits[0].float().log_softmax(-1) | |
| expected = sum(logits[prefix.input_ids.shape[1] - 1 + j, t] | |
| for j, t in enumerate(tokens)) | |
| torch.testing.assert_close(scores[i], expected, atol=1e-5, rtol=1e-5) | |
| assert passes == 2 | |
| def test_batched_padding_and_collision(tiny_engine): | |
| model, tokenizer = tiny_engine | |
| schema = StructuredSchema({"x": {"type": "boolean"}, | |
| "long_field_name": {"type": "enum", "choices": ["ab", "abc", "ad", "z"]}}) | |
| result = engine.run_parallel_generation("example", schema) | |
| assert result["schema_match"] | |
| assert isinstance(result["parsed_json"]["x"]["value"], bool) | |
| assert result["parsed_json"]["long_field_name"]["value"] in schema["long_field_name"].choices | |
| assert result["sequential_forward_passes"] == 3 | |
| # Compare every probability against an independent, unpadded full forward. | |
| meta = schema.compile_parallel_metadata(tokenizer) | |
| prefix = (f"<|im_start|>system\nClassify JSON attributes:\n{schema.to_parallel_schema_str()}" | |
| "<|im_end|>\n<|im_start|>user\nexample<|im_end|>\n" | |
| "<|im_start|>assistant\n{\n") | |
| with torch.inference_mode(): | |
| for i, (name, field) in enumerate(schema.fields.items()): | |
| ids = tokenizer.encode(prefix) + meta["suffixes_batch"][i, :meta["suffix_lengths"][i]].tolist() | |
| scores = [] | |
| for tokens in meta["candidate_sequences"][i]: | |
| out = model(torch.tensor([ids + tokens]), use_cache=False).logits[0].float() | |
| if meta["has_collisions"][i]: | |
| lp = out.log_softmax(-1) | |
| scores.append(sum(lp[len(ids) - 1 + j, t] for j, t in enumerate(tokens))) | |
| else: | |
| scores.append(out[len(ids) - 1, tokens[0]]) | |
| expected = torch.stack(scores).softmax(-1).tolist() | |
| actual = {x["choice"]: x["probability"] for x in result["field_telemetry"][name]["top_choices"]} | |
| for c, p in zip(field.choices, expected): | |
| assert actual[c] == pytest.approx(p, abs=0.0001) | |
| def test_stream_resumes_on_different_threads_and_releases_lock(tiny_engine): | |
| schema = StructuredSchema({"x": {"type": "boolean"}}) | |
| iterator = engine.stream_naive_generation("test", schema, max_tokens=3, temperature=0) | |
| with ThreadPoolExecutor(1) as first, ThreadPoolExecutor(1) as second: | |
| a = first.submit(next, iterator).result(timeout=10) | |
| b = second.submit(next, iterator).result(timeout=10) | |
| second.submit(iterator.close).result(timeout=10) | |
| assert a["type"] == b["type"] == "token" | |
| assert engine._gpu_lock.acquire(timeout=1) | |
| engine._gpu_lock.release() | |
| def test_json_validation(): | |
| schema = StructuredSchema({"x": {"type": "boolean"}}) | |
| for text in ['[]', '{"x":"true"}', '{"x":true,"extra":1}', '{}']: | |
| assert not engine._naive_result(text, schema, 1, 1, 1)["schema_match"] | |
| assert engine._naive_result('{"x":true}', schema, 1, 1, 1)["schema_match"] | |
| def test_server_routes(tiny_engine, monkeypatch): | |
| from fastapi.testclient import TestClient | |
| from server import app as server | |
| monkeypatch.setattr(server, "get_engine", lambda: tiny_engine) | |
| payload = {"context": "test", "schema": {"x": {"type": "boolean"}}, | |
| "max_tokens": 2, "temperature": 0} | |
| with TestClient(server.app) as client: | |
| assert client.get("/").status_code == 200 | |
| assert len(client.get("/api/presets").json()) == 4 | |
| for route in ["run-parallel", "run-rlcd", "run-tree", "run-naive", "compare"]: | |
| response = client.post("/api/" + route, json=payload) | |
| assert response.status_code == 200, response.text | |
| if route == "compare": | |
| data = response.json() | |
| assert {"tree", "parallel", "naive"} <= data.keys() | |
| assert data["tree"]["sequential_forward_passes"] == 2 | |
| assert data["tree_speedup_multiplier"] == round(data["naive"]["elapsed_ms"] / | |
| max(data["tree"]["elapsed_ms"], 1), 1) | |
| response = client.post("/api/stream-naive", json=payload) | |
| events = [json.loads(line[6:]) for line in response.text.splitlines() if line.startswith("data: ")] | |
| assert events[-1]["type"] == "done" | |
| assert all(e["type"] != "error" for e in events) | |
| assert client.post("/api/run-parallel", json={**payload, "schema": {}}).status_code == 400 | |
| assert client.post("/api/run-tree", json={**payload, "schema": {}}).status_code == 400 | |
| assert client.post("/api/run-tree", json={**payload, "temperature": -1}).status_code == 422 | |
| assert client.post("/api/run-naive", json={**payload, "temperature": -1}).status_code == 422 | |
| def test_tree_route_temperature_and_errors(tiny_engine, monkeypatch): | |
| from fastapi.testclient import TestClient | |
| from server import app as server | |
| monkeypatch.setattr(server, "get_engine", lambda: tiny_engine) | |
| temperatures = [] | |
| def tree(context, schema, temperature): | |
| temperatures.append(temperature) | |
| if context == "fail": | |
| raise ValueError("Tree unavailable") | |
| return {"mode": "parallel_constrained_tree"} | |
| monkeypatch.setattr(server, "run_parallel_generation_tree", tree) | |
| payload = {"context": "test", "schema": {"x": {"type": "boolean"}}} | |
| with TestClient(server.app) as client: | |
| assert client.post("/api/run-tree", json=payload).status_code == 200 | |
| assert client.post("/api/run-tree", json={**payload, "temperature": 0}).status_code == 200 | |
| response = client.post("/api/run-tree", json={**payload, "context": "fail"}) | |
| assert response.status_code == 400 | |
| assert response.json()["detail"] == "Tree unavailable" | |
| assert temperatures == [1.0, 0, 1.0] | |
| def test_tree_scores_match_independent_forwards(tiny_engine, monkeypatch, backend, temperature, device): | |
| model, tokenizer = tiny_engine | |
| model.to(device) | |
| model.set_attn_implementation(backend) | |
| schema = StructuredSchema({ | |
| "payment_status": {"choices": ["PAYMENT", "PAYMENT_FAILED", "PAYMENT_PENDING"]}, | |
| "payment_state": {"choices": ["a", "abc", "abd", "z"]}, | |
| "payment_escalate": {"type": "boolean"}, | |
| "unique": {"choices": ["x", "yyy"]}, | |
| 'quoted"key': {"choices": ['a"b', "a\\b", "a\nb"]}, | |
| "only": {"choices": ["one"]}, | |
| }) | |
| forward = model.forward | |
| calls = [] | |
| def record_forward(*args, **kwargs): | |
| if "past_key_values" in kwargs: | |
| assert model.config._attn_implementation == "flex_attention" | |
| output = forward(*args, **kwargs) | |
| calls.append((kwargs, output)) | |
| return output | |
| monkeypatch.setattr(model, "forward", record_forward) | |
| result = engine.run_parallel_generation_tree("example", schema, temperature) | |
| assert model.config._attn_implementation == backend | |
| assert len(calls) == result["sequential_forward_passes"] == 2 | |
| assert result["total_tokens_generated"] == 0 | |
| assert result["scoring"] == "mean_token_log_probability" | |
| assert result["schema_match"] and result["is_valid_json"] | |
| assert isinstance(result["parsed_json"]["payment_escalate"]["value"], bool) | |
| assert result["parsed_json"]["only"] == {"value": "one", "prob": 1.0} | |
| prefix = calls[0][0]["input_ids"][0].tolist() | |
| tree_args, tree_output = calls[1] | |
| assert tree_args["input_ids"].shape[0] == 1 | |
| assert tree_args["past_key_values"] is calls[0][1].past_key_values | |
| cache = tree_output.past_key_values | |
| assert cache.get_seq_length() == len(prefix) + result["tree_nodes"] | |
| for layer in cache.layers: | |
| assert layer.keys.shape[0] == layer.values.shape[0] == 1 | |
| # Recover each scoring context independently from the mask, then compare | |
| # full-vocabulary logits and complete candidate probabilities with ordinary | |
| # causal forwards. This catches sibling leakage and incorrect RoPE depths. | |
| tree_ids = tree_args["input_ids"][0].tolist() | |
| from torch.nn.attention.flex_attention import BlockMask | |
| block_mask = tree_args["attention_mask"] | |
| assert isinstance(block_mask, BlockMask) | |
| mask = block_mask.mask_mod(torch.tensor(0, device=device), torch.tensor(0, device=device), | |
| torch.arange(len(tree_ids), device=device)[:, None], | |
| torch.arange(len(prefix) + len(tree_ids), device=device)[None, :]) | |
| assert mask[:, :len(prefix)].all() | |
| context_rows = {} | |
| for row, node in enumerate(tree_args["logits_to_keep"].tolist()): | |
| visible = torch.where(mask[node, len(prefix):])[0].tolist() | |
| path = tuple(tree_ids[j] for j in visible) | |
| context_rows[path] = row | |
| assert tree_args["position_ids"][0, node] == len(prefix) + len(path) - 1 | |
| with torch.inference_mode(): | |
| for i, (name, field) in enumerate(schema.fields.items()): | |
| field_prompt = f" {json.dumps(name)}: " + ('"' if field.field_type != "boolean" else '') | |
| suffix = tokenizer.encode(field_prompt) | |
| candidate_texts = [c + ',' if field.field_type == "boolean" else | |
| json.dumps(c, ensure_ascii=False)[1:] for c in field.choices] | |
| expected_scores, tree_scores = [], [] | |
| for candidate in candidate_texts: | |
| tokens = tokenizer.encode(candidate) | |
| ids = prefix + suffix + tokens | |
| logits = forward(torch.tensor([ids], device=device), use_cache=False).logits[0].float() | |
| log_probs = logits.log_softmax(-1) | |
| expected_scores.append(sum(log_probs[len(prefix) + len(suffix) - 1 + j, token] | |
| for j, token in enumerate(tokens))) | |
| score = 0 | |
| for j, token in enumerate(tokens): | |
| row = context_rows[tuple(suffix + tokens[:j])] | |
| actual = tree_output.logits[0, row].float() | |
| torch.testing.assert_close(actual, logits[len(prefix) + len(suffix) - 1 + j], | |
| atol=1e-5, rtol=1e-5) | |
| score += actual.log_softmax(-1)[token] | |
| tree_scores.append(score) | |
| torch.testing.assert_close(torch.stack(tree_scores), torch.stack(expected_scores), | |
| atol=1e-5, rtol=1e-5) | |
| lengths = torch.tensor([len(tokenizer.encode(c)) for c in candidate_texts], device=device) | |
| probabilities = (torch.stack(expected_scores) / lengths / max(temperature, 1e-4)).softmax(-1).tolist() | |
| actual = {c["choice"]: c["probability"] for c in result["field_telemetry"][name]["top_choices"]} | |
| for choice, probability in zip(field.choices, probabilities): | |
| assert actual[choice] == pytest.approx(probability, abs=0.0001) | |
| def test_tree_shares_field_and_candidate_prefixes(tiny_engine, monkeypatch): | |
| model, tokenizer = tiny_engine | |
| schema = StructuredSchema({ | |
| "shared_name_a": {"choices": ["abc", "abd", "z"]}, | |
| "shared_name_b": {"choices": ["abc", "abd", "z"]}, | |
| }) | |
| paths = set() | |
| unshared_nodes = 0 | |
| for name, field in schema.fields.items(): | |
| suffix = tokenizer.encode(f' {json.dumps(name)}: "') | |
| candidates = [tokenizer.encode(json.dumps(c, ensure_ascii=False)[1:]) for c in field.choices] | |
| for tokens in candidates: | |
| path = suffix + tokens[:-1] | |
| paths.update(tuple(path[:j]) for j in range(1, len(path) + 1)) | |
| unshared_nodes += len(suffix) + sum(len(t) - 1 for t in candidates) | |
| tree = schema.compile_tree_metadata(tokenizer) | |
| assert tree["input_ids"].shape[1] == len(paths) < unshared_nodes | |
| assert schema.compile_tree_metadata(tokenizer) is tree | |
| assert schema.compile_tree_metadata(Tokenizer()) is not tree | |
| def no_copy(*args, **kwargs): | |
| pytest.fail("Tree inference must not copy or batch-repeat the prefix KV cache") | |
| from transformers import DynamicCache | |
| monkeypatch.setattr(DynamicCache, "__deepcopy__", no_copy, raising=False) | |
| monkeypatch.setattr(DynamicCache, "batch_repeat_interleave", no_copy) | |
| result = engine.run_parallel_generation_tree("test", schema) | |
| assert result["tree_nodes"] == len(paths) | |
| def test_tree_equal_token_likelihoods_do_not_favor_shorter_candidates(tiny_engine): | |
| model, _ = tiny_engine | |
| # Every token has probability 1/vocab_size. Different-length candidates | |
| # must tie under mean scoring, including when they share a value prefix. | |
| with torch.no_grad(): | |
| model.lm_head.weight.zero_() | |
| schema = StructuredSchema({ | |
| "plain": {"choices": ["A", "LONG_VALUE"]}, | |
| "shared": {"choices": ["PAYMENT_A", "PAYMENT_LONG_VALUE"]}, | |
| "flag": {"type": "boolean"}, | |
| }) | |
| result = engine.run_parallel_generation_tree("test", schema) | |
| for field in result["field_telemetry"].values(): | |
| assert [c["probability"] for c in field["top_choices"]] == [0.5, 0.5] | |
| assert result["sequential_forward_passes"] == 2 | |
| def test_tree_many_candidates_still_uses_two_passes(tiny_engine, monkeypatch): | |
| model, _ = tiny_engine | |
| schema = StructuredSchema({"category": {"choices": [f"VALUE_{i:03d}" for i in range(255)]}}) | |
| calls = [] | |
| handle = model.register_forward_pre_hook(lambda module, args: calls.append(1)) | |
| try: | |
| result = engine.run_parallel_generation_tree("test", schema) | |
| finally: | |
| handle.remove() | |
| assert len(calls) == result["sequential_forward_passes"] == 2 | |
| assert result["parsed_json"]["category"]["value"] in schema["category"].choices | |
| assert result["field_telemetry"]["category"]["cardinality"] == 255 | |
| def test_tree_rejects_negative_temperature(tiny_engine): | |
| with pytest.raises(ValueError, match="temperature"): | |
| engine.run_parallel_generation_tree("test", StructuredSchema({"x": {"type": "boolean"}}), -1) | |
| def test_tree_rejects_unsupported_attention(tiny_engine): | |
| model, _ = tiny_engine | |
| schema = StructuredSchema({"x": {"type": "boolean"}}) | |
| model.config.layer_types = ["sliding_attention"] | |
| with pytest.raises(ValueError, match="full attention"): | |
| engine.run_parallel_generation_tree("test", schema) | |
| def test_tree_restores_backend_on_error(tiny_engine, monkeypatch): | |
| model, _ = tiny_engine | |
| backend = model.config._attn_implementation | |
| forward = model.forward | |
| def fail_tree(*args, **kwargs): | |
| if "past_key_values" in kwargs: | |
| assert model.config._attn_implementation == "flex_attention" | |
| raise RuntimeError("tree forward failed") | |
| return forward(*args, **kwargs) | |
| monkeypatch.setattr(model, "forward", fail_tree) | |
| with pytest.raises(RuntimeError, match="tree forward failed"): | |
| engine.run_parallel_generation_tree("test", StructuredSchema({"x": {"type": "boolean"}})) | |
| assert model.config._attn_implementation == backend | |
| assert engine._gpu_lock.acquire(timeout=1) | |
| engine._gpu_lock.release() | |