Instructions to use briscoooe/tiny-cube-dagger with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use briscoooe/tiny-cube-dagger with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="briscoooe/tiny-cube-dagger")# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("briscoooe/tiny-cube-dagger") model = AutoModelForCausalLM.from_pretrained("briscoooe/tiny-cube-dagger", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use briscoooe/tiny-cube-dagger with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "briscoooe/tiny-cube-dagger" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "briscoooe/tiny-cube-dagger", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/briscoooe/tiny-cube-dagger
- SGLang
How to use briscoooe/tiny-cube-dagger 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 "briscoooe/tiny-cube-dagger" \ --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": "briscoooe/tiny-cube-dagger", "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 "briscoooe/tiny-cube-dagger" \ --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": "briscoooe/tiny-cube-dagger", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use briscoooe/tiny-cube-dagger with Docker Model Runner:
docker model run hf.co/briscoooe/tiny-cube-dagger
File size: 13,081 Bytes
bce3b06 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | """
Training-data generator for the tiny cube-solving model.
Emits (state, solution) pairs as JSONL:
{"state": "<54 facelet chars>", "solution": "R U2 F' ...", "depth": <walk length>}
## Why states come from a random walk, not direct cubie sampling
Sampling a cube state directly (random piece permutation + orientations) is the
textbook approach, but it needs a consistent orientation reference per slot, and
the slot lists in `packages/engine/src/moveTables.ts` identify slots without
encoding one -- they are not ordered U/D-first with a consistent cyclic
direction. Sampling against them naively produces a valid cube only ~1 call in
37 (measured), because the corner-twist, edge-flip and permutation-parity
constraints are then being enforced against the wrong reference.
Walking from solved using the engine's own move tables sidesteps the whole
question: every reachable state is valid by construction, and the tables are
already cross-checked against an independent implementation. Measured against
the exact-uniform samples that *did* pass, the two agree on the distribution of
Kociemba solution length (mean 20.75 vs 21.0, both spanning 17-22), so the walk
is reaching the uniform distribution, not a biased corner of it.
## Why the walk is long
A deep walk is the point, not a cost. Every reachable state is at most 20 moves
from solved (God's Number), so a walk of 200 samples the same "fully mixed"
distribution as a walk of 50 or 1000 -- which is exactly why the model does not
need a depth curriculum to handle depth-50 and depth-100 benchmark buckets. It
never sees "depth"; it sees a state. Shallow depths are the rare tail, so they
are mixed in explicitly via --shallow-frac rather than left to chance.
"""
import argparse, json, os, random, sys, time
QT={
"U": [6, 3, 0, 7, 4, 1, 8, 5, 2, 45, 46, 47, 12, 13, 14, 15, 16, 17, 9, 10, 11, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 18, 19, 20, 39, 40, 41, 42, 43, 44, 36, 37, 38, 48, 49, 50, 51, 52, 53],
"R": [0, 1, 20, 3, 4, 23, 6, 7, 26, 15, 12, 9, 16, 13, 10, 17, 14, 11, 18, 19, 29, 21, 22, 32, 24, 25, 35, 27, 28, 51, 30, 31, 48, 33, 34, 45, 36, 37, 38, 39, 40, 41, 42, 43, 44, 8, 46, 47, 5, 49, 50, 2, 52, 53],
"F": [0, 1, 2, 3, 4, 5, 44, 41, 38, 6, 10, 11, 7, 13, 14, 8, 16, 17, 24, 21, 18, 25, 22, 19, 26, 23, 20, 15, 12, 9, 30, 31, 32, 33, 34, 35, 36, 37, 27, 39, 40, 28, 42, 43, 29, 45, 46, 47, 48, 49, 50, 51, 52, 53],
"D": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 24, 25, 26, 18, 19, 20, 21, 22, 23, 42, 43, 44, 33, 30, 27, 34, 31, 28, 35, 32, 29, 36, 37, 38, 39, 40, 41, 51, 52, 53, 45, 46, 47, 48, 49, 50, 15, 16, 17],
"L": [53, 1, 2, 50, 4, 5, 47, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 0, 19, 20, 3, 22, 23, 6, 25, 26, 18, 28, 29, 21, 31, 32, 24, 34, 35, 42, 39, 36, 43, 40, 37, 44, 41, 38, 45, 46, 33, 48, 49, 30, 51, 52, 27],
"B": [11, 14, 17, 3, 4, 5, 6, 7, 8, 9, 10, 35, 12, 13, 34, 15, 16, 33, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 36, 39, 42, 2, 37, 38, 1, 40, 41, 0, 43, 44, 51, 48, 45, 52, 49, 46, 53, 50, 47],
}
SOLVED = "".join("URFDLB"[i // 9] for i in range(54))
MOVES = [f + s for f in "URFDLB" for s in ("", "'", "2")]
_TURNS = {"": 1, "'": 3, "2": 2}
def apply_move(state: str, move: str) -> str:
"""Applies one move in standard notation to a 54-char facelet string."""
perm = QT[move[0]]
for _ in range(_TURNS[move[1:]]):
state = "".join(state[perm[i]] for i in range(54))
return state
def apply_sequence(state: str, moves) -> str:
for m in moves:
state = apply_move(state, m)
return state
def random_state(rng: random.Random, depth: int) -> str:
"""Walks `depth` moves from solved, never turning the same face twice in a row."""
state, prev = SOLVED, None
for _ in range(depth):
while True:
move = rng.choice(MOVES)
if move[0] != prev:
break
state = apply_move(state, move)
prev = move[0]
return state
def check_kociemba_is_native(warn_only=False):
"""Refuses to run against kociemba's pure-Python fallback.
The package ships a C extension and a pure-Python implementation, and falls
back to the latter *silently* -- only a warning -- when the extension cannot
build (typically missing dev headers for whichever interpreter the venv was
made from). The fallback is roughly 50x slower, which turns a two-hour dataset
into a multi-day one. Since the only symptom is a slow log, this is checked
up front rather than discovered later: a rented machine generating at
fallback speed bills the whole time.
"""
import time
import kociemba
probe = "DRLUUBFBRBLURRLRUBLRDDFDLFUFUFFDBRDUBRUFLLFDDBFLUBLRBD"
kociemba.solve(probe) # warm the pruning tables before timing
start = time.time()
for _ in range(10):
kociemba.solve(probe)
ms = (time.time() - start) / 10 * 1000
if ms > 200:
msg = (f"kociemba is running at {ms:.0f} ms/solve, which is pure-Python "
f"fallback speed (native is ~20 ms). Install build-essential and "
f"python3-dev for this interpreter, then reinstall kociemba.")
if not warn_only:
raise SystemExit(f"ABORT: {msg}")
print(f"WARNING: {msg}", file=sys.stderr)
return ms
def generate(count, seed, deep_depth, shallow_frac, max_shallow, augment, out):
import kociemba
rng = random.Random(seed)
kociemba.solve(random_state(rng, 20)) # warm the pruning tables
written = 0
start = time.time()
while written < count:
if rng.random() < shallow_frac:
depth = rng.randint(1, max_shallow)
else:
depth = deep_depth
state = random_state(rng, depth)
if state == SOLVED:
continue
solution = kociemba.solve(state).split()
# One Kociemba call also settles every state along its own solution path:
# after applying a prefix, the remaining suffix is a valid solve of the
# state you land on. That is a ~20x multiplier on a call costing ~23ms,
# and it is what makes a multi-million-pair dataset practical on few
# cores. The suffix labels are valid but not what Kociemba would itself
# emit for those states, so they are a slightly different (more
# multi-modal) target than the canonical pairs -- hence opt-in, and worth
# measuring against augment=0 before relying on it.
pairs = [(state, solution, depth)]
if augment:
walk = state
for i, move in enumerate(solution[:-1]):
walk = apply_move(walk, move)
if walk == SOLVED:
break
pairs.append((walk, solution[i + 1:], len(solution) - i - 1))
rng.shuffle(pairs)
pairs = pairs[:augment]
for st, sol, d in pairs:
if written >= count:
break
out.write(json.dumps({"state": st, "solution": " ".join(sol), "depth": d}) + "\n")
written += 1
if written % 20000 < len(pairs):
rate = written / max(time.time() - start, 1e-9)
print(f" {written}/{count} ({rate:.0f}/s)", file=sys.stderr)
return written
def _worker(job):
"""One shard, in its own process with its own seed and its own pruning tables."""
idx, count, args = job
path = f"{args['out']}.part{idx}"
with open(path, "w") as fh:
generate(count, args["seed"] + 1000 * idx, args["deep_depth"],
args["shallow_frac"], args["max_shallow"], args["augment"], fh)
return path
def effective_cpus():
"""Number of CPUs this process may actually use.
`nproc` and os.cpu_count() report the *host's* cores, which on a rented
container is not what we get: a box advertising 128 cores routinely runs
under a cgroup quota of a fraction of that. Spawning one worker per host core
then puts 128 processes on ~16 CPUs, each running at an eighth speed. The
aggregate is unchanged -- it is capped by the quota either way -- but every
throughput estimate derived from the advertised core count is wrong by that
factor, which is how a 20-minute job came to be predicted for a 100-minute
one.
Prefers the scheduler affinity mask, then the cgroup v2 and v1 quotas.
"""
candidates = []
try:
candidates.append(len(os.sched_getaffinity(0)))
except AttributeError:
pass
for quota_path, period_path in (
("/sys/fs/cgroup/cpu.max", None),
("/sys/fs/cgroup/cpu/cpu.cfs_quota_us", "/sys/fs/cgroup/cpu/cpu.cfs_period_us"),
):
try:
raw = open(quota_path).read().split()
quota = raw[0]
period = int(raw[1]) if period_path is None else int(open(period_path).read())
if quota not in ("max", "-1"):
candidates.append(max(1, int(int(quota) / period)))
except (OSError, ValueError, IndexError):
pass
candidates.append(os.cpu_count() or 1)
return max(1, min(candidates))
def generate_parallel(count, workers, args_dict, shards=None):
"""Shards generation across processes.
`shards` fixes the number of shards independently of `workers`. Per-shard
seeds are derived as seed + 1000*index, so the dataset produced depends on the
shard count -- meaning two machines with different core counts generate
*different* data from the same --seed. That is fine for training data and
quietly wrong for a shared holdout: two models meant to face identical puzzles
face merely similar ones, turning a paired comparison into an unpaired one.
Pass a fixed `shards` whenever the output must be reproducible across
machines.
"""
"""Shards generation across processes. Kociemba is CPU-bound and releases
nothing to threads, so processes are the only way to use more than one core --
and generation, not training, is the wall-clock bottleneck for this dataset."""
import multiprocessing as mp
n_shards = shards or workers
per = [count // n_shards] * n_shards
for i in range(count % n_shards):
per[i] += 1
jobs = [(i, per[i], args_dict) for i in range(n_shards) if per[i]]
with mp.Pool(min(workers, len(jobs))) as pool:
parts = pool.map(_worker, jobs)
written = 0
with open(args_dict["out"], "w") as out:
for path in parts:
with open(path) as fh:
for line in fh:
out.write(line)
written += 1
os.remove(path)
return written
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--count", type=int, default=1_000_000)
p.add_argument("--seed", type=int, default=0)
p.add_argument("--deep-depth", type=int, default=200,
help="Walk length for the deep (fully mixed) majority of states.")
p.add_argument("--shallow-frac", type=float, default=0.15,
help="Fraction drawn from near-solved states instead.")
p.add_argument("--max-shallow", type=int, default=8)
p.add_argument("--augment", type=int, default=0, metavar="K",
help="Also emit up to K states from each solution path (0 = canonical pairs only).")
p.add_argument("--out", default="-")
p.add_argument("--shards", type=int, default=0,
help="Fix the shard count so output is identical across machines "
"regardless of core count. Required for a shared holdout.")
p.add_argument("--workers", type=int, default=0,
help="Parallel generator processes. 0 = the CPUs actually available "
"to this container, which is usually far fewer than nproc reports.")
args = p.parse_args()
ms = check_kociemba_is_native()
if args.workers <= 0:
args.workers = effective_cpus()
print(f"kociemba {ms:.1f} ms/solve | workers {args.workers} "
f"(host reports {os.cpu_count()})", file=sys.stderr)
if args.workers > 1:
if args.out == "-":
p.error("--workers needs --out (shards are merged into a file)")
start = time.time()
n = generate_parallel(args.count, args.workers, {
"out": args.out, "seed": args.seed, "deep_depth": args.deep_depth,
"shallow_frac": args.shallow_frac, "max_shallow": args.max_shallow,
"augment": args.augment}, shards=args.shards or None)
print(f"wrote {n} pairs in {time.time()-start:.0f}s "
f"({n/max(time.time()-start,1e-9):.0f}/s, {args.workers} workers)", file=sys.stderr)
return
out = sys.stdout if args.out == "-" else open(args.out, "w")
try:
n = generate(args.count, args.seed, args.deep_depth, args.shallow_frac,
args.max_shallow, args.augment, out)
print(f"wrote {n} pairs", file=sys.stderr)
finally:
if out is not sys.stdout:
out.close()
if __name__ == "__main__":
main()
|