59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
214 lines
7.2 KiB
Python
214 lines
7.2 KiB
Python
"""Regression tests for logits processing helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from types import SimpleNamespace
|
|
|
|
# CI Registration (parsed via AST, runtime no-op)
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from ci_system.ci_register import register_cuda_ci # noqa: E402
|
|
|
|
register_cuda_ci(est_time=90, suite="runtime-1gpu")
|
|
|
|
import pytest # noqa: E402
|
|
import torch # noqa: E402
|
|
|
|
import tokenspeed.runtime.layers.logits_processor as logits_processor_module # noqa: E402
|
|
from tokenspeed.runtime.execution.forward_batch_info import ForwardMode # noqa: E402
|
|
from tokenspeed.runtime.layers.logits_processor import ( # noqa: E402
|
|
LogitsMetadata,
|
|
LogitsProcessor,
|
|
fused_softcap,
|
|
)
|
|
|
|
|
|
def test_logits_processor_only_uses_fused_lm_head_for_kimi(monkeypatch):
|
|
hidden_states = torch.tensor([[1.0, 2.0]], dtype=torch.float32)
|
|
lm_head = SimpleNamespace(weight=torch.eye(2, dtype=torch.float32))
|
|
metadata = LogitsMetadata(forward_mode=ForwardMode.DECODE)
|
|
calls = {"fused": 0}
|
|
|
|
def fake_lm_head_matmul(hidden, weight):
|
|
calls["fused"] += 1
|
|
return torch.matmul(hidden.to(weight.dtype), weight.T)
|
|
|
|
monkeypatch.setattr(logits_processor_module, "_lm_head_matmul", fake_lm_head_matmul)
|
|
|
|
non_kimi = LogitsProcessor(config=SimpleNamespace(model_type="test", vocab_size=2))
|
|
non_kimi(
|
|
input_ids=None,
|
|
hidden_states=hidden_states,
|
|
lm_head=lm_head,
|
|
logits_metadata=metadata,
|
|
)
|
|
assert calls["fused"] == 0
|
|
|
|
kimi = LogitsProcessor(config=SimpleNamespace(model_type="kimi_k2", vocab_size=2))
|
|
kimi(
|
|
input_ids=None,
|
|
hidden_states=hidden_states,
|
|
lm_head=lm_head,
|
|
logits_metadata=metadata,
|
|
)
|
|
assert calls["fused"] == 1
|
|
|
|
|
|
def test_tp_logits_all_gather_handles_zero_rows(monkeypatch):
|
|
processor = LogitsProcessor(
|
|
config=SimpleNamespace(model_type="test", vocab_size=6),
|
|
tp_rank=0,
|
|
tp_size=2,
|
|
tp_group=(0, 1),
|
|
)
|
|
hidden_states = torch.empty((0, 2), dtype=torch.float32)
|
|
lm_head = SimpleNamespace(weight=torch.ones((3, 2), dtype=torch.float32))
|
|
metadata = LogitsMetadata(forward_mode=ForwardMode.DECODE)
|
|
calls = {"all_gather": 0}
|
|
|
|
def fake_all_gather_into_tensor(output, input_, group):
|
|
calls["all_gather"] += 1
|
|
assert group == (0, 1)
|
|
assert tuple(output.shape) == (0, 3)
|
|
assert tuple(input_.shape) == (0, 3)
|
|
|
|
monkeypatch.setattr(
|
|
logits_processor_module,
|
|
"all_gather_into_tensor",
|
|
fake_all_gather_into_tensor,
|
|
)
|
|
|
|
output = processor(
|
|
input_ids=None,
|
|
hidden_states=hidden_states,
|
|
lm_head=lm_head,
|
|
logits_metadata=metadata,
|
|
)
|
|
|
|
assert calls["all_gather"] == 1
|
|
assert tuple(output.next_token_logits.shape) == (0, 6)
|
|
|
|
|
|
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
|
|
def test_fused_softcap_handles_large_logits_without_nan():
|
|
cap = 30.0
|
|
logits = torch.tensor(
|
|
[[5000.0, 2000.0, 1500.0, 100.0, 0.0, -100.0, -1500.0, -5000.0]],
|
|
device="cuda",
|
|
dtype=torch.float32,
|
|
)
|
|
expected = cap * torch.tanh(logits / cap)
|
|
|
|
out = fused_softcap(logits.clone(), cap)
|
|
torch.cuda.synchronize()
|
|
|
|
assert torch.isfinite(out).all()
|
|
torch.testing.assert_close(out, expected, rtol=1e-5, atol=2e-5)
|
|
|
|
|
|
def test_argmax_routes_sharded_to_kernel(monkeypatch):
|
|
"""Sharded logits (tp shards reconstruct vocab) hit the fused kernel."""
|
|
proc = LogitsProcessor(
|
|
config=SimpleNamespace(model_type="test", vocab_size=8),
|
|
tp_rank=0,
|
|
tp_size=2,
|
|
tp_group=(0, 1),
|
|
)
|
|
proc._dist_argmax_state = object() # non-None, non-sentinel => active
|
|
|
|
recorded = {}
|
|
|
|
def fake_dist(state, logits):
|
|
recorded["called"] = True
|
|
return None, logits.argmax(dim=-1)
|
|
|
|
monkeypatch.setattr(logits_processor_module, "distributed_argmax", fake_dist)
|
|
|
|
shard = torch.randn(4, 4, dtype=torch.float32) # 4 * tp_size(2) == vocab_size(8)
|
|
ids = proc._argmax(shard)
|
|
assert recorded.get("called")
|
|
assert torch.equal(ids, shard.argmax(dim=-1))
|
|
|
|
|
|
def test_argmax_falls_back_without_state(monkeypatch):
|
|
"""No fused state (e.g. EAGLE3 draft vocab != target, or gate failed):
|
|
_argmax falls back to a plain argmax instead of routing to the kernel."""
|
|
proc = LogitsProcessor(
|
|
config=SimpleNamespace(model_type="test", vocab_size=100),
|
|
tp_rank=0,
|
|
tp_size=2,
|
|
tp_group=(0, 1),
|
|
)
|
|
proc._dist_argmax_state = None # gate failed (draft vocab != target vocab)
|
|
|
|
monkeypatch.setattr(
|
|
logits_processor_module,
|
|
"distributed_argmax",
|
|
lambda *a, **k: pytest.fail("kernel must not run without a fused state"),
|
|
)
|
|
|
|
# Gathered draft logits are narrower than the target config.vocab_size.
|
|
draft = torch.randn(4, 32, dtype=torch.float32)
|
|
ids = proc._argmax(draft)
|
|
assert torch.equal(ids, draft.argmax(dim=-1))
|
|
|
|
|
|
def test_get_logits_skips_gather_when_dist_argmax_active(monkeypatch):
|
|
"""do_argmax + active state keeps logits sharded (no all-gather)."""
|
|
proc = LogitsProcessor(
|
|
config=SimpleNamespace(
|
|
model_type="test", vocab_size=8, final_logit_softcapping=None
|
|
),
|
|
tp_rank=0,
|
|
tp_size=2,
|
|
tp_group=(0, 1),
|
|
do_argmax=True,
|
|
)
|
|
monkeypatch.setattr(proc, "_init_dist_argmax_state", lambda lm_head: object())
|
|
monkeypatch.setattr(
|
|
logits_processor_module,
|
|
"all_gather_inner",
|
|
lambda *a, **k: pytest.fail("gather must be skipped on the fused path"),
|
|
)
|
|
|
|
hidden = torch.randn(4, 2, dtype=torch.float32)
|
|
lm_head = SimpleNamespace(weight=torch.randn(4, 2, dtype=torch.float32)) # 4*2 == 8
|
|
md = LogitsMetadata(forward_mode=ForwardMode.DECODE)
|
|
out = proc._get_logits(hidden, lm_head, md)
|
|
assert out.shape == (4, 4) # local shard width retained, not gathered to 8
|
|
|
|
|
|
def test_get_logits_softcap_disables_fused_argmax(monkeypatch):
|
|
"""final_logit_softcapping must disable the fused early-return so the
|
|
softcap is applied to full-vocab logits (then a plain argmax runs)."""
|
|
proc = LogitsProcessor(
|
|
config=SimpleNamespace(
|
|
model_type="test", vocab_size=8, final_logit_softcapping=30.0
|
|
),
|
|
tp_rank=0,
|
|
tp_size=2,
|
|
tp_group=(0, 1),
|
|
do_argmax=True,
|
|
)
|
|
# Fused state is otherwise eligible; softcap must still force the gather.
|
|
monkeypatch.setattr(proc, "_init_dist_argmax_state", lambda lm_head: object())
|
|
monkeypatch.setattr(proc, "_init_all_gather_state", lambda lm_head: object())
|
|
called = {}
|
|
|
|
def fake_ag(state, logits, **kw):
|
|
called["ag"] = True
|
|
return logits.repeat(1, proc.tp_size) # [bs, vocab/tp] -> [bs, vocab]
|
|
|
|
monkeypatch.setattr(logits_processor_module, "all_gather_inner", fake_ag)
|
|
monkeypatch.setattr(
|
|
logits_processor_module, "fused_softcap_generic", lambda *a, **k: None
|
|
)
|
|
|
|
hidden = torch.randn(4, 2, dtype=torch.float32)
|
|
lm_head = SimpleNamespace(weight=torch.randn(4, 2, dtype=torch.float32)) # 4*2 == 8
|
|
md = LogitsMetadata(forward_mode=ForwardMode.DECODE)
|
|
out = proc._get_logits(hidden, lm_head, md)
|
|
assert called.get("ag") # gathered (softcap on full vocab), not early-returned
|
|
assert out.shape == (4, 8)
|