Spaces:
Running
Running
zero-gpu: size the token budget to the measured decode rate
Browse files- BUILD.json +2 -2
- traceforge/space/local_llm.py +10 -3
- traceforge/space/pipeline.py +3 -1
BUILD.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
-
"built_at": "2026-10-29T17:
|
| 3 |
-
"commit": "
|
| 4 |
"files": 53,
|
| 5 |
"with_store": false,
|
| 6 |
"with_card": true
|
|
|
|
| 1 |
{
|
| 2 |
+
"built_at": "2026-10-29T17:14:25",
|
| 3 |
+
"commit": "b187ff2",
|
| 4 |
"files": 53,
|
| 5 |
"with_store": false,
|
| 6 |
"with_card": true
|
traceforge/space/local_llm.py
CHANGED
|
@@ -255,9 +255,10 @@ def _fused_moe_forward(self, hidden_states, top_k_index, top_k_weights):
|
|
| 255 |
transformers' Lfm2MoeExperts.forward loops over the experts that fired and
|
| 256 |
calls .nonzero(), torch.where() and parameter indexing inside that loop.
|
| 257 |
Every one of those is a device-to-host sync, so a single decoded token
|
| 258 |
-
costs ~6 syncs per expert per layer -- ~
|
| 259 |
-
|
| 260 |
-
|
|
|
|
| 261 |
|
| 262 |
For a handful of tokens we gather the selected experts' weights and run two
|
| 263 |
batched GEMMs instead, which needs no sync at all. Prefill keeps the
|
|
@@ -386,6 +387,12 @@ def generate(messages: list[dict], *, max_tokens: int = 512,
|
|
| 386 |
enc = tok(prompt, return_tensors="pt", truncation=True,
|
| 387 |
max_length=LOCAL_MAX_INPUT_TOKENS).to(LOCAL_DEVICE)
|
| 388 |
n = max(1, min(int(max_tokens or 512), LOCAL_MAX_NEW_TOKENS))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
t0 = time.time()
|
| 390 |
with torch.inference_mode():
|
| 391 |
out = model.generate(
|
|
|
|
| 255 |
transformers' Lfm2MoeExperts.forward loops over the experts that fired and
|
| 256 |
calls .nonzero(), torch.where() and parameter indexing inside that loop.
|
| 257 |
Every one of those is a device-to-host sync, so a single decoded token
|
| 258 |
+
costs ~6 syncs per expert per layer -- ~960 for this 40-layer top-4 model
|
| 259 |
+
(64 experts, 4 per token). At the ~60 us a sync costs on a ZeroGPU
|
| 260 |
+
container that is 58 ms per token, i.e. the 16 tok/s we measured on a full
|
| 261 |
+
RTX PRO 6000 Blackwell, where the weights alone allow hundreds.
|
| 262 |
|
| 263 |
For a handful of tokens we gather the selected experts' weights and run two
|
| 264 |
batched GEMMs instead, which needs no sync at all. Prefill keeps the
|
|
|
|
| 387 |
enc = tok(prompt, return_tensors="pt", truncation=True,
|
| 388 |
max_length=LOCAL_MAX_INPUT_TOKENS).to(LOCAL_DEVICE)
|
| 389 |
n = max(1, min(int(max_tokens or 512), LOCAL_MAX_NEW_TOKENS))
|
| 390 |
+
# Size the budget to what this slice can actually decode. The first call
|
| 391 |
+
# measures tok/s; later calls trim the ceiling to ~60 s of generation so a
|
| 392 |
+
# slow slice fails fast instead of burning the visitor's whole quota.
|
| 393 |
+
measured = float(LAST_GEN.get("tok_s") or 0)
|
| 394 |
+
if measured > 0:
|
| 395 |
+
n = max(64, min(n, int(measured * 60)))
|
| 396 |
t0 = time.time()
|
| 397 |
with torch.inference_mode():
|
| 398 |
out = model.generate(
|
traceforge/space/pipeline.py
CHANGED
|
@@ -88,7 +88,9 @@ def space_config(data_dir: Optional[str] = None, **overrides: Any) -> Config:
|
|
| 88 |
cfg.triage_max_chars = cfg.splice_max_chars
|
| 89 |
# Labels are compact JSON, but LFM2.5 always thinks first and ignores
|
| 90 |
# a pre-closed think block: 600 tokens only ever bought reasoning.
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
cfg.rewrite_max_tokens = _env_int("TRACEFORGE_LOCAL_REWRITE_TOKENS",
|
| 93 |
1000)
|
| 94 |
cfg.judge_max_tokens = _env_int("TRACEFORGE_LOCAL_JUDGE_TOKENS", 1200)
|
|
|
|
| 88 |
cfg.triage_max_chars = cfg.splice_max_chars
|
| 89 |
# Labels are compact JSON, but LFM2.5 always thinks first and ignores
|
| 90 |
# a pre-closed think block: 600 tokens only ever bought reasoning.
|
| 91 |
+
# The ceiling is generous because generate() trims it to what the
|
| 92 |
+
# measured decode rate can actually deliver inside one slice.
|
| 93 |
+
cfg.triage_max_tokens = _env_int("TRACEFORGE_LOCAL_TRIAGE_TOKENS", 4000)
|
| 94 |
cfg.rewrite_max_tokens = _env_int("TRACEFORGE_LOCAL_REWRITE_TOKENS",
|
| 95 |
1000)
|
| 96 |
cfg.judge_max_tokens = _env_int("TRACEFORGE_LOCAL_JUDGE_TOKENS", 1200)
|