Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
cee23bf
fix(runtime): correct overlap token limits and cache safety (#546)
jason-fxz Sep 24, 2026
f5b9700
refactor(ple): extract a shared disk row store (#548)
jason-fxz Sep 25, 2026
0d652e7
feat(rocm): add RDNA3 and RDNA4 runtime foundation (#132)
zihaomu Sep 26, 2026
5031e3d
perf(qsa): reduce block top-k candidates in levels past 16 splits (#571)
YevheniiKotyrlo Sep 29, 2026
eba7120
feat(install): record the install root as FREETOKEN_HOME in environme…
jason-fxz Sep 30, 2026
235e201
feat(rotary): size the rope table for YaRN and add --hf-overrides (#573)
YevheniiKotyrlo Oct 1, 2026
d3512b4
fix(ple): fence disk staging against in-flight lookups (#588)
jason-fxz Oct 1, 2026
64c99bc
merge #408: nvfp4 kv quantization
gdevenyi Sep 23, 2026
08c223e
test(kvcache): give the qsa fp8 pool fakes the mrope flag main now reads
gdevenyi Sep 14, 2026
3b3b742
merge #435: resolve JSON Schema refs and union types in tool arguments
gdevenyi Sep 23, 2026
078d14a
merge #391: accept the OpenAI developer role
gdevenyi Sep 23, 2026
a38a09b
merge #475: respect explicit enable_thinking=false with tools
gdevenyi Sep 23, 2026
dce1579
merge #339: drop the D2H sync from the varlen GDN/KDA prefill conv
gdevenyi Sep 23, 2026
e270985
merge #338: fuse the PLE n-gram row-id hash into one Triton kernel
gdevenyi Sep 23, 2026
0d28e10
merge #169: load prefill triton kernels at startup, not mid-request
gdevenyi Sep 23, 2026
af1be3d
merge #231: --moe-collect-stats, so expert-cache behaviour is measurable
gdevenyi Sep 23, 2026
e5581e4
merge #389: load-time per-tensor FP8 dense projections (stacked on #3…
gdevenyi Sep 23, 2026
8ff7f4a
fix(models): restore the os import and truthy set the fp8 env helpers…
gdevenyi Sep 15, 2026
a1398c8
test(qwen4_exp): restore the rope device and clear get_rope after the…
gdevenyi Sep 15, 2026
5dee633
test(kernels): give the block_ends backend fake the kv scale accessor…
gdevenyi Sep 15, 2026
b616e41
merge fork #18: fix(moe): report the routing top-k figure as a static…
gdevenyi Sep 23, 2026
340c81b
merge fork #19: fix(qwen4_exp): refuse an ftw checkpoint whose fp8 sp…
gdevenyi Sep 23, 2026
e1cdc87
merge fork #20: fix(engine): let moe-cache-auto drop prefill overlap …
gdevenyi Sep 23, 2026
704bc90
merge fork #21: fix(triton): keep the float glm dsa sparse launches t…
gdevenyi Sep 23, 2026
e9e63eb
merge fork #22: fix(engine): resize shrinking cache pools before grow…
gdevenyi Sep 23, 2026
bdbae96
merge fork #23 (upstream FlashML-org/FreeToken#17 rebased): fix(serve…
gdevenyi Sep 23, 2026
f8492b8
merge fork #47 (upstream FlashML-org/FreeToken#393 rebased): OpenAI p…
gdevenyi Sep 23, 2026
b5c3c4f
fix(models): skip the ftw layout check for a bare ftw without an hf c…
gdevenyi Sep 18, 2026
f79bd0d
merge #505: fix(scheduler): preserve pending hybrid checkpoint across…
gdevenyi Sep 23, 2026
1777351
merge #466: fix(utils): tolerate coalesced msgpack frames in zmq pull…
gdevenyi Sep 23, 2026
fa65913
merge #414: fix(kernel): order the batch-memcpy probe against the cur…
gdevenyi Sep 23, 2026
e308046
merge #118: fix(scheduler): clamp admission to the actual KV pool so …
gdevenyi Sep 23, 2026
2300590
test(scheduler): give the rejection test's engine fake the pool size …
gdevenyi Sep 18, 2026
e559779
merge #476: feat(server): add --default-thinking-mode flag for server…
gdevenyi Sep 23, 2026
4f5ccf2
merge #340: fix(engine): reserve the pool's dummy page in the --moe-c…
gdevenyi Sep 23, 2026
7177982
merge #198: fix(engine): reserve explicit KV pages during MoE auto-si…
gdevenyi Sep 23, 2026
133c014
merge #459: fix(checkpoint): validate FTW v1 indexes eagerly
gdevenyi Sep 23, 2026
d20ba39
merge #487: fix(server): hoist system messages to front for templates…
gdevenyi Sep 23, 2026
ae0093b
merge #222: fix(server): guarantee AbortMsg delivery on stream cancel…
gdevenyi Sep 23, 2026
8430f00
test(server): abort every sample of an n>1 non-streaming request on d…
gdevenyi Sep 18, 2026
8ece637
fix(server): record requests with a pure asgi middleware so handlers …
gdevenyi Sep 18, 2026
448fd9f
merge #499: feat(kvcache): host-RAM KV tier for sparse-attention mode…
gdevenyi Sep 23, 2026
3b96582
merge #525: feat(kvcache): host-resident prefix tier for hybrid GDN m…
gdevenyi Sep 23, 2026
f02c3bb
fix(kvcache): refuse the host kv tiers on an nvfp4 pool whose block s…
gdevenyi Sep 19, 2026
fb6b756
merge #500: perf(moe): single-launch prefill buffer invalidation with…
gdevenyi Sep 23, 2026
61a799b
merge #108: feat(server): add OpenAI /v1/models/{model_id} endpoint
gdevenyi Sep 23, 2026
25f11c3
merge #504: feat(server): serve per-request inference metrics under -…
gdevenyi Sep 23, 2026
b658dee
test(scheduler): give the scheduler stubs the prefill-start map the m…
gdevenyi Sep 23, 2026
941b607
merge #224: feat(server): OpenAI-compatible logprobs for chat and leg…
gdevenyi Sep 23, 2026
fb6b9a5
test(engine): give the logprobs sampler fakes the logits-processing f…
gdevenyi Sep 23, 2026
b7c05d7
merge fork review fixes: 11 bugs found re-reviewing the 5080deploy stack
gdevenyi Sep 24, 2026
1d195ae
merge fork fix (issue #65): run the PLE graph fill on its own thread,…
gdevenyi Sep 24, 2026
60961c3
merge fork perf: load only the routed experts for short prefills (FRE…
gdevenyi Sep 24, 2026
1d20164
merge fork perf: opt-in post-load FP8 for the qwen4_exp shared expert…
gdevenyi Sep 24, 2026
4985d83
revert the post-load FP8 shared expert / hyper-connection merge
gdevenyi Sep 24, 2026
31d7bc9
merge fork fix: small-prefill on-demand path hands the LRU kernel uni…
gdevenyi Sep 24, 2026
a426033
merge fork fix: small-prefill ensures its experts in decode-width chunks
gdevenyi Sep 24, 2026
f2bd437
merge fork perf: scored expert eviction (--moe-cache-policy), shared …
gdevenyi Sep 26, 2026
9afbb4c
merge fork perf: expert prefetch into low-priority slots with single-…
gdevenyi Sep 27, 2026
43e0cad
test(engine): give the kv quant config fake the rope table size #573'…
gdevenyi Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ parsers all resolve automatically from the checkpoint and the GPU.
|---|---|---|
| `--model-path`, `--model` | required | Local dir, HF repo id, or an FTW dir (auto-detected) |
| `--served-model-name` | basename of `--model` | Model id reported by `/v1/models` |
| `--hf-overrides` | — | JSON object applied to the checkpoint's config as vLLM's `--hf-overrides`: a nested config section updates key by key, any other value is replaced whole. A YaRN `rope_parameters` override serves `original_max_position_embeddings * factor` positions |

### Server & runtime

Expand Down
2 changes: 2 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Install

AMD users: see the [AMD ROCm installation guide (WIP)](install_amd.md).

## Requirements

- Linux x86_64, NVIDIA GPU, driver r580+ (CUDA 13)
Expand Down
39 changes: 39 additions & 0 deletions docs/install_amd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# AMD ROCm installation (WIP)

AMD support is experimental and remains a work in progress. See the
[AMD Support roadmap](https://github.com/FlashML-org/FreeToken/issues/541) for
the current integration and qualification status.

## Requirements

- Linux x86_64
- AMD RDNA3/RDNA4 GPU (`gfx1100`-`gfx1103`, `gfx1200`, or `gfx1201`)
- ROCm 7.14
- Python >= 3.10

## Install from source

Use an official ROCm PyTorch image whose PyTorch version satisfies the project's
`torch>=2.11,<2.12` constraint. For RDNA4, the matching ROCm 7.14 image is:

```bash
VIDEO_GID="$(getent group video | cut -d: -f3)"
RENDER_GID="$(getent group render | cut -d: -f3)"
docker run --rm -it \
--device=/dev/kfd --device=/dev/dri \
--group-add="$VIDEO_GID" --group-add="$RENDER_GID" --ipc=host \
--cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
-e PYTORCH_ROCM_ARCH=gfx1201 -e FREETOKEN_ROCM_ARCH=gfx1201 \
-v "$PWD:/workspace/FreeToken" -w /workspace/FreeToken \
rocm/pytorch:rocm7.14_ubuntu24.04_py3.12_pytorch_release_2.11.0 bash
```

Inside the container, preserve the ROCm-enabled PyTorch already supplied by the
image and disable build isolation so it is also used to compile the extensions:

```bash
python -m pip install --no-build-isolation -e .
```

Set both architecture variables to `gfx1200` for RX 9060 family GPUs, or to the
actual target reported by `rocminfo`.
12 changes: 6 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
# Defaults to $DEFAULT_KERNEL_CACHE_WHEEL_URL; if
# unset and FREETOKEN_WHEEL is local, the script
# auto-detects a sibling freetoken_kernel_cache-*.whl.
# FREETOKEN_HOME install root (default: ~/.freetoken); venv at $FREETOKEN_HOME/venv
# FREETOKEN_HOME install root (default: ~/.freetoken); venv at $FREETOKEN_HOME/venv;
# a successful install writes it to environment.d
# FREETOKEN_PY_VERSION python for the venv (default: 3.12 — must match the wheel tag)
# FREETOKEN_BIN_DIR where to symlink `ft` (default: ~/.local/bin)
# FREETOKEN_ENV_DIR environment.d dir (default: ~/.config/environment.d)
Expand Down Expand Up @@ -248,8 +249,8 @@ ln -sf "$FT_BIN" "$BIN_DIR/ft"
say "symlinked $BIN_DIR/ft -> $FT_BIN"

mkdir -p "$ENV_DIR"
printf 'FREETOKEN_FT_BIN=%s\n' "$FT_BIN" > "$ENV_DIR/50-freetoken.conf"
say "wrote $ENV_DIR/50-freetoken.conf (FREETOKEN_FT_BIN) — GUI picks it up after next login"
printf 'FREETOKEN_HOME=%s\n' "$(cd "$FT_HOME" && pwd)" >"$ENV_DIR/50-freetoken.conf"
say "wrote $ENV_DIR/50-freetoken.conf (FREETOKEN_HOME) - FreeToken Desktop reads the file, other programs see the variable after next login"

# --- 5. Self-check ---------------------------------------------------------
if "$FT_BIN" --help >/dev/null 2>&1; then
Expand All @@ -264,10 +265,9 @@ ${C_GREEN}FreeToken engine installed.${C_RESET}

ft binary $FT_BIN
on PATH as $BIN_DIR/ft (ensure $BIN_DIR is on PATH)
Desktop env FREETOKEN_FT_BIN via environment.d (re-login to apply)
Desktop env FREETOKEN_HOME in $ENV_DIR/50-freetoken.conf

Run in this shell without re-login:
export FREETOKEN_FT_BIN="$FT_BIN"
Run:
ft serve --model <path> --port 1919

EOF
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ classifiers = [
"Intended Audience :: Science/Research",
"Operating System :: POSIX :: Linux",
"Environment :: GPU :: NVIDIA CUDA",
"Environment :: GPU :: AMD ROCm",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down Expand Up @@ -59,7 +60,10 @@ dependencies = [
"torchvision>=0.26,<0.27",
"tqdm>=4.66,<5",
"transformers>=5.16,<5.17",
"triton==3.6.0; platform_system == 'Linux'",
# CUDA torch 2.11 resolves Triton 3.6; AMD's ROCm 7.14 image supplies its
# gfx1201-enabled Triton 3.7 build. Keep both supported without replacing the
# runtime-specific wheel selected by the PyTorch distribution.
"triton>=3.6,<3.8; platform_system == 'Linux'",
"uvicorn>=0.30,<1",
]

Expand Down
17 changes: 8 additions & 9 deletions python/freetoken/attention/qsa_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,23 +187,22 @@ def _cmp_pages(self, slot: int) -> torch.Tensor:

def _index_rope_cache(self) -> torch.Tensor:
"""cos/sin table of the indexer rope: same rotary_dim and frequencies as the main
attention, ``head_size`` 128 instead of 256, so it is a separate get_rope instance.
attention with ``head_size`` 128 instead of 256, so the attention's own table.

The table itself (not RotaryEmbedding.forward) because the indexer's norm+rope is one
fused kernel and the compressed keys rope at their group's position, not the query's."""
if self._index_cos_sin is None:
from freetoken.layers.rotary import get_rope
from freetoken.layers.rotary import rope_cos_sin_table

rotary = self.rotary_config
with torch.device(self.device):
rope = get_rope(
head_dim=self.index_head_dim,
rotary_dim=rotary.rotary_dim,
max_position=rotary.max_position,
base=rotary.base,
rope_scaling=tuple(rotary.scaling.items()) if rotary.scaling else None,
self._index_cos_sin = rope_cos_sin_table(
self.index_head_dim,
rotary.rotary_dim,
rotary.max_position,
rotary.base,
tuple(rotary.scaling.items()) if rotary.scaling else None,
)
self._index_cos_sin = rope._cos_sin_cache.to(self.device)
return self._index_cos_sin

def _index_rope_rows(self, positions: torch.Tensor) -> torch.Tensor:
Expand Down
8 changes: 5 additions & 3 deletions python/freetoken/engine/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from dataclasses import dataclass, field, replace
from functools import cached_property
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, Any, List, Mapping

import torch
from freetoken.distributed import DistributedInfo
Expand All @@ -24,6 +24,8 @@ class EngineConfig:
model_path: str
tp_info: DistributedInfo
dtype: torch.dtype
# --hf-overrides: applied to the checkpoint config the model is built from (cached_load_hf_config)
hf_overrides: Mapping[str, Any] = field(default_factory=dict)
max_running_req: int = 4
attention_backend: str = "auto"
moe_strategy: str = "auto"
Expand Down Expand Up @@ -117,7 +119,7 @@ def __post_init__(self):

@cached_property
def hf_config(self):
return cached_load_hf_config(self.model_path)
return cached_load_hf_config(self.model_path, self.hf_overrides)

@cached_property
def model_spec(self) -> ModelSpec:
Expand Down Expand Up @@ -156,7 +158,7 @@ def model_config(self) -> ModelConfig:
def max_seq_len(self) -> int:
if self.max_seq_len_override is not None:
return self.max_seq_len_override
return self.model_config.rotary_config.max_position
return self.model_config.rotary_config.table_positions

@property
def max_forward_len(self) -> int:
Expand Down
9 changes: 4 additions & 5 deletions python/freetoken/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2129,18 +2129,17 @@ def override(attr: str, value: Any): # this is dangerous, use with caution
)
override("num_page_override", config.num_token_override // config.page_size)

# The rope cos/sin table is baked to rotary_config.max_position, and neither rope kernel
# The rope cos/sin table covers rotary_config.table_positions, and neither rope kernel
# bounds-checks the position it gathers with -- a longer ceiling reads past the table.
# DSV4 is exempt: it sizes its own table from the resolved max_seq_len (_adjust_dsv4_config).
rotary = getattr(model_config, "rotary_config", None)
seq_override = getattr(config, "max_seq_len_override", None)
if seq_override is not None and rotary is not None and not is_dsv4:
if seq_override > rotary.max_position:
if seq_override > rotary.table_positions:
raise ValueError(
f"--max-seq-len-override {seq_override} exceeds the model's "
f"rope table ({rotary.max_position} positions). Serving past it would read "
"out of bounds; extend the checkpoint's rope_scaling / "
"max_position_embeddings in config.json instead."
f"rope table ({rotary.table_positions} positions). Serving past it would read "
"out of bounds; extend the rope with --hf-overrides (YaRN rope_parameters) instead."
)

# The startup ServerArgs dump is the *requested* config, printed in the frontend process
Expand Down
35 changes: 20 additions & 15 deletions python/freetoken/kernel/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@ def generate_clangd():
import subprocess

from freetoken.kernel.utils import DEFAULT_INCLUDE
from freetoken.utils import init_logger
from freetoken.utils import get_rocm_gfx_arch, init_logger, is_rocm
from tvm_ffi.libinfo import find_dlpack_include_path, find_include_path

logger = init_logger(__name__)
logger.info("Generating .clangd file...")
include_paths = [find_include_path(), find_dlpack_include_path()] + DEFAULT_INCLUDE
status = subprocess.run(
args=["nvidia-smi", "--query-gpu=compute_cap", "--format=csv,noheader"],
capture_output=True,
check=True,
)
compute_cap = status.stdout.decode("utf-8").strip().split("\n")[0]
major, minor = compute_cap.split(".")

# TODO(ROCm): hiprtc JIT cache should be separate from nvcc JIT cache to avoid stale binaries.
if is_rocm():
arch_flags = ["-xhip", f"--offload-arch={get_rocm_gfx_arch() or 'gfx1201'}"]
else:
try:
status = subprocess.run(
args=["nvidia-smi", "--query-gpu=compute_cap", "--format=csv,noheader"],
capture_output=True,
check=True,
)
compute_cap = status.stdout.decode("utf-8").strip().split("\n")[0]
major, minor = compute_cap.split(".")
except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
import torch

major, minor = torch.cuda.get_device_capability()
arch_flags = ["-xcuda", f"--cuda-gpu-arch=sm_{major}{minor}"]
compile_flags = ",\n ".join(
[
"-xcuda",
f"--cuda-gpu-arch=sm_{major}{minor}",
"-std=c++20",
"-Wall",
"-Wextra",
]
arch_flags + ["-std=c++20", "-Wall", "-Wextra"]
+ [f"-isystem{path}" for path in include_paths]
)
clangd_content = f"""
Expand Down
9 changes: 8 additions & 1 deletion python/freetoken/kernel/_toolchain.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""CUDA toolchain/torch consistency checks.
"""CUDA/HIP toolchain/torch consistency checks.

Standalone on purpose: setup.py and the kernel-cache build backend load this
file by path, so it must not import the freetoken package.
Expand All @@ -16,6 +16,11 @@
_TRUE_VALUES = {"1", "true", "yes", "on"}


def _is_rocm() -> bool:
import torch
return getattr(torch.version, "hip", None) is not None


def _nvcc_path() -> str | None:
from torch.utils.cpp_extension import CUDA_HOME

Expand Down Expand Up @@ -49,6 +54,8 @@ def check_nvcc_matches_torch() -> None:
nvcc-built binaries link libcudart.so.<nvcc major>; at runtime only the
torch wheel's own CUDA runtime is guaranteed to be loadable.
"""
if _is_rocm():
return # ROCm uses hipcc, not nvcc
if os.getenv(ALLOW_MISMATCH_ENV, "").strip().lower() in _TRUE_VALUES:
return
torch_major = torch_cuda_major()
Expand Down
18 changes: 18 additions & 0 deletions python/freetoken/kernel/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ def device_capability() -> tuple[int, int]:
return (int(major), int(minor))


@functools.cache
def is_rocm() -> bool:
"""True when torch is built for ROCm (AMD GPU)."""
import torch
return getattr(torch.version, "hip", None) is not None


@functools.cache
def driver_hip_version() -> int | None:
"""ROCm driver version, or None if undetermined."""
# TODO(ROCm): flashinfer/sgl_kernel have no ROCm builds — Triton fallback is used.
try:
from freetoken.kernel.pinned import _load_pinned_extension
return int(_load_pinned_extension().driver_cuda_version()) or None
except Exception:
return None


@functools.cache
def driver_cuda_version() -> int | None:
"""Max CUDA version the installed NVIDIA driver supports (``13000`` == CUDA 13.0),
Expand Down
2 changes: 1 addition & 1 deletion python/freetoken/kernel/csrc/cpu_moe/cpu_moe_ext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#include <thread>
#include <vector>

#include <cuda_runtime_api.h>
#include <freetoken/hip_compat.h>
#include <torch/extension.h>

#if defined(__linux__)
Expand Down
Loading
Loading