Initial ZONOS2 ZeroGPU Space
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- .gitignore +7 -0
- README.md +13 -8
- app.py +317 -0
- requirements.txt +19 -0
- zonos2/__main__.py +5 -0
- zonos2/attention/__init__.py +94 -0
- zonos2/attention/base.py +69 -0
- zonos2/attention/fa.py +199 -0
- zonos2/attention/fi.py +360 -0
- zonos2/attention/utils.py +56 -0
- zonos2/benchmark/client.py +501 -0
- zonos2/benchmark/perf.py +74 -0
- zonos2/core.py +218 -0
- zonos2/distributed/__init__.py +12 -0
- zonos2/distributed/impl.py +97 -0
- zonos2/distributed/info.py +38 -0
- zonos2/engine/__init__.py +4 -0
- zonos2/engine/config.py +55 -0
- zonos2/engine/engine.py +336 -0
- zonos2/engine/graph.py +157 -0
- zonos2/engine/sample.py +170 -0
- zonos2/env.py +87 -0
- zonos2/kernel/__init__.py +14 -0
- zonos2/kernel/__main__.py +47 -0
- zonos2/kernel/csrc/include/zonos2/nccl227.h +571 -0
- zonos2/kernel/csrc/include/zonos2/tensor.h +496 -0
- zonos2/kernel/csrc/include/zonos2/utils.cuh +144 -0
- zonos2/kernel/csrc/include/zonos2/utils.h +122 -0
- zonos2/kernel/csrc/include/zonos2/warp.cuh +80 -0
- zonos2/kernel/csrc/jit/index.cu +172 -0
- zonos2/kernel/csrc/jit/store.cu +123 -0
- zonos2/kernel/csrc/src/pynccl.cu +188 -0
- zonos2/kernel/csrc/src/radix.cpp +44 -0
- zonos2/kernel/csrc/src/tensor.cpp +31 -0
- zonos2/kernel/index.py +50 -0
- zonos2/kernel/moe_impl.py +59 -0
- zonos2/kernel/pynccl.py +78 -0
- zonos2/kernel/radix.py +20 -0
- zonos2/kernel/store.py +42 -0
- zonos2/kernel/tensor.py +19 -0
- zonos2/kernel/triton/fused_moe.py +230 -0
- zonos2/kernel/utils.py +129 -0
- zonos2/kvcache/__init__.py +74 -0
- zonos2/kvcache/base.py +130 -0
- zonos2/kvcache/mha_pool.py +79 -0
- zonos2/kvcache/naive_manager.py +44 -0
- zonos2/kvcache/radix_manager.py +221 -0
- zonos2/layers/__init__.py +32 -0
- zonos2/layers/activation.py +15 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
zonos2/vendor/nemo_text_processing/text_normalization/en/data/number/cardinal_number_name.far filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
.gradio/
|
| 4 |
+
.venv/
|
| 5 |
+
venv/
|
| 6 |
+
*.wav
|
| 7 |
+
.DS_Store
|
README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ZONOS2
|
| 3 |
+
emoji: 🔊
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.10.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
+
short_description: ZeroGPU text-to-speech with ZONOS2
|
| 10 |
+
python_version: "3.10"
|
| 11 |
+
startup_duration_timeout: 1h
|
| 12 |
+
preload_from_hub:
|
| 13 |
+
- Zyphra/ZONOS2
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# ZONOS2 on ZeroGPU
|
| 17 |
+
|
| 18 |
+
Gradio Space for [Zyphra/ZONOS2](https://huggingface.co/Zyphra/ZONOS2), using the upstream [ZONOS2](https://github.com/Zyphra/ZONOS2) inference code with small ZeroGPU compatibility patches.
|
app.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import tempfile
|
| 3 |
+
import threading
|
| 4 |
+
import time
|
| 5 |
+
import wave
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
os.environ.setdefault("HF_HOME", "/data/.cache/huggingface")
|
| 9 |
+
os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules")
|
| 10 |
+
os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
|
| 11 |
+
os.environ.setdefault("ZONOS2_TTS_NORM_CACHE_DIR", "/tmp/zonos2-tts-norm")
|
| 12 |
+
os.environ.setdefault("GRADIO_SSR_MODE", "false")
|
| 13 |
+
os.environ.setdefault("NUMBA_DISABLE_CUDA", "1")
|
| 14 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 15 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 16 |
+
|
| 17 |
+
for cache_dir in (
|
| 18 |
+
os.environ["HF_HOME"],
|
| 19 |
+
os.environ["HF_MODULES_CACHE"],
|
| 20 |
+
os.environ["MPLCONFIGDIR"],
|
| 21 |
+
os.environ["ZONOS2_TTS_NORM_CACHE_DIR"],
|
| 22 |
+
):
|
| 23 |
+
Path(cache_dir).mkdir(parents=True, exist_ok=True)
|
| 24 |
+
|
| 25 |
+
import spaces
|
| 26 |
+
import gradio as gr
|
| 27 |
+
import numpy as np
|
| 28 |
+
import torch
|
| 29 |
+
|
| 30 |
+
from zonos2.message import TTSSamplingParams
|
| 31 |
+
from zonos2.tokenizer.textnorm import SERVER_TO_NEMO_LANG, TTSTextNormalizer
|
| 32 |
+
from zonos2.tts import TTSLLM
|
| 33 |
+
|
| 34 |
+
MODEL_ID = "Zyphra/ZONOS2"
|
| 35 |
+
SAMPLE_RATE = 44100
|
| 36 |
+
LANGUAGES = [
|
| 37 |
+
("English (US)", "en_us"),
|
| 38 |
+
("English (UK)", "en_gb"),
|
| 39 |
+
("French", "fr_fr"),
|
| 40 |
+
("German", "de"),
|
| 41 |
+
("Spanish", "es"),
|
| 42 |
+
("Italian", "it"),
|
| 43 |
+
("Portuguese (Brazil)", "pt_br"),
|
| 44 |
+
("Japanese", "ja"),
|
| 45 |
+
("Mandarin Chinese", "cmn"),
|
| 46 |
+
("Korean", "ko"),
|
| 47 |
+
]
|
| 48 |
+
SPEAKING_RATE_BUCKETS = [
|
| 49 |
+
("Default", "default"),
|
| 50 |
+
("Very slow", "0"),
|
| 51 |
+
("Slow", "1"),
|
| 52 |
+
("Relaxed", "2"),
|
| 53 |
+
("Natural", "3"),
|
| 54 |
+
("Bright", "4"),
|
| 55 |
+
("Fast", "5"),
|
| 56 |
+
("Very fast", "6"),
|
| 57 |
+
("Extreme", "7"),
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _load_model() -> TTSLLM:
|
| 64 |
+
print(f"Loading {MODEL_ID} for ZeroGPU inference...", flush=True)
|
| 65 |
+
started = time.perf_counter()
|
| 66 |
+
model = TTSLLM(
|
| 67 |
+
model_path=MODEL_ID,
|
| 68 |
+
decode_audio=True,
|
| 69 |
+
cuda_graph_max_bs=0,
|
| 70 |
+
max_running_req=4,
|
| 71 |
+
max_extend_tokens=4096,
|
| 72 |
+
memory_ratio=0.75,
|
| 73 |
+
use_pynccl=False,
|
| 74 |
+
)
|
| 75 |
+
elapsed = time.perf_counter() - started
|
| 76 |
+
print(f"Loaded {MODEL_ID} in {elapsed:.1f}s", flush=True)
|
| 77 |
+
return model
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
TTS = _load_model()
|
| 81 |
+
TEXT_NORMALIZER = TTSTextNormalizer()
|
| 82 |
+
TTS_LOCK = threading.Lock()
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _estimate_duration(*args, **kwargs) -> int:
|
| 86 |
+
max_tokens = kwargs.get("max_tokens")
|
| 87 |
+
if max_tokens is None and len(args) > 4:
|
| 88 |
+
max_tokens = args[4]
|
| 89 |
+
try:
|
| 90 |
+
max_tokens = int(max_tokens)
|
| 91 |
+
except (TypeError, ValueError):
|
| 92 |
+
max_tokens = 768
|
| 93 |
+
return min(180, max(60, 45 + max_tokens // 12))
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def _pcm_float32_to_wav(audio_bytes: bytes, sample_rate: int = SAMPLE_RATE) -> str:
|
| 97 |
+
audio = np.frombuffer(audio_bytes, dtype=np.float32)
|
| 98 |
+
if audio.size == 0:
|
| 99 |
+
raise gr.Error("The model returned no audio. Try increasing max tokens.")
|
| 100 |
+
audio = np.nan_to_num(audio, nan=0.0, posinf=0.0, neginf=0.0)
|
| 101 |
+
audio = np.clip(audio, -1.0, 1.0)
|
| 102 |
+
audio_i16 = (audio * 32767.0).astype(np.int16)
|
| 103 |
+
|
| 104 |
+
handle = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
|
| 105 |
+
handle.close()
|
| 106 |
+
with wave.open(handle.name, "wb") as wav:
|
| 107 |
+
wav.setnchannels(1)
|
| 108 |
+
wav.setsampwidth(2)
|
| 109 |
+
wav.setframerate(sample_rate)
|
| 110 |
+
wav.writeframes(audio_i16.tobytes())
|
| 111 |
+
return handle.name
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _normalize_text(text: str, language: str, enabled: bool) -> str:
|
| 115 |
+
if not enabled:
|
| 116 |
+
return text
|
| 117 |
+
if language not in SERVER_TO_NEMO_LANG:
|
| 118 |
+
return text
|
| 119 |
+
return TEXT_NORMALIZER.normalize(text, language)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _speaking_rate_bucket(value: str) -> int | None:
|
| 123 |
+
if value == "default":
|
| 124 |
+
return None
|
| 125 |
+
return int(value)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
@spaces.GPU(duration=_estimate_duration)
|
| 129 |
+
def synthesize(
|
| 130 |
+
text: str,
|
| 131 |
+
language: str,
|
| 132 |
+
text_normalization: bool,
|
| 133 |
+
speaking_rate: str,
|
| 134 |
+
max_tokens: int,
|
| 135 |
+
temperature: float,
|
| 136 |
+
topk: int,
|
| 137 |
+
top_p: float,
|
| 138 |
+
min_p: float,
|
| 139 |
+
repetition_penalty: float,
|
| 140 |
+
seed: int,
|
| 141 |
+
):
|
| 142 |
+
text = (text or "").strip()
|
| 143 |
+
if not text:
|
| 144 |
+
raise gr.Error("Enter text to synthesize.")
|
| 145 |
+
if len(text) > 1200:
|
| 146 |
+
raise gr.Error("Keep the prompt under 1200 characters for this Space.")
|
| 147 |
+
|
| 148 |
+
normalized = _normalize_text(text, language, text_normalization)
|
| 149 |
+
params = TTSSamplingParams(
|
| 150 |
+
temperature=float(temperature),
|
| 151 |
+
topk=int(topk),
|
| 152 |
+
top_p=float(top_p),
|
| 153 |
+
min_p=float(min_p),
|
| 154 |
+
max_tokens=int(max_tokens),
|
| 155 |
+
repetition_window=50,
|
| 156 |
+
repetition_penalty=float(repetition_penalty),
|
| 157 |
+
repetition_codebooks=8,
|
| 158 |
+
seed=None if seed is None or int(seed) < 0 else int(seed),
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
started = time.perf_counter()
|
| 162 |
+
with TTS_LOCK:
|
| 163 |
+
torch.cuda.set_stream(TTS.stream)
|
| 164 |
+
result = TTS.generate_one(
|
| 165 |
+
normalized,
|
| 166 |
+
params,
|
| 167 |
+
decode_audio=True,
|
| 168 |
+
speaking_rate_bucket=_speaking_rate_bucket(speaking_rate),
|
| 169 |
+
quality_buckets=None,
|
| 170 |
+
)
|
| 171 |
+
elapsed = time.perf_counter() - started
|
| 172 |
+
|
| 173 |
+
wav_path = _pcm_float32_to_wav(result["audio"], result.get("sample_rate", SAMPLE_RATE))
|
| 174 |
+
frames = len(result.get("audio_tokens") or [])
|
| 175 |
+
eos_frame = result.get("eos_frame")
|
| 176 |
+
status = f"Generated {frames} frames in {elapsed:.1f}s"
|
| 177 |
+
if eos_frame is not None:
|
| 178 |
+
status += f" (EOS frame {eos_frame})"
|
| 179 |
+
if normalized != text:
|
| 180 |
+
status += f"\n\nNormalized text: {normalized}"
|
| 181 |
+
return wav_path, status
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
CSS = """
|
| 185 |
+
main, .gradio-container, .gradio-container > .fillable {
|
| 186 |
+
max-width: 1180px !important;
|
| 187 |
+
margin-inline: auto !important;
|
| 188 |
+
}
|
| 189 |
+
.compact-status textarea {
|
| 190 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
| 191 |
+
}
|
| 192 |
+
"""
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
with gr.Blocks(title="ZONOS2", css=CSS) as demo:
|
| 196 |
+
gr.Markdown("# ZONOS2")
|
| 197 |
+
with gr.Row():
|
| 198 |
+
with gr.Column(scale=5):
|
| 199 |
+
text = gr.Textbox(
|
| 200 |
+
label="Text",
|
| 201 |
+
value="In the quiet hum of the studio, ZONOS2 turns written words into natural speech.",
|
| 202 |
+
lines=6,
|
| 203 |
+
max_length=1200,
|
| 204 |
+
)
|
| 205 |
+
with gr.Row():
|
| 206 |
+
language = gr.Dropdown(
|
| 207 |
+
choices=LANGUAGES,
|
| 208 |
+
value="en_us",
|
| 209 |
+
label="Language",
|
| 210 |
+
)
|
| 211 |
+
speaking_rate = gr.Dropdown(
|
| 212 |
+
choices=SPEAKING_RATE_BUCKETS,
|
| 213 |
+
value="default",
|
| 214 |
+
label="Speaking rate",
|
| 215 |
+
)
|
| 216 |
+
text_normalization = gr.Checkbox(value=True, label="Text normalization")
|
| 217 |
+
generate = gr.Button("Generate", variant="primary")
|
| 218 |
+
with gr.Column(scale=4):
|
| 219 |
+
audio = gr.Audio(label="Audio", type="filepath", format="wav")
|
| 220 |
+
status = gr.Textbox(
|
| 221 |
+
label="Status",
|
| 222 |
+
lines=5,
|
| 223 |
+
interactive=False,
|
| 224 |
+
elem_classes=["compact-status"],
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
with gr.Accordion("Sampling", open=False):
|
| 228 |
+
with gr.Row():
|
| 229 |
+
max_tokens = gr.Slider(
|
| 230 |
+
minimum=128,
|
| 231 |
+
maximum=2048,
|
| 232 |
+
step=64,
|
| 233 |
+
value=768,
|
| 234 |
+
label="Max audio tokens",
|
| 235 |
+
)
|
| 236 |
+
seed = gr.Number(value=-1, precision=0, label="Seed (-1 random)")
|
| 237 |
+
with gr.Row():
|
| 238 |
+
temperature = gr.Slider(0.1, 2.0, value=1.15, step=0.05, label="Temperature")
|
| 239 |
+
topk = gr.Slider(1, 512, value=106, step=1, label="Top-k")
|
| 240 |
+
with gr.Row():
|
| 241 |
+
top_p = gr.Slider(0.0, 1.0, value=0.0, step=0.01, label="Top-p")
|
| 242 |
+
min_p = gr.Slider(0.0, 0.5, value=0.18, step=0.01, label="Min-p")
|
| 243 |
+
repetition_penalty = gr.Slider(
|
| 244 |
+
1.0,
|
| 245 |
+
2.0,
|
| 246 |
+
value=1.2,
|
| 247 |
+
step=0.05,
|
| 248 |
+
label="Repetition penalty",
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
gr.Examples(
|
| 252 |
+
examples=[
|
| 253 |
+
[
|
| 254 |
+
"The first explorers landed just after sunrise, carrying maps, coffee, and impossible optimism.",
|
| 255 |
+
"en_us",
|
| 256 |
+
True,
|
| 257 |
+
"default",
|
| 258 |
+
512,
|
| 259 |
+
1.15,
|
| 260 |
+
106,
|
| 261 |
+
0.0,
|
| 262 |
+
0.18,
|
| 263 |
+
1.2,
|
| 264 |
+
-1,
|
| 265 |
+
],
|
| 266 |
+
[
|
| 267 |
+
"Le modèle parle avec une voix claire, expressive et naturellement rythmée.",
|
| 268 |
+
"fr_fr",
|
| 269 |
+
True,
|
| 270 |
+
"default",
|
| 271 |
+
512,
|
| 272 |
+
1.15,
|
| 273 |
+
106,
|
| 274 |
+
0.0,
|
| 275 |
+
0.18,
|
| 276 |
+
1.2,
|
| 277 |
+
-1,
|
| 278 |
+
],
|
| 279 |
+
],
|
| 280 |
+
inputs=[
|
| 281 |
+
text,
|
| 282 |
+
language,
|
| 283 |
+
text_normalization,
|
| 284 |
+
speaking_rate,
|
| 285 |
+
max_tokens,
|
| 286 |
+
temperature,
|
| 287 |
+
topk,
|
| 288 |
+
top_p,
|
| 289 |
+
min_p,
|
| 290 |
+
repetition_penalty,
|
| 291 |
+
seed,
|
| 292 |
+
],
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
generate.click(
|
| 296 |
+
fn=synthesize,
|
| 297 |
+
inputs=[
|
| 298 |
+
text,
|
| 299 |
+
language,
|
| 300 |
+
text_normalization,
|
| 301 |
+
speaking_rate,
|
| 302 |
+
max_tokens,
|
| 303 |
+
temperature,
|
| 304 |
+
topk,
|
| 305 |
+
top_p,
|
| 306 |
+
min_p,
|
| 307 |
+
repetition_penalty,
|
| 308 |
+
seed,
|
| 309 |
+
],
|
| 310 |
+
outputs=[audio, status],
|
| 311 |
+
api_name="generate",
|
| 312 |
+
concurrency_limit=1,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
if __name__ == "__main__":
|
| 317 |
+
demo.queue(max_size=8, default_concurrency_limit=1).launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=6.10.0
|
| 2 |
+
spaces>=0.41.0
|
| 3 |
+
numpy
|
| 4 |
+
tqdm
|
| 5 |
+
msgpack
|
| 6 |
+
sgl_kernel>=0.3.17.post1
|
| 7 |
+
descript-audio-codec==1.0.0
|
| 8 |
+
transformers>=4.56.0,<=4.57.3
|
| 9 |
+
huggingface_hub
|
| 10 |
+
pyzmq
|
| 11 |
+
apache-tvm-ffi>=0.1.4
|
| 12 |
+
nvidia-cutlass-dsl==4.3.1
|
| 13 |
+
flashinfer-cubin==0.5.3
|
| 14 |
+
flashinfer-python>=0.5.3
|
| 15 |
+
flashinfer-jit-cache @ https://github.com/flashinfer-ai/flashinfer/releases/download/v0.5.3/flashinfer_jit_cache-0.5.3+cu128-cp39-abi3-manylinux_2_28_x86_64.whl
|
| 16 |
+
ninja>=1.13.0
|
| 17 |
+
kernels>=0.12.1
|
| 18 |
+
pynini==2.1.6
|
| 19 |
+
sacremoses>=0.1.1
|
zonos2/__main__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .server import launch_server
|
| 2 |
+
|
| 3 |
+
assert __name__ == "__main__"
|
| 4 |
+
|
| 5 |
+
launch_server()
|
zonos2/attention/__init__.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import TYPE_CHECKING, Protocol
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from zonos2.utils import Registry, init_logger, is_sm90_supported, is_sm100_supported
|
| 7 |
+
|
| 8 |
+
from .base import BaseAttnBackend, BaseAttnMetadata, HybridBackend
|
| 9 |
+
|
| 10 |
+
if TYPE_CHECKING:
|
| 11 |
+
from zonos2.kvcache import BaseKVCache
|
| 12 |
+
from zonos2.models import ModelConfig
|
| 13 |
+
|
| 14 |
+
logger = init_logger(__name__)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class BackendCreator(Protocol):
|
| 18 |
+
def __call__(
|
| 19 |
+
self, config: ModelConfig, kvcache: BaseKVCache, page_table: torch.Tensor
|
| 20 |
+
) -> BaseAttnBackend: ...
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
SUPPORTED_ATTENTION_BACKENDS = Registry[BackendCreator]("Attention Backend")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def resolve_auto_backend(config: ModelConfig) -> str:
|
| 27 |
+
"""Determine the best attention backend based on the GPU architecture and model."""
|
| 28 |
+
if is_sm100_supported(): # blackwell
|
| 29 |
+
return "fi"
|
| 30 |
+
elif is_sm90_supported(): # hopper
|
| 31 |
+
return "fa,fi"
|
| 32 |
+
else: # pre-hopper
|
| 33 |
+
return "fi"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@SUPPORTED_ATTENTION_BACKENDS.register("fi")
|
| 37 |
+
def create_fi_backend(config: ModelConfig, kvcache: BaseKVCache, page_table: torch.Tensor):
|
| 38 |
+
from .fi import FlashInferBackend
|
| 39 |
+
|
| 40 |
+
return FlashInferBackend(config, kvcache, page_table)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@SUPPORTED_ATTENTION_BACKENDS.register("fa")
|
| 44 |
+
def create_fa_backend(config: ModelConfig, kvcache: BaseKVCache, page_table: torch.Tensor):
|
| 45 |
+
from .fa import FlashAttentionBackend
|
| 46 |
+
|
| 47 |
+
return FlashAttentionBackend(config, kvcache, page_table)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def validate_backend(backend: str):
|
| 51 |
+
if backend != "auto":
|
| 52 |
+
required_backends = backend.split(",") if "," in backend else [backend]
|
| 53 |
+
supported = SUPPORTED_ATTENTION_BACKENDS.supported_names()
|
| 54 |
+
for b in required_backends:
|
| 55 |
+
if b not in supported:
|
| 56 |
+
from argparse import ArgumentTypeError
|
| 57 |
+
|
| 58 |
+
raise ArgumentTypeError(
|
| 59 |
+
f"Unsupported attention backend: {b}. Supported backends: {supported}"
|
| 60 |
+
)
|
| 61 |
+
return backend
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def create_attention_backend(
|
| 65 |
+
backend: str,
|
| 66 |
+
config: ModelConfig,
|
| 67 |
+
kvcache: BaseKVCache,
|
| 68 |
+
page_table: torch.Tensor,
|
| 69 |
+
) -> BaseAttnBackend:
|
| 70 |
+
if backend == "auto":
|
| 71 |
+
backend = resolve_auto_backend(config)
|
| 72 |
+
logger.info(f"Auto-selected attention backend: {backend}")
|
| 73 |
+
|
| 74 |
+
if "," in backend:
|
| 75 |
+
assert backend.count(",") == 1, "Only one comma is allowed in hybrid backend"
|
| 76 |
+
p_backend, d_backend = backend.split(",", 1)
|
| 77 |
+
if p_backend != d_backend:
|
| 78 |
+
logger.info(f"Using hybrid attention backend: prefill={p_backend}, decode={d_backend}")
|
| 79 |
+
p_backend = create_attention_backend(p_backend, config, kvcache, page_table)
|
| 80 |
+
d_backend = create_attention_backend(d_backend, config, kvcache, page_table)
|
| 81 |
+
return HybridBackend(p_backend, d_backend)
|
| 82 |
+
backend = p_backend # both are the same, fall through to single backend
|
| 83 |
+
logger.warning(f"P/D attention backends are the same: {backend}, using single backend.")
|
| 84 |
+
|
| 85 |
+
return SUPPORTED_ATTENTION_BACKENDS[backend](config, kvcache, page_table)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
__all__ = [
|
| 89 |
+
"BaseAttnMetadata",
|
| 90 |
+
"BaseAttnBackend",
|
| 91 |
+
"create_attention_backend",
|
| 92 |
+
"SUPPORTED_ATTENTION_BACKENDS",
|
| 93 |
+
"validate_backend",
|
| 94 |
+
]
|
zonos2/attention/base.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from abc import ABC, abstractmethod
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import TYPE_CHECKING, List
|
| 6 |
+
|
| 7 |
+
if TYPE_CHECKING:
|
| 8 |
+
import torch
|
| 9 |
+
from zonos2.core import TTSBatch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class BaseAttnMetadata(ABC):
|
| 14 |
+
positions: torch.Tensor
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def get_last_indices(self, bs: int) -> torch.Tensor: ...
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class BaseAttnBackend(ABC):
|
| 21 |
+
@abstractmethod
|
| 22 |
+
def forward(
|
| 23 |
+
self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, layer_id: int, batch: TTSBatch
|
| 24 |
+
) -> torch.Tensor: ...
|
| 25 |
+
|
| 26 |
+
@abstractmethod
|
| 27 |
+
def prepare_metadata(self, batch: TTSBatch) -> None: ...
|
| 28 |
+
|
| 29 |
+
@abstractmethod
|
| 30 |
+
def init_capture_graph(
|
| 31 |
+
self, max_seq_len: int, bs_list: List[int], frame_width: int = 1
|
| 32 |
+
) -> None: ...
|
| 33 |
+
|
| 34 |
+
@abstractmethod
|
| 35 |
+
def prepare_for_capture(self, batch: TTSBatch) -> None: ...
|
| 36 |
+
|
| 37 |
+
@abstractmethod
|
| 38 |
+
def prepare_for_replay(self, batch: TTSBatch) -> None: ...
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class HybridBackend(BaseAttnBackend):
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
prefill_backend: BaseAttnBackend,
|
| 45 |
+
decode_backend: BaseAttnBackend,
|
| 46 |
+
) -> None:
|
| 47 |
+
self.prefill_backend = prefill_backend
|
| 48 |
+
self.decode_backend = decode_backend
|
| 49 |
+
|
| 50 |
+
def forward(
|
| 51 |
+
self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, layer_id: int, batch: TTSBatch
|
| 52 |
+
) -> torch.Tensor:
|
| 53 |
+
backend = self.prefill_backend if batch.is_prefill else self.decode_backend
|
| 54 |
+
return backend.forward(q, k, v, layer_id, batch)
|
| 55 |
+
|
| 56 |
+
def prepare_metadata(self, batch: TTSBatch) -> None:
|
| 57 |
+
backend = self.prefill_backend if batch.is_prefill else self.decode_backend
|
| 58 |
+
return backend.prepare_metadata(batch)
|
| 59 |
+
|
| 60 |
+
def init_capture_graph(
|
| 61 |
+
self, max_seq_len: int, bs_list: List[int], frame_width: int = 1
|
| 62 |
+
) -> None:
|
| 63 |
+
self.decode_backend.init_capture_graph(max_seq_len, bs_list, frame_width)
|
| 64 |
+
|
| 65 |
+
def prepare_for_capture(self, batch: TTSBatch) -> None:
|
| 66 |
+
self.decode_backend.prepare_for_capture(batch)
|
| 67 |
+
|
| 68 |
+
def prepare_for_replay(self, batch: TTSBatch) -> None:
|
| 69 |
+
self.decode_backend.prepare_for_replay(batch)
|
zonos2/attention/fa.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import TYPE_CHECKING, List, Tuple
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from .base import BaseAttnBackend, BaseAttnMetadata
|
| 9 |
+
from .utils import BaseCaptureData, make_positions
|
| 10 |
+
|
| 11 |
+
if TYPE_CHECKING:
|
| 12 |
+
from zonos2.core import TTSBatch
|
| 13 |
+
from zonos2.kvcache import BaseKVCache
|
| 14 |
+
from zonos2.models import ModelConfig
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass
|
| 18 |
+
class FACaptureData(BaseCaptureData):
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@dataclass
|
| 23 |
+
class FAMetadata(BaseAttnMetadata):
|
| 24 |
+
cu_seqlens_k: torch.Tensor
|
| 25 |
+
cu_seqlens_q: torch.Tensor
|
| 26 |
+
cache_seqlens: torch.Tensor
|
| 27 |
+
max_seqlen_k: int
|
| 28 |
+
max_seqlen_q: int
|
| 29 |
+
|
| 30 |
+
page_table: torch.Tensor
|
| 31 |
+
|
| 32 |
+
def get_positions(self) -> torch.Tensor:
|
| 33 |
+
return self.positions
|
| 34 |
+
|
| 35 |
+
def get_last_indices(self, bs: int) -> torch.Tensor:
|
| 36 |
+
return self.cu_seqlens_q[1 : 1 + bs] - 1
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class FlashAttentionBackend(BaseAttnBackend):
|
| 40 |
+
def __init__(self, config: ModelConfig, kvcache: BaseKVCache, page_table: torch.Tensor):
|
| 41 |
+
self.config = config
|
| 42 |
+
self.kvcache = kvcache
|
| 43 |
+
self.capture: FACaptureData | None = None
|
| 44 |
+
self.max_graph_bs = 0
|
| 45 |
+
self.capture_bs: List[int] = []
|
| 46 |
+
self.scale = config.head_dim**-0.5
|
| 47 |
+
self.page_table = page_table
|
| 48 |
+
|
| 49 |
+
def forward(
|
| 50 |
+
self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, layer_id: int, batch: TTSBatch
|
| 51 |
+
) -> torch.Tensor:
|
| 52 |
+
metadata = batch.attn_metadata
|
| 53 |
+
assert isinstance(metadata, FAMetadata)
|
| 54 |
+
self.kvcache.store_kv(k, v, batch.out_loc, layer_id)
|
| 55 |
+
return _fa_sgl_impl(
|
| 56 |
+
q=q,
|
| 57 |
+
k_cache=self.kvcache.k_cache(layer_id),
|
| 58 |
+
v_cache=self.kvcache.v_cache(layer_id),
|
| 59 |
+
page_table=metadata.page_table,
|
| 60 |
+
cache_seqlens=metadata.cache_seqlens,
|
| 61 |
+
cu_seqlens_q=metadata.cu_seqlens_q,
|
| 62 |
+
cu_seqlens_k_new=metadata.cu_seqlens_k,
|
| 63 |
+
max_seqlen_q=metadata.max_seqlen_q,
|
| 64 |
+
softmax_scale=self.scale,
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def prepare_metadata(self, batch: TTSBatch) -> None:
|
| 68 |
+
reqs = batch.padded_reqs
|
| 69 |
+
|
| 70 |
+
padded_size = len(reqs)
|
| 71 |
+
seqlens_q = [req.extend_len for req in reqs]
|
| 72 |
+
seqlens_k = [req.device_len for req in reqs]
|
| 73 |
+
cached_lens = [req.cached_len for req in reqs]
|
| 74 |
+
max_seqlen_k = max(seqlens_k)
|
| 75 |
+
max_seqlen_q = max(seqlens_q)
|
| 76 |
+
cpu_kwargs = {"device": "cpu", "dtype": torch.int32, "pin_memory": True}
|
| 77 |
+
|
| 78 |
+
device = self.kvcache.device
|
| 79 |
+
cache_seqlens = torch.tensor(seqlens_k, **cpu_kwargs)
|
| 80 |
+
cache_seqlens = cache_seqlens.to(device, non_blocking=True)
|
| 81 |
+
cu_seqlens_k = torch.tensor([0] + seqlens_k, **cpu_kwargs).cumsum_(dim=0)
|
| 82 |
+
cu_seqlens_k = cu_seqlens_k.to(device, non_blocking=True)
|
| 83 |
+
|
| 84 |
+
if max_seqlen_q == 1:
|
| 85 |
+
cu_seqlens_q = torch.arange(0, padded_size + 1, device=device, dtype=torch.int32)
|
| 86 |
+
elif all(l == 0 for l in cached_lens): # prefill with no cache hit
|
| 87 |
+
cu_seqlens_q = cu_seqlens_k
|
| 88 |
+
else: # normal extend prefill, with partial cache hit
|
| 89 |
+
cu_seqlens_q = torch.tensor([0] + seqlens_q, **cpu_kwargs).cumsum_(dim=0)
|
| 90 |
+
cu_seqlens_q = cu_seqlens_q.to(self.kvcache.device, non_blocking=True)
|
| 91 |
+
|
| 92 |
+
positions = make_positions(device, reqs)
|
| 93 |
+
page_table = self.page_table
|
| 94 |
+
new_page_table = torch.stack([page_table[req.table_idx, :max_seqlen_k] for req in reqs])
|
| 95 |
+
|
| 96 |
+
# copy from CPU to GPU
|
| 97 |
+
batch.attn_metadata = FAMetadata(
|
| 98 |
+
cu_seqlens_k=cu_seqlens_k,
|
| 99 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 100 |
+
positions=positions,
|
| 101 |
+
cache_seqlens=cache_seqlens,
|
| 102 |
+
max_seqlen_k=max_seqlen_k,
|
| 103 |
+
max_seqlen_q=max_seqlen_q,
|
| 104 |
+
page_table=new_page_table,
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
def init_capture_graph(
|
| 108 |
+
self, max_seq_len: int, bs_list: List[int], frame_width: int = 1
|
| 109 |
+
) -> None:
|
| 110 |
+
assert self.capture is None, "Capture already initialized."
|
| 111 |
+
max_bs = max(bs_list)
|
| 112 |
+
capture = FACaptureData.create(max_bs, max_seq_len, self.kvcache.device, frame_width)
|
| 113 |
+
self.max_graph_bs = max_bs
|
| 114 |
+
self.capture = capture
|
| 115 |
+
self.capture_bs = sorted(bs_list)
|
| 116 |
+
|
| 117 |
+
def prepare_for_capture(self, batch: TTSBatch) -> None:
|
| 118 |
+
assert (bs := batch.size) in self.capture_bs and self.capture
|
| 119 |
+
capture = self.capture
|
| 120 |
+
metadata = FAMetadata(
|
| 121 |
+
cu_seqlens_k=capture.cu_seqlens_k[: bs + 1],
|
| 122 |
+
cu_seqlens_q=capture.cu_seqlens_q[: bs + 1],
|
| 123 |
+
positions=capture.positions[:bs],
|
| 124 |
+
cache_seqlens=capture.seq_lens[:bs],
|
| 125 |
+
max_seqlen_k=capture.page_table.size(1),
|
| 126 |
+
max_seqlen_q=1, # decode only
|
| 127 |
+
page_table=capture.page_table[:bs, :],
|
| 128 |
+
)
|
| 129 |
+
batch.attn_metadata = metadata
|
| 130 |
+
batch.input_ids = capture.input_ids[:bs]
|
| 131 |
+
batch.out_loc = capture.out_loc[:bs]
|
| 132 |
+
|
| 133 |
+
def prepare_for_replay(self, batch: TTSBatch) -> None:
|
| 134 |
+
metadata, bs = batch.attn_metadata, batch.padded_size
|
| 135 |
+
assert isinstance(metadata, FAMetadata)
|
| 136 |
+
assert self.capture is not None and bs in self.capture_bs
|
| 137 |
+
capture = self.capture
|
| 138 |
+
|
| 139 |
+
# Copy all dynamic tensors to capture buffers (fixed memory addresses for CUDA graph)
|
| 140 |
+
capture.input_ids[:bs].copy_(batch.input_ids)
|
| 141 |
+
capture.out_loc[:bs].copy_(batch.out_loc)
|
| 142 |
+
capture.positions[:bs].copy_(metadata.positions)
|
| 143 |
+
capture.cu_seqlens_k[: bs + 1].copy_(metadata.cu_seqlens_k)
|
| 144 |
+
capture.seq_lens[:bs].copy_(metadata.cache_seqlens)
|
| 145 |
+
capture.page_table[:bs, : metadata.max_seqlen_k].copy_(metadata.page_table)
|
| 146 |
+
|
| 147 |
+
# Update metadata and batch to point to capture buffers
|
| 148 |
+
# This ensures CUDA graph uses fixed memory addresses for all inputs
|
| 149 |
+
metadata.positions = capture.positions[:bs]
|
| 150 |
+
metadata.cu_seqlens_k = capture.cu_seqlens_k[: bs + 1]
|
| 151 |
+
metadata.cache_seqlens = capture.seq_lens[:bs]
|
| 152 |
+
metadata.page_table = capture.page_table[:bs, :]
|
| 153 |
+
batch.input_ids = capture.input_ids[:bs]
|
| 154 |
+
batch.out_loc = capture.out_loc[:bs]
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def _fa_sgl_impl(
|
| 158 |
+
q: torch.Tensor,
|
| 159 |
+
k_cache: torch.Tensor,
|
| 160 |
+
v_cache: torch.Tensor,
|
| 161 |
+
page_table: torch.Tensor,
|
| 162 |
+
cache_seqlens: torch.Tensor,
|
| 163 |
+
cu_seqlens_q: torch.Tensor,
|
| 164 |
+
cu_seqlens_k_new: torch.Tensor,
|
| 165 |
+
max_seqlen_q: int,
|
| 166 |
+
softmax_scale: float,
|
| 167 |
+
sm_margin: int = 0,
|
| 168 |
+
window_size: Tuple[int, int] = (-1, -1), # -1 means infinite context window
|
| 169 |
+
softcap: float = 0.0, # 0.0 means deactivated
|
| 170 |
+
num_splits: int = 0, # Can be tuned for speed
|
| 171 |
+
pack_gqa: bool | None = None, # Can be tuned for speed
|
| 172 |
+
causal: bool = True,
|
| 173 |
+
) -> torch.Tensor:
|
| 174 |
+
try:
|
| 175 |
+
from sgl_kernel.flash_attn import flash_attn_with_kvcache
|
| 176 |
+
except ImportError as e:
|
| 177 |
+
raise ImportError(
|
| 178 |
+
"sgl_kernel.flash_attn is not found. Please install it with `pip install sgl-kernel`.\n"
|
| 179 |
+
"If you're sure it's correctly installed, try `apt update && apt install libnuma1`."
|
| 180 |
+
) from e
|
| 181 |
+
|
| 182 |
+
return flash_attn_with_kvcache( # type: ignore
|
| 183 |
+
q=q,
|
| 184 |
+
k_cache=k_cache,
|
| 185 |
+
v_cache=v_cache,
|
| 186 |
+
page_table=page_table,
|
| 187 |
+
cache_seqlens=cache_seqlens,
|
| 188 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 189 |
+
cu_seqlens_k_new=cu_seqlens_k_new,
|
| 190 |
+
max_seqlen_q=max_seqlen_q,
|
| 191 |
+
softmax_scale=softmax_scale,
|
| 192 |
+
sm_margin=sm_margin,
|
| 193 |
+
window_size=window_size,
|
| 194 |
+
softcap=softcap,
|
| 195 |
+
num_splits=num_splits,
|
| 196 |
+
pack_gqa=pack_gqa,
|
| 197 |
+
causal=causal,
|
| 198 |
+
ver=3, # TODO: support FA4 on blackwell
|
| 199 |
+
)
|
zonos2/attention/fi.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
import math
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from functools import cached_property
|
| 7 |
+
from typing import TYPE_CHECKING, Dict, List, Literal
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from zonos2.distributed import get_tp_info
|
| 11 |
+
from zonos2.env import ENV
|
| 12 |
+
from zonos2.utils import divide_even
|
| 13 |
+
from zonos2.utils.logger import init_logger
|
| 14 |
+
|
| 15 |
+
from .base import BaseAttnBackend, BaseAttnMetadata
|
| 16 |
+
from .utils import BaseCaptureData, make_positions
|
| 17 |
+
|
| 18 |
+
if TYPE_CHECKING:
|
| 19 |
+
from flashinfer import (
|
| 20 |
+
BatchDecodeWithPagedKVCacheWrapper,
|
| 21 |
+
BatchPrefillWithPagedKVCacheWrapper,
|
| 22 |
+
CUDAGraphBatchDecodeWithPagedKVCacheWrapper,
|
| 23 |
+
)
|
| 24 |
+
from zonos2.core import TTSBatch
|
| 25 |
+
from zonos2.kvcache import BaseKVCache
|
| 26 |
+
from zonos2.models import ModelConfig
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _next_power_of_2(n: int) -> int:
|
| 30 |
+
if n <= 1:
|
| 31 |
+
return 1
|
| 32 |
+
return 1 << math.ceil(math.log2(n))
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
logger = init_logger(__name__)
|
| 36 |
+
|
| 37 |
+
_fi_debug_counter = 0
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass
|
| 41 |
+
class FICaptureData(BaseCaptureData):
|
| 42 |
+
@property
|
| 43 |
+
def one_tensor(self) -> torch.Tensor:
|
| 44 |
+
return self.seq_lens
|
| 45 |
+
|
| 46 |
+
@property
|
| 47 |
+
def indices(self) -> torch.Tensor:
|
| 48 |
+
return self.page_table
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dataclass
|
| 52 |
+
class FIMetadata(BaseAttnMetadata):
|
| 53 |
+
# fmt: off
|
| 54 |
+
cu_seqlens_q_cpu: torch.Tensor # on cpu
|
| 55 |
+
cu_seqlens_k_cpu: torch.Tensor # on cpu
|
| 56 |
+
cu_seqlens_q_gpu: torch.Tensor # on gpu
|
| 57 |
+
indices: torch.Tensor # on gpu
|
| 58 |
+
last_page_len_cpu: torch.Tensor # on cpu
|
| 59 |
+
num_qo_heads: int
|
| 60 |
+
num_kv_heads: int
|
| 61 |
+
head_dim: int
|
| 62 |
+
page_size: Literal[1] # currently only support page_size=1
|
| 63 |
+
pos_encoding_mode: str
|
| 64 |
+
seq_lens_cpu: torch.Tensor # on cpu
|
| 65 |
+
dtype: torch.dtype
|
| 66 |
+
wrapper: BatchPrefillWithPagedKVCacheWrapper | BatchDecodeWithPagedKVCacheWrapper
|
| 67 |
+
initialized: bool = False
|
| 68 |
+
sm_scale: float = 0.0 # softmax scale (1/sqrt(head_dim)), set during prepare_metadata
|
| 69 |
+
# fmt: on
|
| 70 |
+
|
| 71 |
+
def __post_init__(self) -> None:
|
| 72 |
+
assert self.page_size == 1, "Currently only page_size=1 is supported."
|
| 73 |
+
assert (
|
| 74 |
+
self.positions.is_cuda
|
| 75 |
+
and self.cu_seqlens_k_cpu.is_cpu
|
| 76 |
+
and self.cu_seqlens_q_cpu.is_cpu
|
| 77 |
+
and self.cu_seqlens_q_gpu.is_cuda
|
| 78 |
+
and self.indices.is_cuda
|
| 79 |
+
and self.last_page_len_cpu.is_cpu
|
| 80 |
+
and self.seq_lens_cpu.is_cpu
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def get_positions(self) -> torch.Tensor:
|
| 84 |
+
return self.positions
|
| 85 |
+
|
| 86 |
+
def get_last_indices(self, bs: int) -> torch.Tensor:
|
| 87 |
+
return self.cu_seqlens_q_gpu[1 : 1 + bs] - 1
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class FlashInferBackend(BaseAttnBackend):
|
| 91 |
+
def __init__(
|
| 92 |
+
self,
|
| 93 |
+
config: ModelConfig,
|
| 94 |
+
kvcache: BaseKVCache,
|
| 95 |
+
page_table: torch.Tensor,
|
| 96 |
+
) -> None:
|
| 97 |
+
from flashinfer import (
|
| 98 |
+
BatchDecodeWithPagedKVCacheWrapper,
|
| 99 |
+
BatchPrefillWithPagedKVCacheWrapper,
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
self.config = config
|
| 103 |
+
self.kvcache = kvcache
|
| 104 |
+
self.device = kvcache.device
|
| 105 |
+
self.float_workspace_buffer = torch.empty(
|
| 106 |
+
128 * 1024 * 1024, dtype=torch.uint8, device=self.device
|
| 107 |
+
)
|
| 108 |
+
self.prefill_wrapper = BatchPrefillWithPagedKVCacheWrapper(
|
| 109 |
+
self.float_workspace_buffer,
|
| 110 |
+
kv_layout="NHD",
|
| 111 |
+
backend="fa2", # flashinfer fa3 is buggy, use fa2 instead
|
| 112 |
+
)
|
| 113 |
+
self.decode_wrappers = BatchDecodeWithPagedKVCacheWrapper(
|
| 114 |
+
self.float_workspace_buffer,
|
| 115 |
+
kv_layout="NHD",
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
# NOTE: some hack to reuse the int_workspace_buffer
|
| 119 |
+
self.int_workspace_buffer = self.prefill_wrapper._int_workspace_buffer
|
| 120 |
+
self.decode_wrappers._int_workspace_buffer = self.int_workspace_buffer
|
| 121 |
+
|
| 122 |
+
# initialize some data members
|
| 123 |
+
tp_size = get_tp_info().size
|
| 124 |
+
self.qo_head_local = divide_even(self.config.num_qo_heads, tp_size)
|
| 125 |
+
self.kv_head_local = divide_even(self.config.num_kv_heads, tp_size)
|
| 126 |
+
|
| 127 |
+
self.cached_ones_cpu: torch.Tensor = torch.tensor([], dtype=torch.int32, pin_memory=True)
|
| 128 |
+
# for cuda graph
|
| 129 |
+
self.capture_bs: List[int] = []
|
| 130 |
+
self.max_graph_bs = 0
|
| 131 |
+
self.graph_wrappers: Dict[int, CUDAGraphBatchDecodeWithPagedKVCacheWrapper] = {}
|
| 132 |
+
self.capture: FICaptureData | None = None
|
| 133 |
+
self.page_table = page_table
|
| 134 |
+
|
| 135 |
+
@staticmethod
|
| 136 |
+
def _initialize_metadata_once(metadata: FIMetadata) -> None:
|
| 137 |
+
if metadata.initialized:
|
| 138 |
+
return
|
| 139 |
+
|
| 140 |
+
from flashinfer import (
|
| 141 |
+
BatchDecodeWithPagedKVCacheWrapper,
|
| 142 |
+
CUDAGraphBatchDecodeWithPagedKVCacheWrapper,
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
metadata.initialized = True
|
| 146 |
+
|
| 147 |
+
is_cuda_graph = isinstance(metadata.wrapper, CUDAGraphBatchDecodeWithPagedKVCacheWrapper)
|
| 148 |
+
is_decode = isinstance(metadata.wrapper, BatchDecodeWithPagedKVCacheWrapper) or is_cuda_graph
|
| 149 |
+
|
| 150 |
+
if is_cuda_graph and logger.isEnabledFor(logging.DEBUG):
|
| 151 |
+
global _fi_debug_counter
|
| 152 |
+
if _fi_debug_counter <= 5:
|
| 153 |
+
logger.debug("plan() for CUDAGraphWrapper: indices shape=%s", metadata.indices.shape)
|
| 154 |
+
logger.debug(" cu_seqlens_k_cpu=%s", metadata.cu_seqlens_k_cpu.tolist())
|
| 155 |
+
|
| 156 |
+
if is_decode:
|
| 157 |
+
# Decode wrapper (both regular and CUDA graph variants)
|
| 158 |
+
# sm_scale must be passed to plan() for decode, not run()
|
| 159 |
+
metadata.wrapper.plan(
|
| 160 |
+
indptr=metadata.cu_seqlens_k_cpu,
|
| 161 |
+
indices=metadata.indices,
|
| 162 |
+
last_page_len=metadata.last_page_len_cpu,
|
| 163 |
+
num_qo_heads=metadata.num_qo_heads,
|
| 164 |
+
num_kv_heads=metadata.num_kv_heads,
|
| 165 |
+
head_dim=metadata.head_dim,
|
| 166 |
+
page_size=metadata.page_size,
|
| 167 |
+
pos_encoding_mode=metadata.pos_encoding_mode,
|
| 168 |
+
seq_lens=metadata.seq_lens_cpu,
|
| 169 |
+
data_type=metadata.dtype,
|
| 170 |
+
q_data_type=metadata.dtype,
|
| 171 |
+
kv_data_type=metadata.dtype,
|
| 172 |
+
sm_scale=metadata.sm_scale,
|
| 173 |
+
non_blocking=True,
|
| 174 |
+
)
|
| 175 |
+
else:
|
| 176 |
+
# Prefill wrapper - sm_scale passed to plan()
|
| 177 |
+
metadata.wrapper.plan(
|
| 178 |
+
qo_indptr=metadata.cu_seqlens_q_cpu,
|
| 179 |
+
paged_kv_indptr=metadata.cu_seqlens_k_cpu,
|
| 180 |
+
paged_kv_indices=metadata.indices,
|
| 181 |
+
paged_kv_last_page_len=metadata.last_page_len_cpu,
|
| 182 |
+
num_qo_heads=metadata.num_qo_heads,
|
| 183 |
+
num_kv_heads=metadata.num_kv_heads,
|
| 184 |
+
head_dim_qk=metadata.head_dim,
|
| 185 |
+
page_size=metadata.page_size,
|
| 186 |
+
pos_encoding_mode=metadata.pos_encoding_mode,
|
| 187 |
+
seq_lens=metadata.seq_lens_cpu,
|
| 188 |
+
q_data_type=metadata.dtype,
|
| 189 |
+
kv_data_type=metadata.dtype,
|
| 190 |
+
sm_scale=metadata.sm_scale,
|
| 191 |
+
non_blocking=True,
|
| 192 |
+
causal=True,
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
def _get_ones_cpu(self, bs: int) -> torch.Tensor:
|
| 196 |
+
if bs <= len(self.cached_ones_cpu):
|
| 197 |
+
return self.cached_ones_cpu[:bs]
|
| 198 |
+
# padding to next pow of 2
|
| 199 |
+
next_len = _next_power_of_2(bs)
|
| 200 |
+
self.cached_ones_cpu = torch.ones(next_len, dtype=torch.int32, pin_memory=True)
|
| 201 |
+
return self.cached_ones_cpu[:bs]
|
| 202 |
+
|
| 203 |
+
def forward(
|
| 204 |
+
self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, layer_id: int, batch: TTSBatch
|
| 205 |
+
) -> torch.Tensor:
|
| 206 |
+
metadata = batch.attn_metadata
|
| 207 |
+
assert isinstance(metadata, FIMetadata)
|
| 208 |
+
self._initialize_metadata_once(metadata)
|
| 209 |
+
self.kvcache.store_kv(k, v, batch.out_loc, layer_id)
|
| 210 |
+
kv_cache = (self.kvcache.k_cache(layer_id), self.kvcache.v_cache(layer_id))
|
| 211 |
+
|
| 212 |
+
# Debug: check attention inputs/outputs for first few steps at layer 0
|
| 213 |
+
global _fi_debug_counter
|
| 214 |
+
is_capturing = torch.cuda.is_current_stream_capturing()
|
| 215 |
+
if (logger.isEnabledFor(logging.DEBUG)
|
| 216 |
+
and layer_id == 0 and batch.is_decode and not is_capturing
|
| 217 |
+
and _fi_debug_counter < 10):
|
| 218 |
+
_fi_debug_counter += 1
|
| 219 |
+
step = _fi_debug_counter
|
| 220 |
+
logger.debug("ATTN Step %d Layer 0", step)
|
| 221 |
+
logger.debug(" Q shape: %s, norm: %.4f, mean: %.6f", q.shape, q.norm().item(), q.mean().item())
|
| 222 |
+
logger.debug(" K shape: %s, norm: %.4f, mean: %.6f", k.shape, k.norm().item(), k.mean().item())
|
| 223 |
+
logger.debug(" V shape: %s, norm: %.4f, mean: %.6f", v.shape, v.norm().item(), v.mean().item())
|
| 224 |
+
logger.debug(" out_loc: %s", batch.out_loc.tolist())
|
| 225 |
+
logger.debug(" positions: %s", metadata.positions.tolist())
|
| 226 |
+
logger.debug(" indices shape: %s, first 10: %s", metadata.indices.shape, metadata.indices[:10].tolist())
|
| 227 |
+
logger.debug(" cu_seqlens_k: %s", metadata.cu_seqlens_k_cpu.tolist())
|
| 228 |
+
k_cache, v_cache = kv_cache
|
| 229 |
+
if batch.out_loc.numel() > 0:
|
| 230 |
+
slot = batch.out_loc[0].item()
|
| 231 |
+
logger.debug(" KV cache slot %d: k_norm=%.4f", slot, k_cache.view(-1, k_cache.shape[-1])[slot].norm().item())
|
| 232 |
+
|
| 233 |
+
output = metadata.wrapper.run(q=q, paged_kv_cache=kv_cache)
|
| 234 |
+
|
| 235 |
+
if (logger.isEnabledFor(logging.DEBUG)
|
| 236 |
+
and layer_id == 0 and batch.is_decode and not is_capturing
|
| 237 |
+
and _fi_debug_counter <= 10):
|
| 238 |
+
logger.debug(" Output shape: %s, norm: %.4f, mean: %.6f", output.shape, output.norm().item(), output.mean().item())
|
| 239 |
+
|
| 240 |
+
return output
|
| 241 |
+
|
| 242 |
+
def prepare_metadata(self, batch: TTSBatch) -> None:
|
| 243 |
+
reqs = batch.padded_reqs
|
| 244 |
+
|
| 245 |
+
padded_size = len(reqs)
|
| 246 |
+
seqlens_q = [req.extend_len for req in reqs]
|
| 247 |
+
seqlens_k = [req.device_len for req in reqs]
|
| 248 |
+
cached_lens = [req.cached_len for req in reqs]
|
| 249 |
+
max_seqlen_q = max(seqlens_q)
|
| 250 |
+
cpu_kwargs = {"device": "cpu", "dtype": torch.int32, "pin_memory": True}
|
| 251 |
+
|
| 252 |
+
device = self.device
|
| 253 |
+
seq_len_cpu = torch.tensor(seqlens_k, **cpu_kwargs)
|
| 254 |
+
cu_seqlens_k_cpu = torch.tensor([0] + seqlens_k, **cpu_kwargs).cumsum_(dim=0)
|
| 255 |
+
if max_seqlen_q == 1: # decode with all extend_len = 1
|
| 256 |
+
cu_seqlens_q_cpu = torch.arange(0, padded_size + 1, **cpu_kwargs)
|
| 257 |
+
elif all(l == 0 for l in cached_lens): # prefill with no cache hit
|
| 258 |
+
cu_seqlens_q_cpu = cu_seqlens_k_cpu
|
| 259 |
+
else: # normal extend prefill, with partial cache hit
|
| 260 |
+
cu_seqlens_q_cpu = torch.tensor([0] + seqlens_q, **cpu_kwargs).cumsum_(dim=0)
|
| 261 |
+
batch.attn_metadata = FIMetadata(
|
| 262 |
+
positions=make_positions(device, reqs),
|
| 263 |
+
cu_seqlens_q_cpu=cu_seqlens_q_cpu,
|
| 264 |
+
cu_seqlens_k_cpu=cu_seqlens_k_cpu,
|
| 265 |
+
cu_seqlens_q_gpu=cu_seqlens_q_cpu.to(device, non_blocking=True),
|
| 266 |
+
indices=torch.cat([self.page_table[req.table_idx, : req.device_len] for req in reqs]),
|
| 267 |
+
last_page_len_cpu=self._get_ones_cpu(padded_size),
|
| 268 |
+
num_qo_heads=self.qo_head_local,
|
| 269 |
+
num_kv_heads=self.kv_head_local,
|
| 270 |
+
head_dim=self.config.head_dim,
|
| 271 |
+
page_size=1,
|
| 272 |
+
pos_encoding_mode="NONE",
|
| 273 |
+
seq_lens_cpu=seq_len_cpu,
|
| 274 |
+
dtype=self.kvcache.dtype,
|
| 275 |
+
wrapper=self.decode_wrappers if batch.is_decode else self.prefill_wrapper,
|
| 276 |
+
sm_scale=1.0 / math.sqrt(self.config.head_dim),
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
def init_capture_graph(
|
| 280 |
+
self, max_seq_len: int, bs_list: List[int], frame_width: int = 1
|
| 281 |
+
) -> None:
|
| 282 |
+
assert self.capture is None, "Capture already initialized."
|
| 283 |
+
max_bs = max(bs_list)
|
| 284 |
+
capture = FICaptureData.create(max_bs, max_seq_len, self.kvcache.device, frame_width)
|
| 285 |
+
capture.page_table = capture.page_table.view(-1) # use 1D as ragged indices
|
| 286 |
+
self.max_graph_bs = max_bs
|
| 287 |
+
self.capture = capture
|
| 288 |
+
self.capture_bs = sorted(bs_list)
|
| 289 |
+
|
| 290 |
+
@cached_property
|
| 291 |
+
def use_tensor_cores(self) -> bool:
|
| 292 |
+
if (overriden_value := ENV.FLASHINFER_USE_TENSOR_CORES.value) is not None:
|
| 293 |
+
logger.warning(f"Overriding FlashInfer tensor core usage to {overriden_value}")
|
| 294 |
+
return overriden_value
|
| 295 |
+
GQA = self.config.num_qo_heads // self.config.num_kv_heads
|
| 296 |
+
return GQA >= 4
|
| 297 |
+
|
| 298 |
+
def prepare_for_capture(self, batch: TTSBatch) -> None:
|
| 299 |
+
from flashinfer import CUDAGraphBatchDecodeWithPagedKVCacheWrapper
|
| 300 |
+
|
| 301 |
+
bs = batch.size
|
| 302 |
+
assert bs in self.capture_bs and bs not in self.graph_wrappers and self.capture
|
| 303 |
+
batch.padded_reqs = batch.reqs
|
| 304 |
+
capture = self.capture
|
| 305 |
+
self.graph_wrappers[bs] = CUDAGraphBatchDecodeWithPagedKVCacheWrapper(
|
| 306 |
+
self.float_workspace_buffer,
|
| 307 |
+
kv_layout="NHD",
|
| 308 |
+
use_tensor_cores=self.use_tensor_cores,
|
| 309 |
+
indptr_buffer=capture.cu_seqlens_k[: bs + 1],
|
| 310 |
+
indices_buffer=capture.indices,
|
| 311 |
+
last_page_len_buffer=capture.one_tensor[:bs],
|
| 312 |
+
)
|
| 313 |
+
self.graph_wrappers[bs]._int_workspace_buffer = self.int_workspace_buffer
|
| 314 |
+
self.prepare_metadata(batch)
|
| 315 |
+
metadata = batch.attn_metadata
|
| 316 |
+
assert isinstance(metadata, FIMetadata)
|
| 317 |
+
# Copy metadata tensors to capture buffers and update metadata to use capture buffers
|
| 318 |
+
# This ensures CUDA graph uses fixed memory addresses
|
| 319 |
+
indices_len = metadata.indices.shape[0]
|
| 320 |
+
capture.indices[:indices_len].copy_(metadata.indices)
|
| 321 |
+
capture.cu_seqlens_k[: bs + 1].copy_(metadata.cu_seqlens_k_cpu.to(capture.indices.device))
|
| 322 |
+
capture.seq_lens[:bs].copy_(metadata.seq_lens_cpu.to(capture.indices.device))
|
| 323 |
+
# Update metadata to point to capture buffers (critical for CUDA graph)
|
| 324 |
+
metadata.indices = capture.indices[:indices_len]
|
| 325 |
+
metadata.wrapper = self.graph_wrappers[bs]
|
| 326 |
+
metadata.positions = capture.positions[:bs]
|
| 327 |
+
batch.input_ids = capture.input_ids[:bs]
|
| 328 |
+
batch.out_loc = capture.out_loc[:bs]
|
| 329 |
+
self._initialize_metadata_once(metadata)
|
| 330 |
+
|
| 331 |
+
def prepare_for_replay(self, batch: TTSBatch) -> None:
|
| 332 |
+
metadata, bs = batch.attn_metadata, batch.padded_size
|
| 333 |
+
assert isinstance(metadata, FIMetadata) and not metadata.initialized
|
| 334 |
+
assert self.capture is not None and bs in self.capture_bs
|
| 335 |
+
capture = self.capture
|
| 336 |
+
|
| 337 |
+
if logger.isEnabledFor(logging.DEBUG) and _fi_debug_counter < 5:
|
| 338 |
+
logger.debug("prepare_for_replay: bs=%d, indices_len=%d", bs, metadata.indices.shape[0])
|
| 339 |
+
logger.debug(" cu_seqlens_k_cpu=%s", metadata.cu_seqlens_k_cpu.tolist())
|
| 340 |
+
|
| 341 |
+
# Copy all dynamic tensors to capture buffers (fixed memory addresses for CUDA graph)
|
| 342 |
+
capture.input_ids[:bs].copy_(batch.input_ids)
|
| 343 |
+
capture.out_loc[:bs].copy_(batch.out_loc)
|
| 344 |
+
capture.positions[:bs].copy_(metadata.positions)
|
| 345 |
+
# Copy indices (page table entries) - critical for KV cache lookups
|
| 346 |
+
indices_len = metadata.indices.shape[0]
|
| 347 |
+
capture.indices[:indices_len].copy_(metadata.indices)
|
| 348 |
+
# Copy cu_seqlens_k for the wrapper's indptr
|
| 349 |
+
capture.cu_seqlens_k[: bs + 1].copy_(metadata.cu_seqlens_k_cpu.to(capture.indices.device))
|
| 350 |
+
# Copy seq_lens for the wrapper
|
| 351 |
+
capture.seq_lens[:bs].copy_(metadata.seq_lens_cpu.to(capture.indices.device))
|
| 352 |
+
# Update metadata to point to capture buffers (CRITICAL: must match prepare_for_capture)
|
| 353 |
+
# This ensures CUDA graph uses fixed memory addresses for all inputs
|
| 354 |
+
metadata.indices = capture.indices[:indices_len]
|
| 355 |
+
metadata.positions = capture.positions[:bs]
|
| 356 |
+
metadata.wrapper = self.graph_wrappers[bs]
|
| 357 |
+
# Update batch input_ids and out_loc to point to capture buffers
|
| 358 |
+
batch.input_ids = capture.input_ids[:bs]
|
| 359 |
+
batch.out_loc = capture.out_loc[:bs]
|
| 360 |
+
self._initialize_metadata_once(metadata)
|
zonos2/attention/utils.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import TYPE_CHECKING, List
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
from zonos2.core import TTSReq
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class BaseCaptureData:
|
| 14 |
+
input_ids: torch.Tensor
|
| 15 |
+
seq_lens: torch.Tensor
|
| 16 |
+
positions: torch.Tensor
|
| 17 |
+
cu_seqlens_k: torch.Tensor
|
| 18 |
+
cu_seqlens_q: torch.Tensor
|
| 19 |
+
page_table: torch.Tensor
|
| 20 |
+
out_loc: torch.Tensor
|
| 21 |
+
|
| 22 |
+
@classmethod
|
| 23 |
+
def create(
|
| 24 |
+
cls, max_bs: int, max_seq_len: int, device: torch.device, frame_width: int = 1, **kwargs
|
| 25 |
+
):
|
| 26 |
+
# Audio token frames are 2D (batch, frame_width); frame_width == 1 keeps a 1D layout
|
| 27 |
+
if frame_width > 1:
|
| 28 |
+
input_ids = torch.zeros((max_bs, frame_width), dtype=torch.int32, device=device)
|
| 29 |
+
else:
|
| 30 |
+
input_ids = torch.zeros((max_bs,), dtype=torch.int32, device=device)
|
| 31 |
+
return cls(
|
| 32 |
+
input_ids=input_ids,
|
| 33 |
+
seq_lens=torch.ones((max_bs,), dtype=torch.int32, device=device),
|
| 34 |
+
positions=torch.zeros((max_bs,), dtype=torch.int32, device=device),
|
| 35 |
+
cu_seqlens_k=torch.arange(0, max_bs + 1, dtype=torch.int32, device=device),
|
| 36 |
+
cu_seqlens_q=torch.arange(0, max_bs + 1, dtype=torch.int32, device=device),
|
| 37 |
+
page_table=torch.zeros((max_bs, max_seq_len), dtype=torch.int32, device=device),
|
| 38 |
+
out_loc=torch.zeros((max_bs,), dtype=torch.int32, device=device),
|
| 39 |
+
**kwargs,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def make_positions(device: torch.device, reqs: List[TTSReq]) -> torch.Tensor:
|
| 44 |
+
needed_size = sum(req.extend_len for req in reqs)
|
| 45 |
+
indices_host = torch.empty(needed_size, dtype=torch.int32, pin_memory=True)
|
| 46 |
+
offset = 0
|
| 47 |
+
for req in reqs:
|
| 48 |
+
length = req.extend_len
|
| 49 |
+
torch.arange(
|
| 50 |
+
req.cached_len,
|
| 51 |
+
req.device_len,
|
| 52 |
+
dtype=torch.int32,
|
| 53 |
+
out=indices_host[offset : offset + length],
|
| 54 |
+
)
|
| 55 |
+
offset += length
|
| 56 |
+
return indices_host.to(device, non_blocking=True)
|
zonos2/benchmark/client.py
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import asyncio
|
| 4 |
+
import random
|
| 5 |
+
import time
|
| 6 |
+
from contextlib import contextmanager
|
| 7 |
+
from dataclasses import dataclass, field
|
| 8 |
+
from typing import Any, Dict, List, Tuple, overload
|
| 9 |
+
|
| 10 |
+
from openai import AsyncOpenAI as OpenAI
|
| 11 |
+
from pydantic import BaseModel
|
| 12 |
+
from tqdm.asyncio import tqdm
|
| 13 |
+
from zonos2.utils import UNSET, Unset, init_logger
|
| 14 |
+
|
| 15 |
+
logger = init_logger(__name__)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass(frozen=True)
|
| 19 |
+
class BenchmarkTrace:
|
| 20 |
+
timestamp: float
|
| 21 |
+
message: str # unit (second)
|
| 22 |
+
output_length: int # output length in tokens
|
| 23 |
+
input_length: int | None = None # input length in tokens, optional
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@dataclass(frozen=True)
|
| 27 |
+
class BenchOneResult:
|
| 28 |
+
tics: List[float]
|
| 29 |
+
input_len: int
|
| 30 |
+
output_len: int
|
| 31 |
+
|
| 32 |
+
def as_json(self) -> List[float]:
|
| 33 |
+
return [self.input_len, self.output_len] + self.tics
|
| 34 |
+
|
| 35 |
+
@staticmethod
|
| 36 |
+
def from_json(raw: List[float]) -> BenchOneResult:
|
| 37 |
+
# check raw[0] and raw[1] are integers
|
| 38 |
+
assert raw[0].is_integer() and raw[1].is_integer()
|
| 39 |
+
return BenchOneResult(tics=raw[2:], input_len=int(raw[0]), output_len=int(raw[1]))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@dataclass(frozen=True)
|
| 43 |
+
class RawResult:
|
| 44 |
+
input_len: int | None
|
| 45 |
+
output_len: int
|
| 46 |
+
message: str
|
| 47 |
+
tics: List[float]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
@dataclass
|
| 51 |
+
class Counter:
|
| 52 |
+
current: int = 0
|
| 53 |
+
history_max: int = 0
|
| 54 |
+
|
| 55 |
+
def inc(self, n=1):
|
| 56 |
+
self.current += n
|
| 57 |
+
self.history_max = max(self.history_max, self.current)
|
| 58 |
+
|
| 59 |
+
def dec(self, n=1):
|
| 60 |
+
self.current -= n
|
| 61 |
+
assert self.current >= 0
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@dataclass
|
| 65 |
+
class Console:
|
| 66 |
+
input_pbar: tqdm
|
| 67 |
+
output_pbar: tqdm
|
| 68 |
+
prefill_pbar: tqdm
|
| 69 |
+
decode_pbar: tqdm
|
| 70 |
+
disabled: bool
|
| 71 |
+
inflight_counter: Counter = field(default_factory=Counter)
|
| 72 |
+
queue_counter: Counter = field(default_factory=Counter)
|
| 73 |
+
|
| 74 |
+
def update_input(self, n=1):
|
| 75 |
+
self.input_pbar.update(n)
|
| 76 |
+
self.input_pbar.refresh()
|
| 77 |
+
self.inflight_counter.inc(n)
|
| 78 |
+
self.queue_counter.inc(n)
|
| 79 |
+
|
| 80 |
+
def update_output(self, n=1):
|
| 81 |
+
self.output_pbar.update(n)
|
| 82 |
+
self.output_pbar.refresh()
|
| 83 |
+
self.inflight_counter.dec(n)
|
| 84 |
+
|
| 85 |
+
def update_prefill(self, n=1):
|
| 86 |
+
self.prefill_pbar.update(n)
|
| 87 |
+
self.prefill_pbar.refresh()
|
| 88 |
+
self.queue_counter.dec(n)
|
| 89 |
+
|
| 90 |
+
def update_decode(self, n=1):
|
| 91 |
+
self.decode_pbar.update(n)
|
| 92 |
+
|
| 93 |
+
@contextmanager
|
| 94 |
+
def inflight(self, n=1):
|
| 95 |
+
self.update_input(n)
|
| 96 |
+
yield
|
| 97 |
+
self.update_output(n)
|
| 98 |
+
|
| 99 |
+
@contextmanager
|
| 100 |
+
def log_stats(self):
|
| 101 |
+
yield
|
| 102 |
+
self.input_pbar.close()
|
| 103 |
+
self.output_pbar.close()
|
| 104 |
+
self.prefill_pbar.close()
|
| 105 |
+
self.decode_pbar.close()
|
| 106 |
+
if not self.disabled:
|
| 107 |
+
max_inflight = self.inflight_counter.history_max
|
| 108 |
+
max_queue = self.queue_counter.history_max
|
| 109 |
+
logger.info(f"Max inflight requests: {max_inflight}, Max queued requests: {max_queue}")
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@dataclass(frozen=True)
|
| 113 |
+
class BenchmarkResult:
|
| 114 |
+
raw_data: List[BenchOneResult]
|
| 115 |
+
|
| 116 |
+
def as_json(self) -> List[List[float]]:
|
| 117 |
+
return [r.as_json() for r in self.raw_data]
|
| 118 |
+
|
| 119 |
+
@staticmethod
|
| 120 |
+
def from_json(raw: List[List[float]]) -> BenchmarkResult:
|
| 121 |
+
return BenchmarkResult(raw_data=[BenchOneResult.from_json(r) for r in raw])
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def make_console(num_requests: int, sum_output_length: int, use_pbar: bool = True) -> Console:
|
| 125 |
+
BAR_FORMAT_0 = (
|
| 126 |
+
"{desc:<10} {percentage:3.0f}%|{bar}|"
|
| 127 |
+
" {n_fmt:>5}/{total_fmt} "
|
| 128 |
+
"[{rate_fmt:>12} {elapsed:>8}/{remaining:<8}]"
|
| 129 |
+
)
|
| 130 |
+
BAR_FORMAT_1 = BAR_FORMAT_0
|
| 131 |
+
n_fmt_align = 5
|
| 132 |
+
prefill_tokens = num_requests
|
| 133 |
+
decode_tokens = sum_output_length - prefill_tokens
|
| 134 |
+
|
| 135 |
+
if len(str(decode_tokens)) > n_fmt_align:
|
| 136 |
+
n_fmt_align = len(str(decode_tokens))
|
| 137 |
+
BAR_FORMAT_0 = BAR_FORMAT_0.replace("{n_fmt:>5}", "{n_fmt:>" + str(n_fmt_align) + "}")
|
| 138 |
+
BAR_FORMAT_1 = BAR_FORMAT_0
|
| 139 |
+
|
| 140 |
+
if len(str(prefill_tokens)) < len(str(decode_tokens)):
|
| 141 |
+
old_align_str = "{n_fmt:>" + str(n_fmt_align) + "}"
|
| 142 |
+
n_fmt_align += len(str(decode_tokens)) - len(str(prefill_tokens))
|
| 143 |
+
BAR_FORMAT_0 = BAR_FORMAT_0.replace(old_align_str, "{n_fmt:>" + str(n_fmt_align) + "}")
|
| 144 |
+
|
| 145 |
+
disabled = not use_pbar
|
| 146 |
+
input_pbar = tqdm(
|
| 147 |
+
total=num_requests,
|
| 148 |
+
desc="Requests sent",
|
| 149 |
+
position=0,
|
| 150 |
+
bar_format=BAR_FORMAT_0,
|
| 151 |
+
disable=disabled,
|
| 152 |
+
)
|
| 153 |
+
output_pbar = tqdm(
|
| 154 |
+
total=num_requests,
|
| 155 |
+
desc="Requests done",
|
| 156 |
+
position=1,
|
| 157 |
+
bar_format=BAR_FORMAT_0,
|
| 158 |
+
disable=disabled,
|
| 159 |
+
)
|
| 160 |
+
prefill_pbar = tqdm(
|
| 161 |
+
total=prefill_tokens,
|
| 162 |
+
desc="Prefill token",
|
| 163 |
+
position=2,
|
| 164 |
+
bar_format=BAR_FORMAT_0,
|
| 165 |
+
disable=disabled,
|
| 166 |
+
)
|
| 167 |
+
decode_pbar = tqdm(
|
| 168 |
+
total=decode_tokens,
|
| 169 |
+
desc="Decode token ",
|
| 170 |
+
position=3,
|
| 171 |
+
bar_format=BAR_FORMAT_1,
|
| 172 |
+
disable=disabled,
|
| 173 |
+
)
|
| 174 |
+
return Console(
|
| 175 |
+
input_pbar=input_pbar,
|
| 176 |
+
output_pbar=output_pbar,
|
| 177 |
+
prefill_pbar=prefill_pbar,
|
| 178 |
+
decode_pbar=decode_pbar,
|
| 179 |
+
disabled=disabled,
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def generate_prompt(tokenizer: Any, n: int) -> str:
|
| 184 |
+
"""Generate a prompt of approximately `n` tokens using the provided tokenizer."""
|
| 185 |
+
vocab_size = tokenizer.vocab_size // 2
|
| 186 |
+
token_ids = [random.randint(0, vocab_size) for _ in range(n - 1)]
|
| 187 |
+
|
| 188 |
+
for _ in range(64):
|
| 189 |
+
prompt = tokenizer.decode(token_ids)
|
| 190 |
+
token_ids = tokenizer.encode(prompt, add_special_tokens=False)
|
| 191 |
+
if len(token_ids) == n:
|
| 192 |
+
return prompt
|
| 193 |
+
if len(token_ids) < n:
|
| 194 |
+
need = n - len(token_ids)
|
| 195 |
+
token_ids.extend([random.randint(0, vocab_size) for _ in range(need)])
|
| 196 |
+
else:
|
| 197 |
+
token_ids = token_ids[:n]
|
| 198 |
+
|
| 199 |
+
raise ValueError("Failed to generate a message of the desired length.")
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
async def benchmark_one(
|
| 203 |
+
client: OpenAI,
|
| 204 |
+
prompt: str,
|
| 205 |
+
output_length: int,
|
| 206 |
+
model: str,
|
| 207 |
+
*,
|
| 208 |
+
pbar: Console | bool = True,
|
| 209 |
+
extra_body: Dict[str, Any] | None = None,
|
| 210 |
+
input_length: int | None = None, # a hack to force input length
|
| 211 |
+
) -> RawResult:
|
| 212 |
+
if isinstance(pbar, bool):
|
| 213 |
+
pbar = make_console(1, output_length, use_pbar=pbar)
|
| 214 |
+
with pbar.inflight(1):
|
| 215 |
+
kwargs = {
|
| 216 |
+
"ignore_eos": True,
|
| 217 |
+
"top_k": 1,
|
| 218 |
+
}
|
| 219 |
+
# this is an internal kwargs that might work for our system
|
| 220 |
+
if input_length is not None:
|
| 221 |
+
kwargs["input_length_override"] = input_length
|
| 222 |
+
kwargs.update(extra_body or {}) # can override kwargs
|
| 223 |
+
response = await client.chat.completions.create(
|
| 224 |
+
model=model,
|
| 225 |
+
stream=True,
|
| 226 |
+
messages=[
|
| 227 |
+
{
|
| 228 |
+
"role": "user",
|
| 229 |
+
"content": prompt,
|
| 230 |
+
},
|
| 231 |
+
],
|
| 232 |
+
max_tokens=output_length,
|
| 233 |
+
temperature=0.0,
|
| 234 |
+
extra_body=kwargs,
|
| 235 |
+
)
|
| 236 |
+
tics = [time.perf_counter()]
|
| 237 |
+
async for _ in response:
|
| 238 |
+
tics.append(time.perf_counter())
|
| 239 |
+
if len(tics) == 2:
|
| 240 |
+
pbar.update_prefill()
|
| 241 |
+
elif len(tics) <= output_length + 1:
|
| 242 |
+
pbar.update_decode()
|
| 243 |
+
return RawResult(
|
| 244 |
+
input_len=input_length,
|
| 245 |
+
output_len=output_length,
|
| 246 |
+
message=prompt,
|
| 247 |
+
tics=tics,
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
async def benchmark_one_batch(
|
| 252 |
+
client: OpenAI,
|
| 253 |
+
prompts: List[str],
|
| 254 |
+
output_lengths: List[int] | int,
|
| 255 |
+
model: str,
|
| 256 |
+
*,
|
| 257 |
+
extra_body: Dict[str, Any] | None = None,
|
| 258 |
+
input_lengths: List[int | None] | None = None,
|
| 259 |
+
pbar: Console | bool = True,
|
| 260 |
+
) -> List[RawResult]:
|
| 261 |
+
if isinstance(output_lengths, int):
|
| 262 |
+
output_lengths = [output_lengths] * len(prompts)
|
| 263 |
+
if isinstance(pbar, bool):
|
| 264 |
+
pbar = make_console(len(prompts), sum(output_lengths), use_pbar=pbar)
|
| 265 |
+
if input_lengths is None:
|
| 266 |
+
l: List[int | None] = [None] * len(prompts)
|
| 267 |
+
input_lengths = l # work-around for typing bug
|
| 268 |
+
|
| 269 |
+
tasks = [
|
| 270 |
+
benchmark_one(
|
| 271 |
+
client=client,
|
| 272 |
+
prompt=prompt,
|
| 273 |
+
output_length=output_length,
|
| 274 |
+
model=model,
|
| 275 |
+
pbar=pbar,
|
| 276 |
+
extra_body=extra_body,
|
| 277 |
+
input_length=input_length,
|
| 278 |
+
)
|
| 279 |
+
for prompt, output_length, input_length in zip(
|
| 280 |
+
prompts, output_lengths, input_lengths, strict=True
|
| 281 |
+
)
|
| 282 |
+
]
|
| 283 |
+
with pbar.log_stats():
|
| 284 |
+
return await asyncio.gather(*tasks)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
async def benchmark_trace(
|
| 288 |
+
client: OpenAI,
|
| 289 |
+
msgs: List[BenchmarkTrace],
|
| 290 |
+
model: str,
|
| 291 |
+
*,
|
| 292 |
+
pbar: Console | bool = True,
|
| 293 |
+
) -> List[RawResult]:
|
| 294 |
+
if isinstance(pbar, bool):
|
| 295 |
+
sum_output_len = sum(msg.output_length for msg in msgs)
|
| 296 |
+
pbar = make_console(len(msgs), sum_output_len, use_pbar=pbar)
|
| 297 |
+
start = time.perf_counter()
|
| 298 |
+
offset = min(msg.timestamp for msg in msgs) - 1
|
| 299 |
+
|
| 300 |
+
async def benchmark_timed(msg: BenchmarkTrace):
|
| 301 |
+
target = start + msg.timestamp - offset
|
| 302 |
+
await asyncio.sleep(max(0, target - time.perf_counter()))
|
| 303 |
+
return await benchmark_one(
|
| 304 |
+
client, msg.message, msg.output_length, model, pbar=pbar, input_length=msg.input_length
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
tasks = [benchmark_timed(msg) for msg in msgs]
|
| 308 |
+
with pbar.log_stats():
|
| 309 |
+
return await asyncio.gather(*tasks)
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
@overload
|
| 313 |
+
def process_benchmark_results(raw_data: List[RawResult], tokenizer: Any) -> BenchmarkResult: ...
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
@overload
|
| 317 |
+
def process_benchmark_results(raw_data: List[RawResult]) -> None: ...
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def process_benchmark_results(
|
| 321 |
+
raw_data: List[RawResult],
|
| 322 |
+
tokenizer: Any = UNSET,
|
| 323 |
+
) -> BenchmarkResult | None:
|
| 324 |
+
accum_times: List[float] = []
|
| 325 |
+
first_times: List[float] = []
|
| 326 |
+
results = [r.tics for r in raw_data]
|
| 327 |
+
for tics in results:
|
| 328 |
+
deltas: List[float] = []
|
| 329 |
+
for i in range(len(tics) - 1):
|
| 330 |
+
diff = tics[i + 1] - tics[i]
|
| 331 |
+
deltas.append(diff)
|
| 332 |
+
first_times.append(deltas[0])
|
| 333 |
+
accum_times.extend(deltas[1:])
|
| 334 |
+
|
| 335 |
+
e2e_times = [tics[-1] - tics[0] for tics in results]
|
| 336 |
+
first_times.sort()
|
| 337 |
+
accum_times.sort()
|
| 338 |
+
e2e_times.sort()
|
| 339 |
+
|
| 340 |
+
def _print_stats(times: List[float], scale: float = 1.0) -> Tuple[float, ...]:
|
| 341 |
+
assert len(times) > 0
|
| 342 |
+
return (
|
| 343 |
+
scale * sum(times) / len(times), # avg
|
| 344 |
+
scale * times[int(len(times) * 0.5)], # p50
|
| 345 |
+
scale * times[int(len(times) * 0.9)], # p90
|
| 346 |
+
scale * times[int(len(times) * 0.99)], # p99
|
| 347 |
+
scale * max(times), # max
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
def _fmt(x: float) -> str:
|
| 351 |
+
if x >= 1000:
|
| 352 |
+
return f"{int(x):>6}"
|
| 353 |
+
elif x >= 10:
|
| 354 |
+
return f"{x:>6.2f}"
|
| 355 |
+
else:
|
| 356 |
+
return f"{x:>6.4f}"
|
| 357 |
+
|
| 358 |
+
avg_ttft, p50_ttft, p90_ttft, p99_ttft, max_ttft = _print_stats(first_times, 1000)
|
| 359 |
+
avg_tpot, p50_tpot, p90_tpot, p99_tpot, max_tpot = _print_stats(accum_times, 1000)
|
| 360 |
+
avg_e2e, p50_e2e, p90_e2e, p99_e2e, max_e2e = _print_stats(e2e_times)
|
| 361 |
+
|
| 362 |
+
min_time = min(min(r) for r in results)
|
| 363 |
+
max_time = max(max(r) for r in results)
|
| 364 |
+
dur = max_time - min_time
|
| 365 |
+
assert dur > 0, "Duration must be positive"
|
| 366 |
+
|
| 367 |
+
num_tokens = sum(len(tic) for tic in results)
|
| 368 |
+
num_requests = len(results)
|
| 369 |
+
|
| 370 |
+
logger.info(f"Num requests: #{num_requests}, Num tokens: #{num_tokens}")
|
| 371 |
+
logger.info(
|
| 372 |
+
f"TTFT: {_fmt(avg_ttft)} ms (p50: {_fmt(p50_ttft)} ms, p90: {_fmt(p90_ttft)} ms,"
|
| 373 |
+
f" p99: {_fmt(p99_ttft)} ms, max: {_fmt(max_ttft)} ms)"
|
| 374 |
+
)
|
| 375 |
+
logger.info(
|
| 376 |
+
f"TPOT: {_fmt(avg_tpot)} ms (p50: {_fmt(p50_tpot)} ms, p90: {_fmt(p90_tpot)} ms,"
|
| 377 |
+
f" p99: {_fmt(p99_tpot)} ms, max: {_fmt(max_tpot)} ms)"
|
| 378 |
+
)
|
| 379 |
+
logger.info(
|
| 380 |
+
f"E2E: {_fmt(avg_e2e) } s (p50: {_fmt(p50_e2e) } s, p90: {_fmt(p90_e2e) } s,"
|
| 381 |
+
f" p99: {_fmt(p99_e2e) } s, max: {_fmt(max_e2e) } s)"
|
| 382 |
+
)
|
| 383 |
+
logger.info(f"Duration: {_fmt(dur)} s")
|
| 384 |
+
logger.info(f"Throughput: {_fmt(num_tokens / dur)} token/s, {_fmt(num_requests / dur)} req/s")
|
| 385 |
+
|
| 386 |
+
# normalize the time to start from zero
|
| 387 |
+
results = [[r - min_time for r in tics] for tics in results]
|
| 388 |
+
if isinstance(tokenizer, Unset):
|
| 389 |
+
return None
|
| 390 |
+
|
| 391 |
+
return BenchmarkResult(
|
| 392 |
+
raw_data=[
|
| 393 |
+
BenchOneResult(
|
| 394 |
+
tics=r.tics,
|
| 395 |
+
input_len=(
|
| 396 |
+
r.input_len
|
| 397 |
+
if r.input_len is not None
|
| 398 |
+
else len(tokenizer.encode(r.message, add_special_tokens=False))
|
| 399 |
+
),
|
| 400 |
+
output_len=r.output_len,
|
| 401 |
+
)
|
| 402 |
+
for r in raw_data
|
| 403 |
+
]
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def read_qwen_trace(
|
| 408 |
+
file_path: str,
|
| 409 |
+
tokenizer: Any,
|
| 410 |
+
n: int | None = None,
|
| 411 |
+
dummy: bool = False,
|
| 412 |
+
) -> List[BenchmarkTrace]:
|
| 413 |
+
class JSONInput(BaseModel):
|
| 414 |
+
chat_id: int
|
| 415 |
+
parent_chat_id: int
|
| 416 |
+
timestamp: float
|
| 417 |
+
input_length: int
|
| 418 |
+
output_length: int
|
| 419 |
+
type: str # unused
|
| 420 |
+
turn: int # unused
|
| 421 |
+
hash_ids: List[int] # unused
|
| 422 |
+
|
| 423 |
+
with open(file_path, "r") as f:
|
| 424 |
+
lines = f.readlines()
|
| 425 |
+
if n is not None:
|
| 426 |
+
lines = lines[:n]
|
| 427 |
+
objs = [JSONInput.model_validate_json(line) for line in lines]
|
| 428 |
+
if dummy:
|
| 429 |
+
prompt = generate_prompt(tokenizer, max(obj.input_length for obj in objs))
|
| 430 |
+
ids = tokenizer.encode(prompt, add_special_tokens=False)
|
| 431 |
+
_get_prompt = lambda obj: tokenizer.decode(ids[: obj.input_length])
|
| 432 |
+
else:
|
| 433 |
+
_get_prompt = lambda obj: generate_prompt(tokenizer, obj.input_length)
|
| 434 |
+
return [
|
| 435 |
+
BenchmarkTrace(
|
| 436 |
+
timestamp=obj.timestamp,
|
| 437 |
+
message=_get_prompt(obj),
|
| 438 |
+
input_length=obj.input_length,
|
| 439 |
+
output_length=obj.output_length,
|
| 440 |
+
)
|
| 441 |
+
for obj in objs
|
| 442 |
+
]
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def read_mooncake_trace(
|
| 446 |
+
file_path: str,
|
| 447 |
+
tokenizer: Any,
|
| 448 |
+
n: int | None = None,
|
| 449 |
+
dummy: bool = False,
|
| 450 |
+
) -> List[BenchmarkTrace]:
|
| 451 |
+
class JSONInput(BaseModel):
|
| 452 |
+
timestamp: int
|
| 453 |
+
input_length: int
|
| 454 |
+
output_length: int
|
| 455 |
+
hash_ids: List[int] # unused for now
|
| 456 |
+
|
| 457 |
+
with open(file_path, "r") as f:
|
| 458 |
+
lines = f.readlines()
|
| 459 |
+
if n is not None:
|
| 460 |
+
lines = lines[:n]
|
| 461 |
+
objs = [JSONInput.model_validate_json(line) for line in lines]
|
| 462 |
+
if dummy:
|
| 463 |
+
prompt = generate_prompt(tokenizer, max(obj.input_length for obj in objs))
|
| 464 |
+
ids = tokenizer.encode(prompt, add_special_tokens=False)
|
| 465 |
+
_get_prompt = lambda obj: tokenizer.decode(ids[: obj.input_length])
|
| 466 |
+
else:
|
| 467 |
+
_get_prompt = lambda obj: generate_prompt(tokenizer, obj.input_length)
|
| 468 |
+
return [
|
| 469 |
+
BenchmarkTrace(
|
| 470 |
+
timestamp=obj.timestamp / 1000,
|
| 471 |
+
message=_get_prompt(obj),
|
| 472 |
+
input_length=obj.input_length,
|
| 473 |
+
output_length=obj.output_length,
|
| 474 |
+
)
|
| 475 |
+
for obj in objs
|
| 476 |
+
]
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
def scale_traces(
|
| 480 |
+
traces: List[BenchmarkTrace],
|
| 481 |
+
scale: float,
|
| 482 |
+
) -> List[BenchmarkTrace]:
|
| 483 |
+
min_tic = min(trace.timestamp for trace in traces)
|
| 484 |
+
return sorted(
|
| 485 |
+
[
|
| 486 |
+
BenchmarkTrace(
|
| 487 |
+
timestamp=(trace.timestamp - min_tic) * scale,
|
| 488 |
+
message=trace.message,
|
| 489 |
+
input_length=trace.input_length,
|
| 490 |
+
output_length=trace.output_length,
|
| 491 |
+
)
|
| 492 |
+
for trace in traces
|
| 493 |
+
],
|
| 494 |
+
key=lambda x: x.timestamp,
|
| 495 |
+
)
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
async def get_model_name(client: OpenAI) -> str:
|
| 499 |
+
async for model in client.models.list():
|
| 500 |
+
return model.id
|
| 501 |
+
raise ValueError("No models available")
|
zonos2/benchmark/perf.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any, Callable, Dict, Tuple
|
| 4 |
+
|
| 5 |
+
from zonos2.utils import init_logger
|
| 6 |
+
|
| 7 |
+
logger = init_logger(__name__)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def perf_cuda(
|
| 11 |
+
f: Callable[[], Any],
|
| 12 |
+
*,
|
| 13 |
+
init_stream: bool = True,
|
| 14 |
+
repetitions: int = 10,
|
| 15 |
+
cuda_graph_repetitions: int | None = 10,
|
| 16 |
+
) -> float:
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
assert repetitions > 0
|
| 20 |
+
tic = torch.cuda.Event(enable_timing=True)
|
| 21 |
+
toc = torch.cuda.Event(enable_timing=True)
|
| 22 |
+
stream = torch.cuda.Stream()
|
| 23 |
+
torch.cuda.synchronize()
|
| 24 |
+
if init_stream:
|
| 25 |
+
stream = torch.cuda.Stream()
|
| 26 |
+
else:
|
| 27 |
+
stream = torch.cuda.current_stream()
|
| 28 |
+
|
| 29 |
+
with torch.cuda.stream(stream):
|
| 30 |
+
f()
|
| 31 |
+
if N := cuda_graph_repetitions:
|
| 32 |
+
g = torch.cuda.CUDAGraph()
|
| 33 |
+
with torch.cuda.graph(g):
|
| 34 |
+
for _ in range(N):
|
| 35 |
+
f()
|
| 36 |
+
replay = g.replay
|
| 37 |
+
del g
|
| 38 |
+
else:
|
| 39 |
+
replay = f
|
| 40 |
+
N = 1
|
| 41 |
+
|
| 42 |
+
torch.cuda.synchronize()
|
| 43 |
+
|
| 44 |
+
replay()
|
| 45 |
+
tic.record()
|
| 46 |
+
for _ in range(repetitions):
|
| 47 |
+
replay()
|
| 48 |
+
toc.record()
|
| 49 |
+
toc.synchronize()
|
| 50 |
+
dur = tic.elapsed_time(toc)
|
| 51 |
+
return dur / (N * repetitions)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def compare_memory_kernel_perf(
|
| 55 |
+
*,
|
| 56 |
+
baseline: Callable[[], Any],
|
| 57 |
+
our_impl: Callable[[], Any],
|
| 58 |
+
memory_footprint: int, # in bytes
|
| 59 |
+
description: str = " ",
|
| 60 |
+
extra_kwargs: Dict[str, Any] | None = None,
|
| 61 |
+
need_latency: bool = True,
|
| 62 |
+
) -> Tuple[float, float]:
|
| 63 |
+
extra_kwargs = extra_kwargs or {}
|
| 64 |
+
|
| 65 |
+
dur = perf_cuda(baseline, **extra_kwargs)
|
| 66 |
+
bandwidth_0 = memory_footprint / (dur * 1e6) # GB/s
|
| 67 |
+
latency_msg = f"{dur:8.3f} ms | " if need_latency else ""
|
| 68 |
+
message_0 = f"Baseline: {latency_msg}{bandwidth_0:8.3f} GB/s"
|
| 69 |
+
|
| 70 |
+
dur = perf_cuda(our_impl, **extra_kwargs)
|
| 71 |
+
bandwidth_1 = memory_footprint / (dur * 1e6) # GB/s
|
| 72 |
+
latency_msg = f"{dur:8.3f} ms | " if need_latency else ""
|
| 73 |
+
logger.info(f"{description}{message_0} | Our Impl: {latency_msg}{bandwidth_1:8.3f} GB/s")
|
| 74 |
+
return bandwidth_0, bandwidth_1
|
zonos2/core.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from contextlib import contextmanager
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from typing import TYPE_CHECKING, List, Literal
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
if TYPE_CHECKING:
|
| 10 |
+
from zonos2.attention import BaseAttnBackend, BaseAttnMetadata
|
| 11 |
+
from zonos2.kvcache import BaseCacheHandle
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class Context:
|
| 16 |
+
page_size: int
|
| 17 |
+
attn_backend: BaseAttnBackend
|
| 18 |
+
_batch: TTSBatch | None = field(default=None, init=False)
|
| 19 |
+
|
| 20 |
+
@property
|
| 21 |
+
def batch(self) -> TTSBatch:
|
| 22 |
+
assert self._batch is not None, "No active batch in context"
|
| 23 |
+
return self._batch
|
| 24 |
+
|
| 25 |
+
@contextmanager
|
| 26 |
+
def forward_batch(self, batch: TTSBatch):
|
| 27 |
+
assert self._batch is None, "Nested forward_batch is not allowed"
|
| 28 |
+
try:
|
| 29 |
+
self._batch = batch
|
| 30 |
+
yield
|
| 31 |
+
finally:
|
| 32 |
+
self._batch = None
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
_GLOBAL_CTX: Context | None = None
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def set_global_ctx(ctx: Context):
|
| 39 |
+
global _GLOBAL_CTX
|
| 40 |
+
assert _GLOBAL_CTX is None, "Global context is already set"
|
| 41 |
+
_GLOBAL_CTX = ctx
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def get_global_ctx() -> Context:
|
| 45 |
+
assert _GLOBAL_CTX is not None, "Global context is not set"
|
| 46 |
+
return _GLOBAL_CTX
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# =============================================================================
|
| 50 |
+
# TTS-specific data structures
|
| 51 |
+
# =============================================================================
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class TTSSamplingParams:
|
| 56 |
+
"""Sampling parameters for TTS generation."""
|
| 57 |
+
|
| 58 |
+
temperature: float = 1.15
|
| 59 |
+
topk: int = 106
|
| 60 |
+
top_p: float = 0.0
|
| 61 |
+
min_p: float = 0.18
|
| 62 |
+
max_tokens: int = 1024
|
| 63 |
+
ignore_eos: bool = False
|
| 64 |
+
repetition_window: int = 50
|
| 65 |
+
repetition_penalty: float = 1.2
|
| 66 |
+
repetition_codebooks: int = 8
|
| 67 |
+
seed: int | None = None
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@dataclass(eq=False)
|
| 71 |
+
class TTSReq:
|
| 72 |
+
"""Request class for TTS generation with 2D token format.
|
| 73 |
+
|
| 74 |
+
Tokens are in unpacked format: [cb0, cb1, ..., cb8, text_token] per frame.
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
input_ids: torch.Tensor # 2D CPU tensor (seq_len, frame_width)
|
| 78 |
+
table_idx: int
|
| 79 |
+
cached_len: int
|
| 80 |
+
output_len: int
|
| 81 |
+
uid: int
|
| 82 |
+
sampling_params: TTSSamplingParams
|
| 83 |
+
cache_handle: BaseCacheHandle
|
| 84 |
+
n_codebooks: int = 9
|
| 85 |
+
eoa_id: int = 1024
|
| 86 |
+
eos_frame: int = -1 # Aligned frame where EOS first appeared (-1 = not seen)
|
| 87 |
+
eos_countdown: int = -1 # Steps remaining after EOS (-1 = not in countdown)
|
| 88 |
+
total_generated: int = 0 # Total frames generated (for logging)
|
| 89 |
+
rng: torch.Generator | None = None # Per-request RNG for deterministic sampling
|
| 90 |
+
speaker_embedding: torch.Tensor | None = None # 1D CPU float32 tensor
|
| 91 |
+
speaker_token_position: int = -1 # Injection position within the prompt sequence
|
| 92 |
+
|
| 93 |
+
def __post_init__(self) -> None:
|
| 94 |
+
assert self.input_ids.is_cpu
|
| 95 |
+
assert self.input_ids.dim() == 2, "TTS input_ids must be 2D (seq_len, frame_width)"
|
| 96 |
+
self.device_len = len(self.input_ids)
|
| 97 |
+
self.max_device_len = len(self.input_ids) + self.output_len
|
| 98 |
+
assert 0 <= self.cached_len < self.device_len <= self.max_device_len
|
| 99 |
+
|
| 100 |
+
if self.speaker_embedding is not None:
|
| 101 |
+
emb = self.speaker_embedding
|
| 102 |
+
if emb.dim() == 2 and emb.shape[0] == 1:
|
| 103 |
+
emb = emb.squeeze(0)
|
| 104 |
+
if emb.dim() != 1:
|
| 105 |
+
raise ValueError(
|
| 106 |
+
f"speaker_embedding must be 1D or (1, D), got shape {tuple(emb.shape)}"
|
| 107 |
+
)
|
| 108 |
+
self.speaker_embedding = emb.to(dtype=torch.float32, device="cpu")
|
| 109 |
+
|
| 110 |
+
if self.speaker_token_position < 0:
|
| 111 |
+
# Training convention: reserved speaker slot is at prompt position 0.
|
| 112 |
+
self.speaker_token_position = 0
|
| 113 |
+
if self.speaker_token_position >= self.device_len:
|
| 114 |
+
self.speaker_token_position = 0
|
| 115 |
+
|
| 116 |
+
@property
|
| 117 |
+
def frame_width(self) -> int:
|
| 118 |
+
"""Number of elements per frame (n_codebooks + extras)."""
|
| 119 |
+
return self.input_ids.shape[-1]
|
| 120 |
+
|
| 121 |
+
@property
|
| 122 |
+
def remain_len(self) -> int:
|
| 123 |
+
return self.max_device_len - self.device_len
|
| 124 |
+
|
| 125 |
+
@property
|
| 126 |
+
def extend_len(self) -> int:
|
| 127 |
+
return self.device_len - self.cached_len
|
| 128 |
+
|
| 129 |
+
@property
|
| 130 |
+
def num_completion_tokens(self) -> int:
|
| 131 |
+
"""Number of generated tokens (frames)."""
|
| 132 |
+
return self.device_len - self.cached_len
|
| 133 |
+
|
| 134 |
+
def complete_one(self) -> None:
|
| 135 |
+
self.cached_len = self.device_len
|
| 136 |
+
self.device_len += 1
|
| 137 |
+
self.total_generated += 1
|
| 138 |
+
|
| 139 |
+
def append_host(self, next_token: torch.Tensor) -> None:
|
| 140 |
+
"""Append a single frame (unpacked token) to input_ids."""
|
| 141 |
+
assert next_token.dim() == 1, "next_token must be 1D (frame_width,)"
|
| 142 |
+
self.input_ids = torch.cat([self.input_ids, next_token.unsqueeze(0)], dim=0)
|
| 143 |
+
|
| 144 |
+
def can_decode(self) -> bool:
|
| 145 |
+
return self.remain_len > 0 and self.eos_countdown != 0
|
| 146 |
+
|
| 147 |
+
def check_eos(self, audio_codes: List[int]) -> bool:
|
| 148 |
+
"""Check for EOS and update countdown state.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
audio_codes: List of audio codebook values for one frame
|
| 152 |
+
|
| 153 |
+
Returns:
|
| 154 |
+
True if sequence is finished (countdown reached 0)
|
| 155 |
+
"""
|
| 156 |
+
if self.sampling_params.ignore_eos:
|
| 157 |
+
return False
|
| 158 |
+
|
| 159 |
+
# Match Zonos2 reference inference: any sampled EOA codebook starts the
|
| 160 |
+
# delayed stop countdown. The aligned frame is shifted back by the
|
| 161 |
+
# highest EOA codebook index and clamped at zero.
|
| 162 |
+
# Use total_generated because this request only sees one decode frame at a time.
|
| 163 |
+
if self.eos_frame < 0:
|
| 164 |
+
step = self.total_generated - 1
|
| 165 |
+
eos_cols = [c == self.eoa_id for c in audio_codes[: self.n_codebooks]]
|
| 166 |
+
if any(eos_cols):
|
| 167 |
+
# First EOS: compute aligned frame
|
| 168 |
+
max_eos_cb = max(i for i, is_eos in enumerate(eos_cols) if is_eos)
|
| 169 |
+
self.eos_frame = max(0, step - max_eos_cb)
|
| 170 |
+
self.eos_countdown = self.n_codebooks + 1
|
| 171 |
+
|
| 172 |
+
# Decrement countdown
|
| 173 |
+
if self.eos_countdown > 0:
|
| 174 |
+
self.eos_countdown -= 1
|
| 175 |
+
if self.eos_countdown == 0:
|
| 176 |
+
return True
|
| 177 |
+
|
| 178 |
+
return False
|
| 179 |
+
|
| 180 |
+
def __repr__(self) -> str:
|
| 181 |
+
return (
|
| 182 |
+
f"{type(self).__name__}(table_idx={self.table_idx}, "
|
| 183 |
+
f"cached_len={self.cached_len}, device_len={self.device_len}, "
|
| 184 |
+
f"max_device_len={self.max_device_len}, eos_frame={self.eos_frame})"
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
@dataclass
|
| 189 |
+
class TTSBatch:
|
| 190 |
+
"""Batch of TTS requests with 2D token format."""
|
| 191 |
+
|
| 192 |
+
reqs: List[TTSReq]
|
| 193 |
+
phase: Literal["prefill", "decode"]
|
| 194 |
+
# these fields should be set by scheduler
|
| 195 |
+
input_ids: torch.Tensor = field(init=False) # (total_tokens, frame_width)
|
| 196 |
+
out_loc: torch.Tensor = field(init=False)
|
| 197 |
+
padded_reqs: List[TTSReq] = field(init=False)
|
| 198 |
+
# this field should be set by attention backend
|
| 199 |
+
attn_metadata: BaseAttnMetadata = field(init=False)
|
| 200 |
+
# Optional per-batch speaker conditioning data (set by TTS scheduler).
|
| 201 |
+
speaker_emb_values: torch.Tensor | None = field(default=None, init=False)
|
| 202 |
+
speaker_token_positions: torch.Tensor | None = field(default=None, init=False)
|
| 203 |
+
|
| 204 |
+
@property
|
| 205 |
+
def is_prefill(self) -> bool:
|
| 206 |
+
return self.phase == "prefill"
|
| 207 |
+
|
| 208 |
+
@property
|
| 209 |
+
def is_decode(self) -> bool:
|
| 210 |
+
return self.phase == "decode"
|
| 211 |
+
|
| 212 |
+
@property
|
| 213 |
+
def size(self) -> int:
|
| 214 |
+
return len(self.reqs)
|
| 215 |
+
|
| 216 |
+
@property
|
| 217 |
+
def padded_size(self) -> int:
|
| 218 |
+
return len(self.padded_reqs)
|
zonos2/distributed/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .impl import DistributedCommunicator, destroy_distributed, enable_pynccl_distributed
|
| 2 |
+
from .info import DistributedInfo, get_tp_info, set_tp_info, try_get_tp_info
|
| 3 |
+
|
| 4 |
+
__all__ = [
|
| 5 |
+
"DistributedInfo",
|
| 6 |
+
"get_tp_info",
|
| 7 |
+
"set_tp_info",
|
| 8 |
+
"enable_pynccl_distributed",
|
| 9 |
+
"DistributedCommunicator",
|
| 10 |
+
"try_get_tp_info",
|
| 11 |
+
"destroy_distributed",
|
| 12 |
+
]
|
zonos2/distributed/impl.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from abc import ABC, abstractmethod
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import TYPE_CHECKING, List
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.distributed as dist
|
| 9 |
+
|
| 10 |
+
if TYPE_CHECKING:
|
| 11 |
+
from zonos2.distributed import DistributedInfo
|
| 12 |
+
from zonos2.kernel import PyNCCLCommunicator
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass
|
| 16 |
+
class DistributedImpl(ABC):
|
| 17 |
+
@abstractmethod
|
| 18 |
+
def all_reduce(self, x: torch.Tensor) -> torch.Tensor: ...
|
| 19 |
+
|
| 20 |
+
@abstractmethod
|
| 21 |
+
def all_gather(self, x: torch.Tensor) -> torch.Tensor: ...
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass
|
| 25 |
+
class TorchDistributedImpl(DistributedImpl):
|
| 26 |
+
def all_reduce(self, x: torch.Tensor) -> torch.Tensor:
|
| 27 |
+
tp_size = dist.get_world_size()
|
| 28 |
+
if tp_size == 1:
|
| 29 |
+
return x
|
| 30 |
+
dist.all_reduce(x, op=dist.ReduceOp.SUM)
|
| 31 |
+
return x
|
| 32 |
+
|
| 33 |
+
def all_gather(self, x: torch.Tensor) -> torch.Tensor:
|
| 34 |
+
tp_size = dist.get_world_size()
|
| 35 |
+
if tp_size == 1:
|
| 36 |
+
return x
|
| 37 |
+
shape = list(x.shape)
|
| 38 |
+
shape[0] = shape[0] * tp_size
|
| 39 |
+
out = torch.empty(shape, dtype=x.dtype, device=x.device)
|
| 40 |
+
dist.all_gather_into_tensor(out, x)
|
| 41 |
+
return out
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@dataclass
|
| 45 |
+
class PyNCCLDistributedImpl(DistributedImpl):
|
| 46 |
+
comm: PyNCCLCommunicator
|
| 47 |
+
|
| 48 |
+
def all_reduce(self, x: torch.Tensor) -> torch.Tensor:
|
| 49 |
+
self.comm.all_reduce(x, "sum")
|
| 50 |
+
return x
|
| 51 |
+
|
| 52 |
+
def all_gather(self, x: torch.Tensor) -> torch.Tensor:
|
| 53 |
+
from .info import get_tp_info
|
| 54 |
+
|
| 55 |
+
world_size = get_tp_info().size
|
| 56 |
+
output_shape = list(x.shape)
|
| 57 |
+
output_shape[0] *= world_size
|
| 58 |
+
result = x.new_empty(output_shape)
|
| 59 |
+
self.comm.all_gather(result, x)
|
| 60 |
+
return result
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class DistributedCommunicator:
|
| 64 |
+
plugins: List[DistributedImpl] = [TorchDistributedImpl()]
|
| 65 |
+
|
| 66 |
+
def all_reduce(self, x: torch.Tensor) -> torch.Tensor:
|
| 67 |
+
return self.plugins[-1].all_reduce(x)
|
| 68 |
+
|
| 69 |
+
def all_gather(self, x: torch.Tensor) -> torch.Tensor:
|
| 70 |
+
return self.plugins[-1].all_gather(x)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def enable_pynccl_distributed(
|
| 74 |
+
tp_info: DistributedInfo, tp_cpu_group: torch.distributed.ProcessGroup, max_bytes: int
|
| 75 |
+
) -> None:
|
| 76 |
+
"""
|
| 77 |
+
Enable PyNCCL-based distributed communication for tensor parallelism.
|
| 78 |
+
"""
|
| 79 |
+
if tp_info.size == 1:
|
| 80 |
+
return
|
| 81 |
+
from zonos2.kernel import init_pynccl
|
| 82 |
+
|
| 83 |
+
comm = init_pynccl(
|
| 84 |
+
tp_rank=tp_info.rank,
|
| 85 |
+
tp_size=tp_info.size,
|
| 86 |
+
tp_cpu_group=tp_cpu_group,
|
| 87 |
+
max_size_bytes=max_bytes,
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
DistributedCommunicator.plugins.append(PyNCCLDistributedImpl(comm))
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def destroy_distributed() -> None:
|
| 94 |
+
"""
|
| 95 |
+
Destroy all the distributed communication plugins.
|
| 96 |
+
"""
|
| 97 |
+
DistributedCommunicator.plugins = []
|
zonos2/distributed/info.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@dataclass(frozen=True)
|
| 7 |
+
class DistributedInfo: # should not export from here
|
| 8 |
+
rank: int
|
| 9 |
+
size: int
|
| 10 |
+
|
| 11 |
+
def __post_init__(self):
|
| 12 |
+
assert 0 <= self.rank < self.size
|
| 13 |
+
|
| 14 |
+
def is_primary(self) -> bool:
|
| 15 |
+
return self.rank == 0
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_TP_INFO: DistributedInfo | None = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def set_tp_info(rank: int, size: int) -> None:
|
| 22 |
+
global _TP_INFO
|
| 23 |
+
if _TP_INFO is not None:
|
| 24 |
+
raise RuntimeError("TP info has been set")
|
| 25 |
+
_TP_INFO = DistributedInfo(rank, size)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def get_tp_info() -> DistributedInfo:
|
| 29 |
+
if _TP_INFO is None:
|
| 30 |
+
raise RuntimeError("TP info has not been set")
|
| 31 |
+
return _TP_INFO
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def try_get_tp_info() -> DistributedInfo | None:
|
| 35 |
+
return _TP_INFO
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
__all__ = ["DistributedInfo", "set_tp_info", "get_tp_info", "try_get_tp_info"]
|
zonos2/engine/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .config import EngineConfig
|
| 2 |
+
from .engine import Engine
|
| 3 |
+
|
| 4 |
+
__all__ = ["Engine", "EngineConfig"]
|
zonos2/engine/config.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from functools import cached_property
|
| 5 |
+
from typing import TYPE_CHECKING, List
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from zonos2.distributed import DistributedInfo
|
| 9 |
+
from zonos2.utils import cached_load_checkpoint_config
|
| 10 |
+
|
| 11 |
+
if TYPE_CHECKING:
|
| 12 |
+
from zonos2.models import ModelConfig
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass(frozen=True)
|
| 16 |
+
class EngineConfig:
|
| 17 |
+
model_path: str
|
| 18 |
+
tp_info: DistributedInfo
|
| 19 |
+
dtype: torch.dtype
|
| 20 |
+
max_running_req: int = 256
|
| 21 |
+
attention_backend: str = "auto"
|
| 22 |
+
moe_backend: str = "fused_moe"
|
| 23 |
+
cuda_graph_bs: List[int] | None = None
|
| 24 |
+
cuda_graph_max_bs: int | None = None
|
| 25 |
+
page_size: int = 1
|
| 26 |
+
memory_ratio: float = 0.9
|
| 27 |
+
distributed_timeout: float = 60.0
|
| 28 |
+
use_dummy_weight: bool = False
|
| 29 |
+
use_pynccl: bool = True
|
| 30 |
+
max_seq_len_override: int | None = None
|
| 31 |
+
num_page_override: int | None = None # if not None, will override the number of pages
|
| 32 |
+
|
| 33 |
+
@cached_property
|
| 34 |
+
def checkpoint_config(self):
|
| 35 |
+
return cached_load_checkpoint_config(self.model_path)
|
| 36 |
+
|
| 37 |
+
@cached_property
|
| 38 |
+
def model_config(self) -> ModelConfig:
|
| 39 |
+
from zonos2.models import ModelConfig
|
| 40 |
+
|
| 41 |
+
return ModelConfig.from_checkpoint_config(self.checkpoint_config)
|
| 42 |
+
|
| 43 |
+
@property
|
| 44 |
+
def max_seq_len(self) -> int:
|
| 45 |
+
if self.max_seq_len_override is not None:
|
| 46 |
+
return self.max_seq_len_override
|
| 47 |
+
return self.model_config.rotary_config.max_position
|
| 48 |
+
|
| 49 |
+
@property
|
| 50 |
+
def max_forward_len(self) -> int:
|
| 51 |
+
return self.max_seq_len
|
| 52 |
+
|
| 53 |
+
@property
|
| 54 |
+
def distributed_addr(self) -> str:
|
| 55 |
+
return "tcp://127.0.0.1:23333"
|
zonos2/engine/engine.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from datetime import timedelta
|
| 5 |
+
from typing import Dict, Tuple
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from zonos2.attention import create_attention_backend
|
| 9 |
+
from zonos2.core import (
|
| 10 |
+
Context,
|
| 11 |
+
TTSBatch,
|
| 12 |
+
TTSReq,
|
| 13 |
+
TTSSamplingParams,
|
| 14 |
+
set_global_ctx,
|
| 15 |
+
)
|
| 16 |
+
from zonos2.distributed import destroy_distributed, enable_pynccl_distributed, set_tp_info
|
| 17 |
+
from zonos2.kvcache import create_kvcache
|
| 18 |
+
from zonos2.layers import set_rope_device
|
| 19 |
+
from zonos2.models import create_model, load_checkpoint_weight
|
| 20 |
+
from zonos2.utils import divide_even, init_logger, torch_dtype
|
| 21 |
+
|
| 22 |
+
from .config import EngineConfig
|
| 23 |
+
from .graph import GraphRunner, get_free_memory, mem_GB
|
| 24 |
+
|
| 25 |
+
logger = init_logger(__name__)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def create_page_table(shape: Tuple[int, int], device: torch.device) -> torch.Tensor:
|
| 29 |
+
return torch.zeros(shape, dtype=torch.int32, device=device)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _align_up_32(num: int) -> int:
|
| 33 |
+
return (num + 31) // 32 * 32
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _get_frame_width(model_config) -> int:
|
| 37 |
+
"""Get frame width for multi-codebook models.
|
| 38 |
+
|
| 39 |
+
Frame width = n_codebooks + (1 if text_vocab).
|
| 40 |
+
"""
|
| 41 |
+
width = model_config.n_codebooks
|
| 42 |
+
if model_config.text_vocab is not None:
|
| 43 |
+
width += 1
|
| 44 |
+
return width
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class Engine:
|
| 48 |
+
def __init__(self, config: EngineConfig):
|
| 49 |
+
self.model_config = config.model_config
|
| 50 |
+
set_tp_info(rank=config.tp_info.rank, size=config.tp_info.size)
|
| 51 |
+
|
| 52 |
+
self.device = torch.device("cuda")
|
| 53 |
+
# Importing the model eagerly pulls in sgl_kernel, which creates a CUDA
|
| 54 |
+
# context on the current device. The scheduler entry point binds this
|
| 55 |
+
# process to its rank's GPU before that import, so an existing context is
|
| 56 |
+
# expected; only one on the wrong device indicates a real ordering bug.
|
| 57 |
+
if config.tp_info.size > 1 and (
|
| 58 |
+
torch.cuda.is_initialized()
|
| 59 |
+
and torch.cuda.current_device() != config.tp_info.rank
|
| 60 |
+
):
|
| 61 |
+
raise RuntimeError(
|
| 62 |
+
f"CUDA was initialized on cuda:{torch.cuda.current_device()} before "
|
| 63 |
+
f"the engine could bind TP rank {config.tp_info.rank}; "
|
| 64 |
+
"set the device before importing model code."
|
| 65 |
+
)
|
| 66 |
+
if config.tp_info.size > 1:
|
| 67 |
+
torch.cuda.set_device(config.tp_info.rank)
|
| 68 |
+
self.stream = torch.cuda.Stream()
|
| 69 |
+
torch.cuda.set_stream(self.stream)
|
| 70 |
+
self.dtype = config.dtype
|
| 71 |
+
|
| 72 |
+
self.tp_cpu_group = self._init_communication(config)
|
| 73 |
+
init_free_memory = self._sync_get_memory()[1]
|
| 74 |
+
logger.info_rank0(f"Free memory before loading model: {mem_GB(init_free_memory)}")
|
| 75 |
+
|
| 76 |
+
# load model and determine number of pages
|
| 77 |
+
set_rope_device(self.device)
|
| 78 |
+
with torch.device("meta"), torch_dtype(config.dtype):
|
| 79 |
+
self.model = create_model(config)
|
| 80 |
+
|
| 81 |
+
# Log expected shapes before loading (for debugging dimension mismatches)
|
| 82 |
+
logger.info_rank0(f"Model config: num_kv_heads={config.model_config.num_kv_heads}, head_dim={config.model_config.head_dim}")
|
| 83 |
+
expected_kv_dim = config.model_config.num_kv_heads * config.model_config.head_dim
|
| 84 |
+
logger.info_rank0(f"Expected kv_dim per TP rank: {expected_kv_dim}")
|
| 85 |
+
|
| 86 |
+
state_dict = self._load_weight_state_dict(config)
|
| 87 |
+
self._check_speaker_lda_weights(state_dict)
|
| 88 |
+
|
| 89 |
+
# Debug: Check for temp keys in state_dict
|
| 90 |
+
if logger.isEnabledFor(logging.DEBUG):
|
| 91 |
+
temp_keys = [k for k in state_dict.keys() if '.temp' in k]
|
| 92 |
+
logger.debug("Found %d temp keys in state_dict: %s", len(temp_keys), temp_keys[:5])
|
| 93 |
+
if temp_keys:
|
| 94 |
+
first_temp = state_dict[temp_keys[0]]
|
| 95 |
+
logger.debug(
|
| 96 |
+
"First temp tensor: shape=%s, dtype=%s, values=%s",
|
| 97 |
+
first_temp.shape, first_temp.dtype, first_temp.flatten()[:4].tolist(),
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# Validate wkv weight shapes match config (catch n_kv_heads mismatch early)
|
| 101 |
+
for key, tensor in state_dict.items():
|
| 102 |
+
if ".wkv." in key and "weight" in key:
|
| 103 |
+
# wkv.weight should be (2, kv_dim, hidden_size) or (2*kv_dim, hidden_size)
|
| 104 |
+
if tensor.dim() == 3:
|
| 105 |
+
actual_kv_dim = tensor.shape[1]
|
| 106 |
+
else:
|
| 107 |
+
actual_kv_dim = tensor.shape[0] // 2
|
| 108 |
+
if actual_kv_dim != expected_kv_dim:
|
| 109 |
+
inferred_kv_heads = actual_kv_dim // config.model_config.head_dim
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"KV dimension mismatch for {key}: checkpoint has kv_dim={actual_kv_dim} "
|
| 112 |
+
f"(implying n_kv_heads={inferred_kv_heads}), but config expects "
|
| 113 |
+
f"kv_dim={expected_kv_dim} (n_kv_heads={config.model_config.num_kv_heads}). "
|
| 114 |
+
f"Please ensure your checkpoint's params.json has the correct n_kv_heads value "
|
| 115 |
+
f"matching the trained model weights."
|
| 116 |
+
)
|
| 117 |
+
break # Only need to check one layer
|
| 118 |
+
|
| 119 |
+
self.model.load_state_dict(state_dict)
|
| 120 |
+
|
| 121 |
+
# Debug: print attention temp values to verify they loaded correctly
|
| 122 |
+
if logger.isEnabledFor(logging.DEBUG):
|
| 123 |
+
logger.debug("Model type: %s", type(self.model).__name__)
|
| 124 |
+
logger.debug("Model has layers: %s", hasattr(self.model, 'layers'))
|
| 125 |
+
if hasattr(self.model, 'layers'):
|
| 126 |
+
logger.debug("layers type: %s", type(self.model.layers))
|
| 127 |
+
if hasattr(self.model.layers, 'op_list'):
|
| 128 |
+
logger.debug("num layers: %d", len(self.model.layers.op_list))
|
| 129 |
+
for i, layer in enumerate(self.model.layers.op_list[:2]):
|
| 130 |
+
logger.debug("Layer %d type: %s", i, type(layer).__name__)
|
| 131 |
+
if hasattr(layer, 'attention'):
|
| 132 |
+
attn = layer.attention
|
| 133 |
+
logger.debug("Layer %d attention type: %s", i, type(attn).__name__)
|
| 134 |
+
logger.debug("Layer %d has_qk_norm: %s", i, getattr(attn, 'has_qk_norm', 'N/A'))
|
| 135 |
+
if hasattr(attn, 'temp'):
|
| 136 |
+
temp = attn.temp
|
| 137 |
+
if temp is not None:
|
| 138 |
+
logger.debug("Layer %d attention.temp: shape=%s, values=%s", i, temp.shape, temp.flatten()[:8].tolist())
|
| 139 |
+
else:
|
| 140 |
+
logger.debug("Layer %d attention.temp is None", i)
|
| 141 |
+
|
| 142 |
+
self.num_pages = self.dummy_page = self._determine_num_pages(init_free_memory, config)
|
| 143 |
+
self.kv_cache = create_kvcache(
|
| 144 |
+
model_config=config.model_config,
|
| 145 |
+
num_pages=self.num_pages + 1, # +1 for dummy page
|
| 146 |
+
device=self.device,
|
| 147 |
+
dtype=self.dtype,
|
| 148 |
+
)
|
| 149 |
+
# NOTE: make page table 128 aligned (32 * sizeof(int32) == 128 bytes)
|
| 150 |
+
self.max_seq_len = _align_up_32(min(config.max_seq_len, self.num_pages))
|
| 151 |
+
self.page_table = create_page_table( # + 1 for dummy request
|
| 152 |
+
(config.max_running_req + 1, self.max_seq_len),
|
| 153 |
+
device=self.device,
|
| 154 |
+
)
|
| 155 |
+
self.attn_backend = create_attention_backend(
|
| 156 |
+
config.attention_backend,
|
| 157 |
+
config.model_config,
|
| 158 |
+
self.kv_cache,
|
| 159 |
+
self.page_table,
|
| 160 |
+
)
|
| 161 |
+
self.ctx = Context(page_size=1, attn_backend=self.attn_backend)
|
| 162 |
+
set_global_ctx(self.ctx)
|
| 163 |
+
|
| 164 |
+
post_free_memory = self._sync_get_memory()[0]
|
| 165 |
+
logger.info_rank0(f"Free memory after initialization: {mem_GB(post_free_memory)}")
|
| 166 |
+
|
| 167 |
+
frame_width = _get_frame_width(self.model_config)
|
| 168 |
+
self.dummy_req = TTSReq(
|
| 169 |
+
input_ids=torch.zeros((1, frame_width), dtype=torch.int32, device="cpu"),
|
| 170 |
+
table_idx=config.max_running_req,
|
| 171 |
+
cached_len=0,
|
| 172 |
+
output_len=1,
|
| 173 |
+
uid=-1,
|
| 174 |
+
sampling_params=TTSSamplingParams(),
|
| 175 |
+
cache_handle=None, # type: ignore
|
| 176 |
+
n_codebooks=self.model_config.n_codebooks,
|
| 177 |
+
eoa_id=self.model_config.eoa_id,
|
| 178 |
+
)
|
| 179 |
+
self.page_table[self.dummy_req.table_idx].fill_(self.dummy_page)
|
| 180 |
+
|
| 181 |
+
self.graph_runner = GraphRunner(
|
| 182 |
+
stream=self.stream,
|
| 183 |
+
device=self.device,
|
| 184 |
+
model=self.model,
|
| 185 |
+
attn_backend=self.attn_backend,
|
| 186 |
+
cuda_graph_bs=config.cuda_graph_bs,
|
| 187 |
+
cuda_graph_max_bs=config.cuda_graph_max_bs,
|
| 188 |
+
free_memory=init_free_memory,
|
| 189 |
+
max_seq_len=self.max_seq_len,
|
| 190 |
+
vocab_size=self.model_config.codebook_size + 2,
|
| 191 |
+
dummy_req=self.dummy_req,
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
def _init_communication(self, config: EngineConfig) -> torch.distributed.ProcessGroup:
|
| 195 |
+
if config.tp_info.size == 1 or config.use_pynccl:
|
| 196 |
+
torch.distributed.init_process_group(
|
| 197 |
+
backend="gloo",
|
| 198 |
+
rank=config.tp_info.rank,
|
| 199 |
+
world_size=config.tp_info.size,
|
| 200 |
+
timeout=timedelta(seconds=config.distributed_timeout),
|
| 201 |
+
init_method=config.distributed_addr,
|
| 202 |
+
)
|
| 203 |
+
tp_cpu_group = torch.distributed.group.WORLD
|
| 204 |
+
assert tp_cpu_group is not None
|
| 205 |
+
max_bytes = (
|
| 206 |
+
config.max_forward_len * config.model_config.hidden_size * self.dtype.itemsize
|
| 207 |
+
)
|
| 208 |
+
enable_pynccl_distributed(config.tp_info, tp_cpu_group, max_bytes)
|
| 209 |
+
else:
|
| 210 |
+
torch.distributed.init_process_group(
|
| 211 |
+
backend="nccl",
|
| 212 |
+
rank=config.tp_info.rank,
|
| 213 |
+
world_size=config.tp_info.size,
|
| 214 |
+
timeout=timedelta(seconds=config.distributed_timeout),
|
| 215 |
+
init_method=config.distributed_addr,
|
| 216 |
+
)
|
| 217 |
+
tp_cpu_group = torch.distributed.new_group(backend="gloo")
|
| 218 |
+
assert tp_cpu_group is not None
|
| 219 |
+
return tp_cpu_group
|
| 220 |
+
|
| 221 |
+
def _load_weight_state_dict(self, config: EngineConfig) -> Dict[str, torch.Tensor]:
|
| 222 |
+
if config.use_dummy_weight:
|
| 223 |
+
return {
|
| 224 |
+
k: torch.randn_like(v, device=self.device)
|
| 225 |
+
for k, v in self.model.state_dict().items()
|
| 226 |
+
}
|
| 227 |
+
else:
|
| 228 |
+
return {
|
| 229 |
+
k: v.to(self.dtype)
|
| 230 |
+
for k, v in load_checkpoint_weight(config.model_path, self.device).items()
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
def _check_speaker_lda_weights(self, state_dict: Dict[str, torch.Tensor]) -> None:
|
| 234 |
+
lda_keys = [key for key in state_dict if key.startswith("speaker_lda_projection.")]
|
| 235 |
+
if getattr(self.model, "speaker_lda_projection", None) is None:
|
| 236 |
+
for key in lda_keys:
|
| 237 |
+
state_dict.pop(key)
|
| 238 |
+
if lda_keys:
|
| 239 |
+
logger.warning_rank0(
|
| 240 |
+
"Dropped %d speaker_lda_projection weights because speaker LDA is not enabled.",
|
| 241 |
+
len(lda_keys),
|
| 242 |
+
)
|
| 243 |
+
return
|
| 244 |
+
|
| 245 |
+
missing = {
|
| 246 |
+
"speaker_lda_projection.weight",
|
| 247 |
+
"speaker_lda_projection.bias",
|
| 248 |
+
}.difference(lda_keys)
|
| 249 |
+
if missing:
|
| 250 |
+
raise ValueError(
|
| 251 |
+
"speaker_lda_dim is configured but the checkpoint is missing "
|
| 252 |
+
f"{sorted(missing)}. Use a checkpoint with the LDA projection merged in."
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
def _determine_num_pages(self, old_free_memory: int, config: EngineConfig) -> int:
|
| 256 |
+
new_free_memory = self._sync_get_memory()[1]
|
| 257 |
+
cache_per_page = (
|
| 258 |
+
2 # key + value
|
| 259 |
+
* self.model_config.head_dim
|
| 260 |
+
* divide_even(self.model_config.num_kv_heads, config.tp_info.size)
|
| 261 |
+
* config.page_size
|
| 262 |
+
* self.dtype.itemsize
|
| 263 |
+
* self.model_config.num_layers
|
| 264 |
+
)
|
| 265 |
+
num_pages = config.num_page_override
|
| 266 |
+
if num_pages is None:
|
| 267 |
+
model_memory = old_free_memory - new_free_memory
|
| 268 |
+
available_memory = int(config.memory_ratio * old_free_memory) - model_memory
|
| 269 |
+
num_pages = available_memory // cache_per_page
|
| 270 |
+
|
| 271 |
+
assert num_pages > 1, "Not enough memory for KV cache, try reducing --num-tokens"
|
| 272 |
+
real_kv_size = num_pages * cache_per_page
|
| 273 |
+
logger.info(f"Allocating {num_pages} pages for KV cache, K + V = {mem_GB(real_kv_size)}")
|
| 274 |
+
return num_pages
|
| 275 |
+
|
| 276 |
+
def _sync_get_memory(self) -> Tuple[int, int]:
|
| 277 |
+
"""Get the min and max free memory across TP ranks."""
|
| 278 |
+
torch.cuda.synchronize(self.device)
|
| 279 |
+
torch.cuda.empty_cache()
|
| 280 |
+
torch.cuda.reset_peak_memory_stats(self.device)
|
| 281 |
+
free_memory = get_free_memory(self.device)
|
| 282 |
+
free_mem_tensor = torch.tensor([free_memory, -free_memory], device="cpu", dtype=torch.int64)
|
| 283 |
+
torch.distributed.all_reduce(
|
| 284 |
+
free_mem_tensor, op=torch.distributed.ReduceOp.MIN, group=self.tp_cpu_group
|
| 285 |
+
)
|
| 286 |
+
min_free_memory = int(free_mem_tensor[0].item())
|
| 287 |
+
max_free_memory = -int(free_mem_tensor[1].item())
|
| 288 |
+
if max_free_memory - min_free_memory > 2 * 1024 * 1024 * 1024:
|
| 289 |
+
logger.error(
|
| 290 |
+
f"Memory across TP ranks are imbalanced:"
|
| 291 |
+
f" min {mem_GB(min_free_memory)}, max {mem_GB(max_free_memory)}"
|
| 292 |
+
)
|
| 293 |
+
raise RuntimeError("Memory across TP ranks are imbalanced")
|
| 294 |
+
|
| 295 |
+
return min_free_memory, max_free_memory
|
| 296 |
+
|
| 297 |
+
def forward_batch_tts(self, batch: TTSBatch) -> torch.Tensor:
|
| 298 |
+
"""Forward pass for TTS batch, returning multi-codebook logits.
|
| 299 |
+
|
| 300 |
+
Args:
|
| 301 |
+
batch: TTS batch with 2D input tokens
|
| 302 |
+
|
| 303 |
+
Returns:
|
| 304 |
+
Logits tensor of shape (batch_size, n_codebooks, vocab_size)
|
| 305 |
+
"""
|
| 306 |
+
assert torch.cuda.current_stream() == self.stream
|
| 307 |
+
with self.ctx.forward_batch(batch):
|
| 308 |
+
# For TTS, the model outputs multi-codebook logits directly
|
| 309 |
+
# The zonos2 model has a MultiParallelLMHead that outputs
|
| 310 |
+
# (batch, seq, n_codebooks, vocab_size)
|
| 311 |
+
if self.graph_runner.can_use_cuda_graph(batch):
|
| 312 |
+
logits = self.graph_runner.replay(batch)
|
| 313 |
+
else:
|
| 314 |
+
logits = self.model.forward()
|
| 315 |
+
|
| 316 |
+
# logits shape: (batch, n_codebooks, vocab) for decode
|
| 317 |
+
# or (total_tokens, n_codebooks, vocab) for prefill
|
| 318 |
+
# We only care about the last token per sequence
|
| 319 |
+
if batch.is_decode:
|
| 320 |
+
return logits[: batch.size]
|
| 321 |
+
else:
|
| 322 |
+
# For prefill, get the LAST token logits for each sequence
|
| 323 |
+
# logits shape: (total_tokens, n_codebooks, vocab)
|
| 324 |
+
# Need to extract logits at the last position of each sequence
|
| 325 |
+
last_indices = []
|
| 326 |
+
cumsum = 0
|
| 327 |
+
for req in batch.reqs:
|
| 328 |
+
cumsum += req.extend_len # extend_len = device_len - cached_len
|
| 329 |
+
last_indices.append(cumsum - 1)
|
| 330 |
+
last_indices = torch.tensor(last_indices, device=logits.device)
|
| 331 |
+
return logits[last_indices]
|
| 332 |
+
|
| 333 |
+
def shutdown(self) -> None:
|
| 334 |
+
self.graph_runner.destroy_cuda_graphs()
|
| 335 |
+
torch.distributed.destroy_process_group()
|
| 336 |
+
destroy_distributed()
|
zonos2/engine/graph.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import gc
|
| 4 |
+
from typing import TYPE_CHECKING, Dict, List
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
from zonos2.core import TTSBatch, TTSReq, get_global_ctx
|
| 9 |
+
from zonos2.distributed import get_tp_info
|
| 10 |
+
from zonos2.utils import init_logger
|
| 11 |
+
|
| 12 |
+
if TYPE_CHECKING:
|
| 13 |
+
from zonos2.attention import BaseAttnBackend
|
| 14 |
+
from zonos2.models import Zonos2ForCausalLM
|
| 15 |
+
|
| 16 |
+
logger = init_logger(__name__)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _determine_cuda_graph_bs(
|
| 20 |
+
cuda_graph_bs: List[int] | None,
|
| 21 |
+
cuda_graph_max_bs: int | None,
|
| 22 |
+
free_memory: int,
|
| 23 |
+
) -> List[int]:
|
| 24 |
+
if cuda_graph_bs is not None:
|
| 25 |
+
return cuda_graph_bs
|
| 26 |
+
|
| 27 |
+
free_memory_gb = free_memory / (1 << 30)
|
| 28 |
+
if cuda_graph_max_bs is None:
|
| 29 |
+
if free_memory_gb > 80: # H200
|
| 30 |
+
cuda_graph_max_bs = 256
|
| 31 |
+
else:
|
| 32 |
+
cuda_graph_max_bs = 160
|
| 33 |
+
|
| 34 |
+
if cuda_graph_max_bs < 1:
|
| 35 |
+
return []
|
| 36 |
+
|
| 37 |
+
return [1, 2, 4] + list(range(8, cuda_graph_max_bs + 1, 8))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def mem_GB(size: int) -> str:
|
| 41 |
+
return f"{size / (1024**3):.2f} GiB"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def get_free_memory(device: torch.device) -> int:
|
| 45 |
+
return torch.cuda.mem_get_info(device)[0]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class GraphRunner:
|
| 49 |
+
def __init__(
|
| 50 |
+
self,
|
| 51 |
+
stream: torch.cuda.Stream,
|
| 52 |
+
device: torch.device,
|
| 53 |
+
model: Zonos2ForCausalLM,
|
| 54 |
+
attn_backend: BaseAttnBackend,
|
| 55 |
+
cuda_graph_bs: List[int] | None,
|
| 56 |
+
cuda_graph_max_bs: int | None,
|
| 57 |
+
free_memory: int,
|
| 58 |
+
max_seq_len: int,
|
| 59 |
+
vocab_size: int,
|
| 60 |
+
dummy_req: TTSReq,
|
| 61 |
+
) -> None:
|
| 62 |
+
cuda_graph_bs = _determine_cuda_graph_bs(
|
| 63 |
+
cuda_graph_bs=cuda_graph_bs,
|
| 64 |
+
cuda_graph_max_bs=cuda_graph_max_bs,
|
| 65 |
+
free_memory=free_memory,
|
| 66 |
+
)
|
| 67 |
+
self.attn_backend = attn_backend
|
| 68 |
+
if not cuda_graph_bs:
|
| 69 |
+
logger.info_rank0("CUDA graph is disabled.")
|
| 70 |
+
self.max_graph_bs = 0
|
| 71 |
+
self.graph_bs_list = []
|
| 72 |
+
self.dummy_req = dummy_req
|
| 73 |
+
self.stream = stream
|
| 74 |
+
self.device = device
|
| 75 |
+
self.graph_map = {}
|
| 76 |
+
return
|
| 77 |
+
|
| 78 |
+
self.max_graph_bs = max(cuda_graph_bs)
|
| 79 |
+
self.graph_bs_list = sorted(cuda_graph_bs)
|
| 80 |
+
self.dummy_req = dummy_req
|
| 81 |
+
self.stream = stream
|
| 82 |
+
self.device = device
|
| 83 |
+
self.graph_map = self._capture_graphs(max_seq_len, vocab_size, model)
|
| 84 |
+
|
| 85 |
+
def _capture_graphs(self, max_seq_len: int, vocab_size: int, model: Zonos2ForCausalLM):
|
| 86 |
+
graph_map: Dict[int, torch.cuda.CUDAGraph] = {}
|
| 87 |
+
if self.max_graph_bs == 0:
|
| 88 |
+
logger.info_rank0("CUDA graph is disabled.")
|
| 89 |
+
return graph_map
|
| 90 |
+
|
| 91 |
+
self.logits = torch.empty(
|
| 92 |
+
(self.max_graph_bs, self.dummy_req.n_codebooks, vocab_size),
|
| 93 |
+
dtype=torch.float32,
|
| 94 |
+
device=self.device,
|
| 95 |
+
)
|
| 96 |
+
frame_width = self.dummy_req.frame_width
|
| 97 |
+
self.attn_backend.init_capture_graph(
|
| 98 |
+
max_seq_len=max_seq_len, bs_list=self.graph_bs_list, frame_width=frame_width
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
torch.cuda.synchronize(self.device)
|
| 102 |
+
torch.cuda.empty_cache()
|
| 103 |
+
torch.cuda.reset_peak_memory_stats(self.device)
|
| 104 |
+
|
| 105 |
+
logger.info_rank0(f"Start capturing CUDA graphs with sizes: {self.graph_bs_list}")
|
| 106 |
+
free_memory = get_free_memory(self.device)
|
| 107 |
+
logger.info_rank0(f"Free GPU memory before capturing CUDA graphs: {mem_GB(free_memory)}")
|
| 108 |
+
|
| 109 |
+
pbar = tqdm(
|
| 110 |
+
sorted(self.graph_bs_list, reverse=True),
|
| 111 |
+
desc="Preparing for capturing CUDA graphs...",
|
| 112 |
+
unit="batch",
|
| 113 |
+
disable=not get_tp_info().is_primary(), # disable for non-primary ranks
|
| 114 |
+
)
|
| 115 |
+
pool = None
|
| 116 |
+
for bs in pbar:
|
| 117 |
+
free_memory = get_free_memory(self.device)
|
| 118 |
+
pbar.desc = f"Capturing graphs: bs = {bs:<3} | avail_mem = {mem_GB(free_memory)}"
|
| 119 |
+
pbar.refresh()
|
| 120 |
+
graph = torch.cuda.CUDAGraph()
|
| 121 |
+
batch = TTSBatch(reqs=[self.dummy_req] * bs, phase="decode")
|
| 122 |
+
self.attn_backend.prepare_for_capture(batch)
|
| 123 |
+
with get_global_ctx().forward_batch(batch):
|
| 124 |
+
self.logits[:bs] = model.forward()
|
| 125 |
+
with torch.cuda.graph(graph, pool=pool, stream=self.stream):
|
| 126 |
+
self.logits[:bs] = model.forward()
|
| 127 |
+
if pool is None:
|
| 128 |
+
pool = graph.pool()
|
| 129 |
+
graph_map[bs] = graph
|
| 130 |
+
|
| 131 |
+
free_memory = get_free_memory(self.device)
|
| 132 |
+
logger.info_rank0(f"Free GPU memory after capturing CUDA graphs: {mem_GB(free_memory)}")
|
| 133 |
+
return graph_map
|
| 134 |
+
|
| 135 |
+
def can_use_cuda_graph(self, batch: TTSBatch) -> bool:
|
| 136 |
+
return batch.is_decode and batch.size <= self.max_graph_bs
|
| 137 |
+
|
| 138 |
+
def replay(self, batch: TTSBatch) -> torch.Tensor:
|
| 139 |
+
assert self.can_use_cuda_graph(batch)
|
| 140 |
+
g = self.graph_map[batch.padded_size]
|
| 141 |
+
self.attn_backend.prepare_for_replay(batch)
|
| 142 |
+
g.replay()
|
| 143 |
+
return self.logits[: batch.size]
|
| 144 |
+
|
| 145 |
+
def pad_batch(self, batch: TTSBatch) -> int:
|
| 146 |
+
padded_size = ( # choose the first available batch size
|
| 147 |
+
next(bs for bs in self.graph_bs_list if bs >= batch.size)
|
| 148 |
+
if self.can_use_cuda_graph(batch)
|
| 149 |
+
else batch.size
|
| 150 |
+
)
|
| 151 |
+
batch.padded_reqs = batch.reqs + [self.dummy_req] * (padded_size - batch.size)
|
| 152 |
+
return batch.padded_size - batch.size
|
| 153 |
+
|
| 154 |
+
# NOTE: This must be called before freeing NCCL resources to prevent program hang
|
| 155 |
+
def destroy_cuda_graphs(self) -> None:
|
| 156 |
+
del self.graph_map
|
| 157 |
+
gc.collect()
|
zonos2/engine/sample.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""TTS sampler for multi-codebook audio generation."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
from typing import TYPE_CHECKING, List
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from zonos2.tts.sampler import sample_tts
|
| 10 |
+
|
| 11 |
+
if TYPE_CHECKING:
|
| 12 |
+
from zonos2.core import TTSBatch
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def make_device_tensor(
|
| 16 |
+
data: List, dtype: torch.dtype, device: torch.device
|
| 17 |
+
) -> torch.Tensor:
|
| 18 |
+
"""Create a tensor on device from a list."""
|
| 19 |
+
return torch.tensor(data, dtype=dtype, pin_memory=True).to(
|
| 20 |
+
device, non_blocking=True
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass
|
| 25 |
+
class TTSBatchSamplingArgs:
|
| 26 |
+
"""Sampling arguments for a TTS batch."""
|
| 27 |
+
|
| 28 |
+
temperatures: torch.Tensor
|
| 29 |
+
top_ks: torch.Tensor
|
| 30 |
+
top_ps: torch.Tensor
|
| 31 |
+
min_ps: torch.Tensor
|
| 32 |
+
text_vocab: int
|
| 33 |
+
repetition_token_ids: torch.Tensor | None = None
|
| 34 |
+
repetition_penalties: torch.Tensor | None = None
|
| 35 |
+
generators: list[torch.Generator | None] = field(default_factory=list)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@dataclass
|
| 39 |
+
class TTSSampler:
|
| 40 |
+
"""Sampler for multi-codebook TTS output."""
|
| 41 |
+
|
| 42 |
+
device: torch.device
|
| 43 |
+
n_codebooks: int
|
| 44 |
+
codebook_size: int
|
| 45 |
+
text_vocab: int
|
| 46 |
+
|
| 47 |
+
def prepare(
|
| 48 |
+
self, batch: TTSBatch, token_pool: torch.Tensor | None = None
|
| 49 |
+
) -> TTSBatchSamplingArgs:
|
| 50 |
+
"""Prepare sampling arguments from a TTS batch.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
batch: TTS batch with requests
|
| 54 |
+
token_pool: Optional device-side token pool for recent generated history
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
TTSBatchSamplingArgs with per-sequence sampling parameters
|
| 58 |
+
"""
|
| 59 |
+
params = [r.sampling_params for r in batch.reqs]
|
| 60 |
+
|
| 61 |
+
MIN_T = 1e-6
|
| 62 |
+
temps = [max(p.temperature, MIN_T) for p in params]
|
| 63 |
+
top_ks = [p.topk if p.topk >= 1 else self.codebook_size for p in params]
|
| 64 |
+
top_ps = [min(max(p.top_p, 0.0), 1.0) for p in params]
|
| 65 |
+
min_ps = [max(p.min_p, 0.0) for p in params]
|
| 66 |
+
repetition_windows = [max(int(p.repetition_window), 0) for p in params]
|
| 67 |
+
repetition_penalties = [max(float(p.repetition_penalty), 1.0) for p in params]
|
| 68 |
+
repetition_codebooks = [
|
| 69 |
+
self.n_codebooks
|
| 70 |
+
if int(p.repetition_codebooks) < 0
|
| 71 |
+
else min(max(int(p.repetition_codebooks), 0), self.n_codebooks)
|
| 72 |
+
for p in params
|
| 73 |
+
]
|
| 74 |
+
generators = [r.rng for r in batch.reqs]
|
| 75 |
+
repetition_token_ids = None
|
| 76 |
+
repetition_penalties_tensor = None
|
| 77 |
+
|
| 78 |
+
active_windows = [
|
| 79 |
+
min(window, r.total_generated, r.device_len)
|
| 80 |
+
if window > 0 and penalty > 1.0 and codebooks > 0
|
| 81 |
+
else 0
|
| 82 |
+
for r, window, penalty, codebooks in zip(
|
| 83 |
+
batch.reqs,
|
| 84 |
+
repetition_windows,
|
| 85 |
+
repetition_penalties,
|
| 86 |
+
repetition_codebooks,
|
| 87 |
+
)
|
| 88 |
+
]
|
| 89 |
+
max_window = max(active_windows, default=0)
|
| 90 |
+
if max_window > 0:
|
| 91 |
+
repetition_token_ids = torch.full(
|
| 92 |
+
(len(batch.reqs), self.n_codebooks, max_window),
|
| 93 |
+
-1,
|
| 94 |
+
dtype=torch.int64,
|
| 95 |
+
device=self.device,
|
| 96 |
+
)
|
| 97 |
+
for i, (req, window) in enumerate(zip(batch.reqs, active_windows)):
|
| 98 |
+
if window <= 0:
|
| 99 |
+
continue
|
| 100 |
+
|
| 101 |
+
start = req.device_len - window
|
| 102 |
+
if token_pool is None:
|
| 103 |
+
history = req.input_ids[start : req.device_len, : self.n_codebooks].to(
|
| 104 |
+
device=self.device, dtype=torch.int64, non_blocking=True
|
| 105 |
+
)
|
| 106 |
+
else:
|
| 107 |
+
history = token_pool[
|
| 108 |
+
req.table_idx, start : req.device_len, : self.n_codebooks
|
| 109 |
+
].to(dtype=torch.int64)
|
| 110 |
+
|
| 111 |
+
history = history.transpose(0, 1).contiguous()
|
| 112 |
+
valid = (history >= 0) & (history < self.codebook_size)
|
| 113 |
+
valid[repetition_codebooks[i] :] = False
|
| 114 |
+
repetition_token_ids[i, :, -window:] = torch.where(
|
| 115 |
+
valid, history, torch.full_like(history, -1)
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
repetition_penalties_tensor = make_device_tensor(
|
| 119 |
+
repetition_penalties, torch.float32, self.device
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
return TTSBatchSamplingArgs(
|
| 123 |
+
temperatures=make_device_tensor(temps, torch.float32, self.device),
|
| 124 |
+
top_ks=make_device_tensor(top_ks, torch.int32, self.device),
|
| 125 |
+
top_ps=make_device_tensor(top_ps, torch.float32, self.device),
|
| 126 |
+
min_ps=make_device_tensor(min_ps, torch.float32, self.device),
|
| 127 |
+
text_vocab=self.text_vocab,
|
| 128 |
+
repetition_token_ids=repetition_token_ids,
|
| 129 |
+
repetition_penalties=repetition_penalties_tensor,
|
| 130 |
+
generators=generators,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
def sample(
|
| 134 |
+
self, logits: torch.Tensor, args: TTSBatchSamplingArgs
|
| 135 |
+
) -> List[List[int]]:
|
| 136 |
+
"""Sample from multi-codebook logits.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
logits: Shape (B, n_codebooks, vocab_size)
|
| 140 |
+
args: Sampling arguments
|
| 141 |
+
|
| 142 |
+
Returns:
|
| 143 |
+
List of unpacked tokens [cb0, cb1, ..., cb8, text_placeholder]
|
| 144 |
+
"""
|
| 145 |
+
return sample_tts(
|
| 146 |
+
logits=logits,
|
| 147 |
+
temperatures=args.temperatures,
|
| 148 |
+
top_ks=args.top_ks,
|
| 149 |
+
top_ps=args.top_ps,
|
| 150 |
+
min_ps=args.min_ps,
|
| 151 |
+
repetition_token_ids=args.repetition_token_ids,
|
| 152 |
+
repetition_penalties=args.repetition_penalties,
|
| 153 |
+
text_vocab=args.text_vocab,
|
| 154 |
+
generators=args.generators,
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
def sample_to_tensor(
|
| 158 |
+
self, logits: torch.Tensor, args: TTSBatchSamplingArgs
|
| 159 |
+
) -> torch.Tensor:
|
| 160 |
+
"""Sample and return as tensor instead of list.
|
| 161 |
+
|
| 162 |
+
Args:
|
| 163 |
+
logits: Shape (B, n_codebooks, vocab_size)
|
| 164 |
+
args: Sampling arguments
|
| 165 |
+
|
| 166 |
+
Returns:
|
| 167 |
+
Tensor of shape (B, n_codebooks + 1) with sampled tokens
|
| 168 |
+
"""
|
| 169 |
+
tokens_list = self.sample(logits, args)
|
| 170 |
+
return torch.tensor(tokens_list, dtype=torch.int32, device=self.device)
|
zonos2/env.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from functools import partial
|
| 5 |
+
from typing import Callable, Generic, TypeVar
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class BaseEnv:
|
| 9 |
+
def _init(self, name: str) -> None:
|
| 10 |
+
raise NotImplementedError
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
T = TypeVar("T")
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class EnvVar(BaseEnv, Generic[T]):
|
| 17 |
+
def __init__(self, default_value: T, fn: Callable[[str], T]):
|
| 18 |
+
self.value = default_value
|
| 19 |
+
self.fn = fn
|
| 20 |
+
super().__init__()
|
| 21 |
+
|
| 22 |
+
def _init(self, name: str) -> None:
|
| 23 |
+
env_value = os.getenv(name)
|
| 24 |
+
if env_value is not None:
|
| 25 |
+
try:
|
| 26 |
+
self.value = self.fn(env_value)
|
| 27 |
+
except Exception:
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
def __bool__(self):
|
| 31 |
+
return self.value
|
| 32 |
+
|
| 33 |
+
def __str__(self):
|
| 34 |
+
return str(self.value)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
_TO_BOOL = lambda x: x.lower() in ("1", "true", "yes")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _PARSE_MEM_BYTES(mem: str) -> int:
|
| 41 |
+
mem = mem.strip().upper()
|
| 42 |
+
if not mem[-1].isalpha():
|
| 43 |
+
return int(mem)
|
| 44 |
+
if mem.endswith("B"):
|
| 45 |
+
mem = mem[:-1]
|
| 46 |
+
UNIT_MAP = {"K": 1024, "M": 1024**2, "G": 1024**3}
|
| 47 |
+
return int(float(mem[:-1]) * UNIT_MAP[mem[-1]])
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
ZONOS2_ENV_PREFIX = "ZONOS2_"
|
| 51 |
+
EnvInt = partial(EnvVar[int], fn=int)
|
| 52 |
+
EnvFloat = partial(EnvVar[float], fn=float)
|
| 53 |
+
EnvBool = partial(EnvVar[bool], fn=_TO_BOOL)
|
| 54 |
+
EnvOption = partial(EnvVar[bool | None], fn=_TO_BOOL, default_value=None)
|
| 55 |
+
EnvMem = partial(EnvVar[int], fn=_PARSE_MEM_BYTES)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class EnvClassSingleton:
|
| 59 |
+
_instance: EnvClassSingleton | None = None
|
| 60 |
+
|
| 61 |
+
# shell
|
| 62 |
+
SHELL_MAX_TOKENS = EnvInt(2048)
|
| 63 |
+
SHELL_TOP_K = EnvInt(-1)
|
| 64 |
+
SHELL_TOP_P = EnvFloat(1.0)
|
| 65 |
+
SHELL_TEMPERATURE = EnvFloat(0.6)
|
| 66 |
+
|
| 67 |
+
# backend runtime
|
| 68 |
+
FLASHINFER_USE_TENSOR_CORES = EnvOption()
|
| 69 |
+
DISABLE_OVERLAP_SCHEDULING = EnvBool(False)
|
| 70 |
+
PYNCCL_MAX_BUFFER_SIZE = EnvMem(1024**3)
|
| 71 |
+
|
| 72 |
+
def __new__(cls):
|
| 73 |
+
# single instance
|
| 74 |
+
if cls._instance is None:
|
| 75 |
+
cls._instance = super().__new__(cls)
|
| 76 |
+
return cls._instance
|
| 77 |
+
|
| 78 |
+
def __init__(self) -> None:
|
| 79 |
+
for attr_name in dir(self):
|
| 80 |
+
if attr_name.startswith("_"):
|
| 81 |
+
continue
|
| 82 |
+
attr_value = getattr(self, attr_name)
|
| 83 |
+
assert isinstance(attr_value, BaseEnv)
|
| 84 |
+
attr_value._init(f"{ZONOS2_ENV_PREFIX}{attr_name}")
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
ENV = EnvClassSingleton()
|
zonos2/kernel/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .index import indexing
|
| 2 |
+
from .pynccl import PyNCCLCommunicator, init_pynccl
|
| 3 |
+
from .radix import fast_compare_key
|
| 4 |
+
from .store import store_cache
|
| 5 |
+
from .tensor import test_tensor
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"indexing",
|
| 9 |
+
"fast_compare_key",
|
| 10 |
+
"store_cache",
|
| 11 |
+
"test_tensor",
|
| 12 |
+
"init_pynccl",
|
| 13 |
+
"PyNCCLCommunicator",
|
| 14 |
+
]
|
zonos2/kernel/__main__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
assert __name__ == "__main__"
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def generate_clangd():
|
| 5 |
+
import os
|
| 6 |
+
import subprocess
|
| 7 |
+
|
| 8 |
+
from tvm_ffi.libinfo import find_dlpack_include_path, find_include_path
|
| 9 |
+
from zonos2.kernel.utils import DEFAULT_INCLUDE
|
| 10 |
+
from zonos2.utils import init_logger
|
| 11 |
+
|
| 12 |
+
logger = init_logger(__name__)
|
| 13 |
+
logger.info("Generating .clangd file...")
|
| 14 |
+
include_paths = [find_include_path(), find_dlpack_include_path()] + DEFAULT_INCLUDE
|
| 15 |
+
status = subprocess.run(
|
| 16 |
+
args=["nvidia-smi", "--query-gpu=compute_cap", "--format=csv,noheader"],
|
| 17 |
+
capture_output=True,
|
| 18 |
+
check=True,
|
| 19 |
+
)
|
| 20 |
+
compute_cap = status.stdout.decode("utf-8").strip().split("\n")[0]
|
| 21 |
+
major, minor = compute_cap.split(".")
|
| 22 |
+
compile_flags = ",\n ".join(
|
| 23 |
+
[
|
| 24 |
+
"-xcuda",
|
| 25 |
+
f"--cuda-gpu-arch=sm_{major}{minor}",
|
| 26 |
+
"-std=c++20",
|
| 27 |
+
"-Wall",
|
| 28 |
+
"-Wextra",
|
| 29 |
+
]
|
| 30 |
+
+ [f"-isystem{path}" for path in include_paths]
|
| 31 |
+
)
|
| 32 |
+
clangd_content = f"""
|
| 33 |
+
CompileFlags:
|
| 34 |
+
Add: [
|
| 35 |
+
{compile_flags}
|
| 36 |
+
]
|
| 37 |
+
"""
|
| 38 |
+
if os.path.exists(".clangd"):
|
| 39 |
+
logger.warning(".clangd file already exists, nothing done.")
|
| 40 |
+
logger.warning(f"suggested content: {clangd_content}")
|
| 41 |
+
else:
|
| 42 |
+
with open(".clangd", "w") as f:
|
| 43 |
+
f.write(clangd_content)
|
| 44 |
+
logger.info(".clangd file generated.")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
generate_clangd()
|
zonos2/kernel/csrc/include/zonos2/nccl227.h
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*************************************************************************
|
| 2 |
+
* Copyright (c) 2015-2021, NVIDIA CORPORATION. All rights reserved.
|
| 3 |
+
*
|
| 4 |
+
* See LICENSE.txt for license information
|
| 5 |
+
************************************************************************/
|
| 6 |
+
|
| 7 |
+
#ifndef NCCL_H_
|
| 8 |
+
#define NCCL_H_
|
| 9 |
+
|
| 10 |
+
#include <cuda_runtime.h>
|
| 11 |
+
#include <cuda_fp16.h>
|
| 12 |
+
#if CUDART_VERSION >= 11000
|
| 13 |
+
#include <cuda_bf16.h>
|
| 14 |
+
#endif
|
| 15 |
+
#if __cplusplus && CUDART_VERSION >= 11080
|
| 16 |
+
#include <cuda_fp8.h>
|
| 17 |
+
#endif
|
| 18 |
+
|
| 19 |
+
#define NCCL_MAJOR 2
|
| 20 |
+
#define NCCL_MINOR 27
|
| 21 |
+
#define NCCL_PATCH 1
|
| 22 |
+
#define NCCL_SUFFIX ""
|
| 23 |
+
|
| 24 |
+
#define NCCL_VERSION_CODE 22701
|
| 25 |
+
#define NCCL_VERSION(X,Y,Z) (((X) <= 2 && (Y) <= 8) ? (X) * 1000 + (Y) * 100 + (Z) : (X) * 10000 + (Y) * 100 + (Z))
|
| 26 |
+
|
| 27 |
+
#ifdef __cplusplus
|
| 28 |
+
extern "C" {
|
| 29 |
+
#endif
|
| 30 |
+
|
| 31 |
+
#include <limits.h>
|
| 32 |
+
|
| 33 |
+
/* Opaque handle to communicator */
|
| 34 |
+
typedef struct ncclComm* ncclComm_t;
|
| 35 |
+
typedef struct ncclWindow_vidmem* ncclWindow_t;
|
| 36 |
+
#define NCCL_COMM_NULL NULL
|
| 37 |
+
|
| 38 |
+
#define NCCL_UNIQUE_ID_BYTES 128
|
| 39 |
+
typedef struct { char internal[NCCL_UNIQUE_ID_BYTES]; } ncclUniqueId;
|
| 40 |
+
|
| 41 |
+
/* Error type */
|
| 42 |
+
typedef enum { ncclSuccess = 0,
|
| 43 |
+
ncclUnhandledCudaError = 1,
|
| 44 |
+
ncclSystemError = 2,
|
| 45 |
+
ncclInternalError = 3,
|
| 46 |
+
ncclInvalidArgument = 4,
|
| 47 |
+
ncclInvalidUsage = 5,
|
| 48 |
+
ncclRemoteError = 6,
|
| 49 |
+
ncclInProgress = 7,
|
| 50 |
+
ncclNumResults = 8 } ncclResult_t;
|
| 51 |
+
|
| 52 |
+
#define NCCL_CONFIG_UNDEF_INT INT_MIN
|
| 53 |
+
#define NCCL_CONFIG_UNDEF_PTR NULL
|
| 54 |
+
#define NCCL_SPLIT_NOCOLOR -1
|
| 55 |
+
#define NCCL_UNDEF_FLOAT -1.0f
|
| 56 |
+
|
| 57 |
+
/* Window Registration flags */
|
| 58 |
+
#define NCCL_WIN_DEFAULT 0x00
|
| 59 |
+
#define NCCL_WIN_COLL_SYMMETRIC 0x01
|
| 60 |
+
|
| 61 |
+
#define NCCL_WIN_REQUIRED_ALIGNMENT 4096
|
| 62 |
+
|
| 63 |
+
/* NCCL performance policy */
|
| 64 |
+
#define NCCL_CTA_POLICY_DEFAULT 0x00
|
| 65 |
+
#define NCCL_CTA_POLICY_EFFICIENCY 0x01
|
| 66 |
+
#define NCCL_CTA_POLICY_ZERO 0x02
|
| 67 |
+
|
| 68 |
+
/* ncclCommShrink flags*/
|
| 69 |
+
#define NCCL_SHRINK_DEFAULT 0x00 /* shrink the parent communicator */
|
| 70 |
+
#define NCCL_SHRINK_ABORT 0x01 /* First, terminate ongoing parent operations, and then shrink the parent communicator */
|
| 71 |
+
|
| 72 |
+
/* Communicator configuration. Users can assign value to attributes to specify the
|
| 73 |
+
* behavior of a communicator. */
|
| 74 |
+
typedef struct ncclConfig_v22800 {
|
| 75 |
+
/* attributes that users should never touch. */
|
| 76 |
+
size_t size;
|
| 77 |
+
unsigned int magic;
|
| 78 |
+
unsigned int version;
|
| 79 |
+
/* attributes that users are able to customize. */
|
| 80 |
+
int blocking;
|
| 81 |
+
int cgaClusterSize;
|
| 82 |
+
int minCTAs;
|
| 83 |
+
int maxCTAs;
|
| 84 |
+
const char *netName;
|
| 85 |
+
int splitShare;
|
| 86 |
+
int trafficClass;
|
| 87 |
+
const char *commName;
|
| 88 |
+
int collnetEnable;
|
| 89 |
+
int CTAPolicy;
|
| 90 |
+
int shrinkShare;
|
| 91 |
+
int nvlsCTAs;
|
| 92 |
+
int nChannelsPerNetPeer;
|
| 93 |
+
int nvlinkCentricSched;
|
| 94 |
+
} ncclConfig_t;
|
| 95 |
+
|
| 96 |
+
/* Config initializer must be assigned to initialize config structure when it is created.
|
| 97 |
+
* Not initialized config will result in NCCL error. */
|
| 98 |
+
#define NCCL_CONFIG_INITIALIZER { \
|
| 99 |
+
sizeof(ncclConfig_t), /* size */ \
|
| 100 |
+
0xcafebeef, /* magic */ \
|
| 101 |
+
NCCL_VERSION(NCCL_MAJOR, NCCL_MINOR, NCCL_PATCH), /* version */ \
|
| 102 |
+
NCCL_CONFIG_UNDEF_INT, /* blocking */ \
|
| 103 |
+
NCCL_CONFIG_UNDEF_INT, /* cgaClusterSize */ \
|
| 104 |
+
NCCL_CONFIG_UNDEF_INT, /* minCTAs */ \
|
| 105 |
+
NCCL_CONFIG_UNDEF_INT, /* maxCTAs */ \
|
| 106 |
+
NCCL_CONFIG_UNDEF_PTR, /* netName */ \
|
| 107 |
+
NCCL_CONFIG_UNDEF_INT, /* splitShare */ \
|
| 108 |
+
NCCL_CONFIG_UNDEF_INT, /* trafficClass */ \
|
| 109 |
+
NCCL_CONFIG_UNDEF_PTR, /* commName */ \
|
| 110 |
+
NCCL_CONFIG_UNDEF_INT, /* collnetEnable */ \
|
| 111 |
+
NCCL_CONFIG_UNDEF_INT, /* CTAPolicy */ \
|
| 112 |
+
NCCL_CONFIG_UNDEF_INT, /* shrinkShare */ \
|
| 113 |
+
NCCL_CONFIG_UNDEF_INT, /* nvlsCTAs */ \
|
| 114 |
+
NCCL_CONFIG_UNDEF_INT, /* nChannelsPerNetPeer */ \
|
| 115 |
+
NCCL_CONFIG_UNDEF_INT, /* nvlinkCentricSched */ \
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
/* This struct will be used by ncclGroupSimulateEnd() API to query information about simulation. */
|
| 119 |
+
typedef struct ncclSimInfo_v22200 {
|
| 120 |
+
size_t size;
|
| 121 |
+
unsigned int magic;
|
| 122 |
+
unsigned int version;
|
| 123 |
+
float estimatedTime;
|
| 124 |
+
} ncclSimInfo_t;
|
| 125 |
+
|
| 126 |
+
/* NCCL_SIM_INFO_INITIALIZER must be assigned to initialize simInfo structure when it is created.
|
| 127 |
+
* Not initialized simInfo will result in NCCL error. */
|
| 128 |
+
#define NCCL_SIM_INFO_INITIALIZER { \
|
| 129 |
+
sizeof(ncclSimInfo_t), /* size */ \
|
| 130 |
+
0x74685283, /* magic */ \
|
| 131 |
+
NCCL_VERSION(NCCL_MAJOR, NCCL_MINOR, NCCL_PATCH), /* version */ \
|
| 132 |
+
NCCL_UNDEF_FLOAT /* estimated time */ \
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
/* NCCL malloc and free function for all types of NCCL optimizations
|
| 136 |
+
* (e.g. user buffer registration). The actual allocated size might
|
| 137 |
+
* be larger than requested due to granularity requirement. */
|
| 138 |
+
ncclResult_t ncclMemAlloc(void** ptr, size_t size);
|
| 139 |
+
ncclResult_t pncclMemAlloc(void** ptr, size_t size);
|
| 140 |
+
|
| 141 |
+
ncclResult_t ncclMemFree(void *ptr);
|
| 142 |
+
ncclResult_t pncclMemFree(void *ptr);
|
| 143 |
+
|
| 144 |
+
/* Return the NCCL_VERSION_CODE of the NCCL library in the supplied integer.
|
| 145 |
+
* This integer is coded with the MAJOR, MINOR and PATCH level of the
|
| 146 |
+
* NCCL library
|
| 147 |
+
*/
|
| 148 |
+
ncclResult_t ncclGetVersion(int *version);
|
| 149 |
+
ncclResult_t pncclGetVersion(int *version);
|
| 150 |
+
|
| 151 |
+
/* Generates an Id to be used in ncclCommInitRank. ncclGetUniqueId should be
|
| 152 |
+
* called once and the Id should be distributed to all ranks in the
|
| 153 |
+
* communicator before calling ncclCommInitRank. */
|
| 154 |
+
ncclResult_t ncclGetUniqueId(ncclUniqueId* uniqueId);
|
| 155 |
+
ncclResult_t pncclGetUniqueId(ncclUniqueId* uniqueId);
|
| 156 |
+
|
| 157 |
+
/* Create a new communicator (multi thread/process version) with a configuration
|
| 158 |
+
* set by users. */
|
| 159 |
+
ncclResult_t ncclCommInitRankConfig(ncclComm_t* comm, int nranks, ncclUniqueId commId, int rank, ncclConfig_t* config);
|
| 160 |
+
ncclResult_t pncclCommInitRankConfig(ncclComm_t* comm, int nranks, ncclUniqueId commId, int rank, ncclConfig_t* config);
|
| 161 |
+
|
| 162 |
+
/* Creates a new communicator (multi thread/process version).
|
| 163 |
+
* rank must be between 0 and nranks-1 and unique within a communicator clique.
|
| 164 |
+
* Each rank is associated to a CUDA device, which has to be set before calling
|
| 165 |
+
* ncclCommInitRank.
|
| 166 |
+
* ncclCommInitRank implicitly syncronizes with other ranks, so it must be
|
| 167 |
+
* called by different threads/processes or use ncclGroupStart/ncclGroupEnd. */
|
| 168 |
+
ncclResult_t ncclCommInitRank(ncclComm_t* comm, int nranks, ncclUniqueId commId, int rank);
|
| 169 |
+
ncclResult_t pncclCommInitRank(ncclComm_t* comm, int nranks, ncclUniqueId commId, int rank);
|
| 170 |
+
|
| 171 |
+
/* Creates a clique of communicators (single process version).
|
| 172 |
+
* This is a convenience function to create a single-process communicator clique.
|
| 173 |
+
* Returns an array of ndev newly initialized communicators in comm.
|
| 174 |
+
* comm should be pre-allocated with size at least ndev*sizeof(ncclComm_t).
|
| 175 |
+
* If devlist is NULL, the first ndev CUDA devices are used.
|
| 176 |
+
* Order of devlist defines user-order of processors within the communicator. */
|
| 177 |
+
ncclResult_t ncclCommInitAll(ncclComm_t* comm, int ndev, const int* devlist);
|
| 178 |
+
ncclResult_t pncclCommInitAll(ncclComm_t* comm, int ndev, const int* devlist);
|
| 179 |
+
|
| 180 |
+
/* Finalize a communicator. ncclCommFinalize flushes all issued communications,
|
| 181 |
+
* and marks communicator state as ncclInProgress. The state will change to ncclSuccess
|
| 182 |
+
* when the communicator is globally quiescent and related resources are freed; then,
|
| 183 |
+
* calling ncclCommDestroy can locally free the rest of the resources (e.g. communicator
|
| 184 |
+
* itself) without blocking. */
|
| 185 |
+
ncclResult_t ncclCommFinalize(ncclComm_t comm);
|
| 186 |
+
ncclResult_t pncclCommFinalize(ncclComm_t comm);
|
| 187 |
+
|
| 188 |
+
/* Frees local resources associated with communicator object. */
|
| 189 |
+
ncclResult_t ncclCommDestroy(ncclComm_t comm);
|
| 190 |
+
ncclResult_t pncclCommDestroy(ncclComm_t comm);
|
| 191 |
+
|
| 192 |
+
/* Frees resources associated with communicator object and aborts any operations
|
| 193 |
+
* that might still be running on the device. */
|
| 194 |
+
ncclResult_t ncclCommAbort(ncclComm_t comm);
|
| 195 |
+
ncclResult_t pncclCommAbort(ncclComm_t comm);
|
| 196 |
+
|
| 197 |
+
/* Creates one or more communicators from an existing one.
|
| 198 |
+
* Ranks with the same color will end up in the same communicator.
|
| 199 |
+
* Within the new communicator, key will be used to order ranks.
|
| 200 |
+
* NCCL_SPLIT_NOCOLOR as color will indicate the rank will not be part of any group
|
| 201 |
+
* and will therefore return a NULL communicator.
|
| 202 |
+
* If config is NULL, the new communicator will inherit the original communicator's
|
| 203 |
+
* configuration*/
|
| 204 |
+
ncclResult_t ncclCommSplit(ncclComm_t comm, int color, int key, ncclComm_t *newcomm, ncclConfig_t* config);
|
| 205 |
+
ncclResult_t pncclCommSplit(ncclComm_t comm, int color, int key, ncclComm_t *newcomm, ncclConfig_t* config);
|
| 206 |
+
|
| 207 |
+
/* Shrink existing communicator.
|
| 208 |
+
* Ranks in excludeRanksList will be removed form the existing communicator.
|
| 209 |
+
* Within the new communicator, ranks will be re-ordered to fill the gap of removed ones.
|
| 210 |
+
* If config is NULL, the new communicator will inherit the original communicator's configuration
|
| 211 |
+
* The flag enables NCCL to adapt to various states of the parent communicator, see NCCL_SHRINK flags.*/
|
| 212 |
+
ncclResult_t ncclCommShrink(ncclComm_t comm, int* excludeRanksList, int excludeRanksCount, ncclComm_t* newcomm, ncclConfig_t* config, int shrinkFlags);
|
| 213 |
+
ncclResult_t pncclCommShrink(ncclComm_t comm, int* excludeRanksList, int excludeRanksCount, ncclComm_t* newcomm, ncclConfig_t* config, int shrinkFlags);
|
| 214 |
+
|
| 215 |
+
/* Creates a new communicator (multi thread/process version), similar to ncclCommInitRankConfig.
|
| 216 |
+
* Allows to use more than one ncclUniqueId (up to one per rank), indicated by nId, to accelerate the init operation.
|
| 217 |
+
* The number of ncclUniqueIds and their order must be the same for every rank.
|
| 218 |
+
*/
|
| 219 |
+
ncclResult_t ncclCommInitRankScalable(ncclComm_t* newcomm, int nranks, int myrank, int nId, ncclUniqueId* commIds, ncclConfig_t* config);
|
| 220 |
+
ncclResult_t pncclCommInitRankScalable(ncclComm_t* newcomm, int nranks, int myrank, int nId, ncclUniqueId* commIds, ncclConfig_t* config);
|
| 221 |
+
|
| 222 |
+
/* Returns a string for each error code. */
|
| 223 |
+
const char* ncclGetErrorString(ncclResult_t result);
|
| 224 |
+
const char* pncclGetErrorString(ncclResult_t result);
|
| 225 |
+
|
| 226 |
+
/* Returns a human-readable message of the last error that occurred. */
|
| 227 |
+
const char* ncclGetLastError(ncclComm_t comm);
|
| 228 |
+
const char* pncclGetLastError(ncclComm_t comm);
|
| 229 |
+
|
| 230 |
+
/* Reload environment variables that determine logging. */
|
| 231 |
+
__attribute__ ((deprecated("ncclResetDebugInit is not supported as part of the NCCL API and will be removed in the future")))
|
| 232 |
+
void ncclResetDebugInit();
|
| 233 |
+
__attribute__ ((deprecated("pncclResetDebugInit is not supported as part of the NCCL API and will be removed in the future")))
|
| 234 |
+
void pncclResetDebugInit();
|
| 235 |
+
|
| 236 |
+
/* Checks whether the comm has encountered any asynchronous errors */
|
| 237 |
+
ncclResult_t ncclCommGetAsyncError(ncclComm_t comm, ncclResult_t *asyncError);
|
| 238 |
+
ncclResult_t pncclCommGetAsyncError(ncclComm_t comm, ncclResult_t *asyncError);
|
| 239 |
+
|
| 240 |
+
/* Gets the number of ranks in the communicator clique. */
|
| 241 |
+
ncclResult_t ncclCommCount(const ncclComm_t comm, int* count);
|
| 242 |
+
ncclResult_t pncclCommCount(const ncclComm_t comm, int* count);
|
| 243 |
+
|
| 244 |
+
/* Returns the cuda device number associated with the communicator. */
|
| 245 |
+
ncclResult_t ncclCommCuDevice(const ncclComm_t comm, int* device);
|
| 246 |
+
ncclResult_t pncclCommCuDevice(const ncclComm_t comm, int* device);
|
| 247 |
+
|
| 248 |
+
/* Returns the user-ordered "rank" associated with the communicator. */
|
| 249 |
+
ncclResult_t ncclCommUserRank(const ncclComm_t comm, int* rank);
|
| 250 |
+
ncclResult_t pncclCommUserRank(const ncclComm_t comm, int* rank);
|
| 251 |
+
|
| 252 |
+
/* Register CUDA buffer for zero-copy operation */
|
| 253 |
+
ncclResult_t ncclCommRegister(const ncclComm_t comm, void* buff, size_t size, void** handle);
|
| 254 |
+
ncclResult_t pncclCommRegister(const ncclComm_t comm, void* buff, size_t size, void** handle);
|
| 255 |
+
|
| 256 |
+
/* Deregister CUDA buffer */
|
| 257 |
+
ncclResult_t ncclCommDeregister(const ncclComm_t comm, void* handle);
|
| 258 |
+
ncclResult_t pncclCommDeregister(const ncclComm_t comm, void* handle);
|
| 259 |
+
|
| 260 |
+
/* Register memory window */
|
| 261 |
+
ncclResult_t ncclCommWindowRegister(ncclComm_t comm, void* buff, size_t size, ncclWindow_t* win, int winFlags);
|
| 262 |
+
ncclResult_t pncclCommWindowRegister(ncclComm_t comm, void* buff, size_t size, ncclWindow_t* win, int winFlags);
|
| 263 |
+
|
| 264 |
+
/* Deregister symmetric memory */
|
| 265 |
+
ncclResult_t ncclCommWindowDeregister(ncclComm_t comm, ncclWindow_t win);
|
| 266 |
+
ncclResult_t pncclCommWindowDeregister(ncclComm_t comm, ncclWindow_t win);
|
| 267 |
+
|
| 268 |
+
/* Reduction operation selector */
|
| 269 |
+
typedef enum { ncclNumOps_dummy = 5 } ncclRedOp_dummy_t;
|
| 270 |
+
typedef enum { ncclSum = 0,
|
| 271 |
+
ncclProd = 1,
|
| 272 |
+
ncclMax = 2,
|
| 273 |
+
ncclMin = 3,
|
| 274 |
+
ncclAvg = 4,
|
| 275 |
+
/* ncclNumOps: The number of built-in ncclRedOp_t values. Also
|
| 276 |
+
* serves as the least possible value for dynamic ncclRedOp_t's
|
| 277 |
+
* as constructed by ncclRedOpCreate*** functions. */
|
| 278 |
+
ncclNumOps = 5,
|
| 279 |
+
/* ncclMaxRedOp: The largest valid value for ncclRedOp_t.
|
| 280 |
+
* It is defined to be the largest signed value (since compilers
|
| 281 |
+
* are permitted to use signed enums) that won't grow
|
| 282 |
+
* sizeof(ncclRedOp_t) when compared to previous NCCL versions to
|
| 283 |
+
* maintain ABI compatibility. */
|
| 284 |
+
ncclMaxRedOp = 0x7fffffff>>(32-8*sizeof(ncclRedOp_dummy_t))
|
| 285 |
+
} ncclRedOp_t;
|
| 286 |
+
|
| 287 |
+
/* Data types */
|
| 288 |
+
typedef enum { ncclInt8 = 0, ncclChar = 0,
|
| 289 |
+
ncclUint8 = 1,
|
| 290 |
+
ncclInt32 = 2, ncclInt = 2,
|
| 291 |
+
ncclUint32 = 3,
|
| 292 |
+
ncclInt64 = 4,
|
| 293 |
+
ncclUint64 = 5,
|
| 294 |
+
ncclFloat16 = 6, ncclHalf = 6,
|
| 295 |
+
ncclFloat32 = 7, ncclFloat = 7,
|
| 296 |
+
ncclFloat64 = 8, ncclDouble = 8,
|
| 297 |
+
ncclBfloat16 = 9,
|
| 298 |
+
ncclFloat8e4m3 = 10,
|
| 299 |
+
ncclFloat8e5m2 = 11,
|
| 300 |
+
ncclNumTypes = 12
|
| 301 |
+
} ncclDataType_t;
|
| 302 |
+
|
| 303 |
+
/* ncclScalarResidence_t: Location and dereferencing logic for scalar arguments. */
|
| 304 |
+
typedef enum {
|
| 305 |
+
/* ncclScalarDevice: The scalar is in device-visible memory and will be
|
| 306 |
+
* dereferenced while the collective is running. */
|
| 307 |
+
ncclScalarDevice = 0,
|
| 308 |
+
|
| 309 |
+
/* ncclScalarHostImmediate: The scalar is in host-visible memory and will be
|
| 310 |
+
* dereferenced before the ncclRedOpCreate***() function returns. */
|
| 311 |
+
ncclScalarHostImmediate = 1
|
| 312 |
+
} ncclScalarResidence_t;
|
| 313 |
+
|
| 314 |
+
/*
|
| 315 |
+
* ncclRedOpCreatePreMulSum
|
| 316 |
+
*
|
| 317 |
+
* Creates a new reduction operator which pre-multiplies input values by a given
|
| 318 |
+
* scalar locally before reducing them with peer values via summation. For use
|
| 319 |
+
* only with collectives launched against *comm* and *datatype*. The
|
| 320 |
+
* *residence* argument indicates how/when the memory pointed to by *scalar*
|
| 321 |
+
* will be dereferenced. Upon return, the newly created operator's handle
|
| 322 |
+
* is stored in *op*.
|
| 323 |
+
*/
|
| 324 |
+
ncclResult_t ncclRedOpCreatePreMulSum(ncclRedOp_t *op, void *scalar, ncclDataType_t datatype, ncclScalarResidence_t residence, ncclComm_t comm);
|
| 325 |
+
ncclResult_t pncclRedOpCreatePreMulSum(ncclRedOp_t *op, void *scalar, ncclDataType_t datatype, ncclScalarResidence_t residence, ncclComm_t comm);
|
| 326 |
+
|
| 327 |
+
/*
|
| 328 |
+
* ncclRedOpDestroy
|
| 329 |
+
*
|
| 330 |
+
* Destroys the reduction operator *op*. The operator must have been created by
|
| 331 |
+
* ncclRedOpCreatePreMul with the matching communicator *comm*. An operator may be
|
| 332 |
+
* destroyed as soon as the last NCCL function which is given that operator returns.
|
| 333 |
+
*/
|
| 334 |
+
ncclResult_t ncclRedOpDestroy(ncclRedOp_t op, ncclComm_t comm);
|
| 335 |
+
ncclResult_t pncclRedOpDestroy(ncclRedOp_t op, ncclComm_t comm);
|
| 336 |
+
|
| 337 |
+
/*
|
| 338 |
+
* Collective communication operations
|
| 339 |
+
*
|
| 340 |
+
* Collective communication operations must be called separately for each
|
| 341 |
+
* communicator in a communicator clique.
|
| 342 |
+
*
|
| 343 |
+
* They return when operations have been enqueued on the CUDA stream.
|
| 344 |
+
*
|
| 345 |
+
* Since they may perform inter-CPU synchronization, each call has to be done
|
| 346 |
+
* from a different thread or process, or need to use Group Semantics (see
|
| 347 |
+
* below).
|
| 348 |
+
*/
|
| 349 |
+
|
| 350 |
+
/*
|
| 351 |
+
* Reduce
|
| 352 |
+
*
|
| 353 |
+
* Reduces data arrays of length count in sendbuff into recvbuff using op
|
| 354 |
+
* operation.
|
| 355 |
+
* recvbuff may be NULL on all calls except for root device.
|
| 356 |
+
* root is the rank (not the CUDA device) where data will reside after the
|
| 357 |
+
* operation is complete.
|
| 358 |
+
*
|
| 359 |
+
* In-place operation will happen if sendbuff == recvbuff.
|
| 360 |
+
*/
|
| 361 |
+
ncclResult_t ncclReduce(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype,
|
| 362 |
+
ncclRedOp_t op, int root, ncclComm_t comm, cudaStream_t stream);
|
| 363 |
+
ncclResult_t pncclReduce(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype,
|
| 364 |
+
ncclRedOp_t op, int root, ncclComm_t comm, cudaStream_t stream);
|
| 365 |
+
|
| 366 |
+
/*
|
| 367 |
+
* (deprecated) Broadcast (in-place)
|
| 368 |
+
*
|
| 369 |
+
* Copies count values from root to all other devices.
|
| 370 |
+
* root is the rank (not the CUDA device) where data resides before the
|
| 371 |
+
* operation is started.
|
| 372 |
+
*
|
| 373 |
+
* This operation is implicitely in place.
|
| 374 |
+
*/
|
| 375 |
+
ncclResult_t ncclBcast(void* buff, size_t count, ncclDataType_t datatype, int root,
|
| 376 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 377 |
+
ncclResult_t pncclBcast(void* buff, size_t count, ncclDataType_t datatype, int root,
|
| 378 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 379 |
+
|
| 380 |
+
/*
|
| 381 |
+
* Broadcast
|
| 382 |
+
*
|
| 383 |
+
* Copies count values from root to all other devices.
|
| 384 |
+
* root is the rank (not the CUDA device) where data resides before the
|
| 385 |
+
* operation is started.
|
| 386 |
+
*
|
| 387 |
+
* In-place operation will happen if sendbuff == recvbuff.
|
| 388 |
+
*/
|
| 389 |
+
ncclResult_t ncclBroadcast(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, int root,
|
| 390 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 391 |
+
ncclResult_t pncclBroadcast(const void* sendbuff, void* recvbuff, size_t count, ncclDataType_t datatype, int root,
|
| 392 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 393 |
+
|
| 394 |
+
/*
|
| 395 |
+
* All-Reduce
|
| 396 |
+
*
|
| 397 |
+
* Reduces data arrays of length count in sendbuff using op operation, and
|
| 398 |
+
* leaves identical copies of result on each recvbuff.
|
| 399 |
+
*
|
| 400 |
+
* In-place operation will happen if sendbuff == recvbuff.
|
| 401 |
+
*/
|
| 402 |
+
ncclResult_t ncclAllReduce(const void* sendbuff, void* recvbuff, size_t count,
|
| 403 |
+
ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm, cudaStream_t stream);
|
| 404 |
+
ncclResult_t pncclAllReduce(const void* sendbuff, void* recvbuff, size_t count,
|
| 405 |
+
ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm, cudaStream_t stream);
|
| 406 |
+
|
| 407 |
+
/*
|
| 408 |
+
* Reduce-Scatter
|
| 409 |
+
*
|
| 410 |
+
* Reduces data in sendbuff using op operation and leaves reduced result
|
| 411 |
+
* scattered over the devices so that recvbuff on rank i will contain the i-th
|
| 412 |
+
* block of the result.
|
| 413 |
+
* Assumes sendcount is equal to nranks*recvcount, which means that sendbuff
|
| 414 |
+
* should have a size of at least nranks*recvcount elements.
|
| 415 |
+
*
|
| 416 |
+
* In-place operations will happen if recvbuff == sendbuff + rank * recvcount.
|
| 417 |
+
*/
|
| 418 |
+
ncclResult_t ncclReduceScatter(const void* sendbuff, void* recvbuff,
|
| 419 |
+
size_t recvcount, ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm,
|
| 420 |
+
cudaStream_t stream);
|
| 421 |
+
ncclResult_t pncclReduceScatter(const void* sendbuff, void* recvbuff,
|
| 422 |
+
size_t recvcount, ncclDataType_t datatype, ncclRedOp_t op, ncclComm_t comm,
|
| 423 |
+
cudaStream_t stream);
|
| 424 |
+
|
| 425 |
+
/*
|
| 426 |
+
* All-Gather
|
| 427 |
+
*
|
| 428 |
+
* Each device gathers sendcount values from other GPUs into recvbuff,
|
| 429 |
+
* receiving data from rank i at offset i*sendcount.
|
| 430 |
+
* Assumes recvcount is equal to nranks*sendcount, which means that recvbuff
|
| 431 |
+
* should have a size of at least nranks*sendcount elements.
|
| 432 |
+
*
|
| 433 |
+
* In-place operations will happen if sendbuff == recvbuff + rank * sendcount.
|
| 434 |
+
*/
|
| 435 |
+
ncclResult_t ncclAllGather(const void* sendbuff, void* recvbuff, size_t sendcount,
|
| 436 |
+
ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream);
|
| 437 |
+
ncclResult_t pncclAllGather(const void* sendbuff, void* recvbuff, size_t sendcount,
|
| 438 |
+
ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream);
|
| 439 |
+
|
| 440 |
+
/*
|
| 441 |
+
* All-to-All
|
| 442 |
+
*
|
| 443 |
+
* Each device sends count values to all other devices and receives count values
|
| 444 |
+
* from all other devices. Data to send to destination rank j is taken from
|
| 445 |
+
* sendbuff+j*count and data received from source rank i is placed at
|
| 446 |
+
* recvbuff+i*count.
|
| 447 |
+
*/
|
| 448 |
+
ncclResult_t ncclAlltoAll(const void* sendbuff, void* recvbuff, size_t count,
|
| 449 |
+
ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream);
|
| 450 |
+
ncclResult_t pncclAlltoAll(const void* sendbuff, void* recvbuff, size_t count,
|
| 451 |
+
ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream);
|
| 452 |
+
|
| 453 |
+
/*
|
| 454 |
+
* Gather
|
| 455 |
+
*
|
| 456 |
+
* Each rank sends count elements from sendbuff to the root rank.
|
| 457 |
+
* On the root rank, data from rank i is placed at recvbuff + i*count.
|
| 458 |
+
* On non-root ranks, recvbuff is not used.
|
| 459 |
+
* root is the rank where data will be gathered.
|
| 460 |
+
*
|
| 461 |
+
* In-place operations will happen if sendbuff == recvbuff + root * count.
|
| 462 |
+
*/
|
| 463 |
+
ncclResult_t ncclGather(const void* sendbuff, void* recvbuff, size_t count,
|
| 464 |
+
ncclDataType_t datatype, int root, ncclComm_t comm, cudaStream_t stream);
|
| 465 |
+
ncclResult_t pncclGather(const void* sendbuff, void* recvbuff, size_t count,
|
| 466 |
+
ncclDataType_t datatype, int root, ncclComm_t comm, cudaStream_t stream);
|
| 467 |
+
|
| 468 |
+
/*
|
| 469 |
+
* Scatter
|
| 470 |
+
*
|
| 471 |
+
* On the root rank, count elements from sendbuff+i*count are sent to rank i.
|
| 472 |
+
* On non-root ranks, sendbuff is not used.
|
| 473 |
+
* Each rank receives count elements into recvbuff.
|
| 474 |
+
* root is the rank that will distribute the data.
|
| 475 |
+
*
|
| 476 |
+
* In-place operations will happen if recvbuff == sendbuff + root * count.
|
| 477 |
+
*/
|
| 478 |
+
ncclResult_t ncclScatter(const void* sendbuff, void* recvbuff, size_t count,
|
| 479 |
+
ncclDataType_t datatype, int root, ncclComm_t comm, cudaStream_t stream);
|
| 480 |
+
ncclResult_t pncclScatter(const void* sendbuff, void* recvbuff, size_t count,
|
| 481 |
+
ncclDataType_t datatype, int root, ncclComm_t comm, cudaStream_t stream);
|
| 482 |
+
|
| 483 |
+
/*
|
| 484 |
+
* Send
|
| 485 |
+
*
|
| 486 |
+
* Send data from sendbuff to rank peer.
|
| 487 |
+
*
|
| 488 |
+
* Rank peer needs to call ncclRecv with the same datatype and the same count from this
|
| 489 |
+
* rank.
|
| 490 |
+
*
|
| 491 |
+
* This operation is blocking for the GPU. If multiple ncclSend and ncclRecv operations
|
| 492 |
+
* need to progress concurrently to complete, they must be fused within a ncclGroupStart/
|
| 493 |
+
* ncclGroupEnd section.
|
| 494 |
+
*/
|
| 495 |
+
ncclResult_t ncclSend(const void* sendbuff, size_t count, ncclDataType_t datatype, int peer,
|
| 496 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 497 |
+
ncclResult_t pncclSend(const void* sendbuff, size_t count, ncclDataType_t datatype, int peer,
|
| 498 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 499 |
+
|
| 500 |
+
/*
|
| 501 |
+
* Receive
|
| 502 |
+
*
|
| 503 |
+
* Receive data from rank peer into recvbuff.
|
| 504 |
+
*
|
| 505 |
+
* Rank peer needs to call ncclSend with the same datatype and the same count to this
|
| 506 |
+
* rank.
|
| 507 |
+
*
|
| 508 |
+
* This operation is blocking for the GPU. If multiple ncclSend and ncclRecv operations
|
| 509 |
+
* need to progress concurrently to complete, they must be fused within a ncclGroupStart/
|
| 510 |
+
* ncclGroupEnd section.
|
| 511 |
+
*/
|
| 512 |
+
ncclResult_t pncclRecv(void* recvbuff, size_t count, ncclDataType_t datatype, int peer,
|
| 513 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 514 |
+
ncclResult_t ncclRecv(void* recvbuff, size_t count, ncclDataType_t datatype, int peer,
|
| 515 |
+
ncclComm_t comm, cudaStream_t stream);
|
| 516 |
+
|
| 517 |
+
/*
|
| 518 |
+
* Group semantics
|
| 519 |
+
*
|
| 520 |
+
* When managing multiple GPUs from a single thread, and since NCCL collective
|
| 521 |
+
* calls may perform inter-CPU synchronization, we need to "group" calls for
|
| 522 |
+
* different ranks/devices into a single call.
|
| 523 |
+
*
|
| 524 |
+
* Grouping NCCL calls as being part of the same collective operation is done
|
| 525 |
+
* using ncclGroupStart and ncclGroupEnd. ncclGroupStart will enqueue all
|
| 526 |
+
* collective calls until the ncclGroupEnd call, which will wait for all calls
|
| 527 |
+
* to be complete. Note that for collective communication, ncclGroupEnd only
|
| 528 |
+
* guarantees that the operations are enqueued on the streams, not that
|
| 529 |
+
* the operation is effectively done.
|
| 530 |
+
*
|
| 531 |
+
* Both collective communication and ncclCommInitRank can be used in conjunction
|
| 532 |
+
* of ncclGroupStart/ncclGroupEnd, but not together.
|
| 533 |
+
*
|
| 534 |
+
* Group semantics also allow to fuse multiple operations on the same device
|
| 535 |
+
* to improve performance (for aggregated collective calls), or to permit
|
| 536 |
+
* concurrent progress of multiple send/receive operations.
|
| 537 |
+
*/
|
| 538 |
+
|
| 539 |
+
/*
|
| 540 |
+
* Group Start
|
| 541 |
+
*
|
| 542 |
+
* Start a group call. All calls to NCCL until ncclGroupEnd will be fused into
|
| 543 |
+
* a single NCCL operation. Nothing will be started on the CUDA stream until
|
| 544 |
+
* ncclGroupEnd.
|
| 545 |
+
*/
|
| 546 |
+
ncclResult_t ncclGroupStart();
|
| 547 |
+
ncclResult_t pncclGroupStart();
|
| 548 |
+
|
| 549 |
+
/*
|
| 550 |
+
* Group End
|
| 551 |
+
*
|
| 552 |
+
* End a group call. Start a fused NCCL operation consisting of all calls since
|
| 553 |
+
* ncclGroupStart. Operations on the CUDA stream depending on the NCCL operations
|
| 554 |
+
* need to be called after ncclGroupEnd.
|
| 555 |
+
*/
|
| 556 |
+
ncclResult_t ncclGroupEnd();
|
| 557 |
+
ncclResult_t pncclGroupEnd();
|
| 558 |
+
|
| 559 |
+
/*
|
| 560 |
+
* Group Simulate End
|
| 561 |
+
*
|
| 562 |
+
* Simulate a ncclGroupEnd() call and return NCCL's simulation info in a struct.
|
| 563 |
+
*/
|
| 564 |
+
ncclResult_t ncclGroupSimulateEnd(ncclSimInfo_t* simInfo);
|
| 565 |
+
ncclResult_t pncclGroupSimulateEnd(ncclSimInfo_t* simInfo);
|
| 566 |
+
|
| 567 |
+
#ifdef __cplusplus
|
| 568 |
+
} // end extern "C"
|
| 569 |
+
#endif
|
| 570 |
+
|
| 571 |
+
#endif // end include guard
|
zonos2/kernel/csrc/include/zonos2/tensor.h
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
#include <zonos2/utils.h>
|
| 3 |
+
|
| 4 |
+
#include <dlpack/dlpack.h>
|
| 5 |
+
#include <tvm/ffi/container/tensor.h>
|
| 6 |
+
#include <tvm/ffi/dtype.h>
|
| 7 |
+
|
| 8 |
+
#include <algorithm>
|
| 9 |
+
#include <array>
|
| 10 |
+
#include <concepts>
|
| 11 |
+
#include <cstddef>
|
| 12 |
+
#include <cstdint>
|
| 13 |
+
#include <initializer_list>
|
| 14 |
+
#include <optional>
|
| 15 |
+
#include <ranges>
|
| 16 |
+
#include <source_location>
|
| 17 |
+
#include <span>
|
| 18 |
+
#include <sstream>
|
| 19 |
+
#include <string>
|
| 20 |
+
#include <string_view>
|
| 21 |
+
#include <type_traits>
|
| 22 |
+
#include <utility>
|
| 23 |
+
|
| 24 |
+
namespace host {
|
| 25 |
+
|
| 26 |
+
namespace stdr = std::ranges;
|
| 27 |
+
namespace stdv = std::views;
|
| 28 |
+
|
| 29 |
+
namespace details {
|
| 30 |
+
|
| 31 |
+
struct SizeRef;
|
| 32 |
+
struct DTypeRef;
|
| 33 |
+
struct DeviceRef;
|
| 34 |
+
|
| 35 |
+
inline constexpr auto kAnyDeviceID = -1;
|
| 36 |
+
inline constexpr auto kAnySize = static_cast<int64_t>(-1);
|
| 37 |
+
inline constexpr auto kNullSize = static_cast<int64_t>(0);
|
| 38 |
+
inline constexpr auto kNullDType = static_cast<DLDataTypeCode>(18u);
|
| 39 |
+
inline constexpr auto kNullDevice = static_cast<DLDeviceType>(-1);
|
| 40 |
+
|
| 41 |
+
template <typename T> struct dtype_trait {};
|
| 42 |
+
|
| 43 |
+
template <std::integral T> struct dtype_trait<T> {
|
| 44 |
+
inline static constexpr auto value =
|
| 45 |
+
DLDataType{.code = std::is_signed_v<T> ? DLDataTypeCode::kDLInt
|
| 46 |
+
: DLDataTypeCode::kDLUInt,
|
| 47 |
+
.bits = static_cast<std::uint8_t>(sizeof(T) * 8),
|
| 48 |
+
.lanes = 1};
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
template <std::floating_point T> struct dtype_trait<T> {
|
| 52 |
+
inline static constexpr auto value =
|
| 53 |
+
DLDataType{.code = DLDataTypeCode::kDLFloat,
|
| 54 |
+
.bits = static_cast<std::uint8_t>(sizeof(T) * 8),
|
| 55 |
+
.lanes = 1};
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
template <DLDeviceType Code> struct device_trait {
|
| 59 |
+
inline static constexpr auto value =
|
| 60 |
+
DLDevice{.device_type = Code, .device_id = kAnyDeviceID};
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
template <typename... Ts>
|
| 64 |
+
inline constexpr auto kDTypeList =
|
| 65 |
+
std::array<DLDataType, sizeof...(Ts)>{dtype_trait<Ts>::value...};
|
| 66 |
+
|
| 67 |
+
template <DLDeviceType... Codes>
|
| 68 |
+
inline constexpr auto kDeviceList =
|
| 69 |
+
std::array<DLDevice, sizeof...(Codes)>{device_trait<Codes>::value...};
|
| 70 |
+
|
| 71 |
+
template <typename T> struct PrintAbleSpan {
|
| 72 |
+
explicit PrintAbleSpan(std::span<const T> data) : data(data) {}
|
| 73 |
+
std::span<const T> data;
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
// define DLDataType comparison and printing in root namespace
|
| 77 |
+
inline constexpr auto kDeviceStringMap = [] {
|
| 78 |
+
constexpr auto map =
|
| 79 |
+
std::array<std::pair<DLDeviceType, std::string_view>, 16>{
|
| 80 |
+
std::pair{DLDeviceType::kDLCPU, "cpu"},
|
| 81 |
+
std::pair{DLDeviceType::kDLCUDA, "cuda"},
|
| 82 |
+
std::pair{DLDeviceType::kDLCUDAHost, "cuda_host"},
|
| 83 |
+
std::pair{DLDeviceType::kDLOpenCL, "opencl"},
|
| 84 |
+
std::pair{DLDeviceType::kDLVulkan, "vulkan"},
|
| 85 |
+
std::pair{DLDeviceType::kDLMetal, "metal"},
|
| 86 |
+
std::pair{DLDeviceType::kDLVPI, "vpi"},
|
| 87 |
+
std::pair{DLDeviceType::kDLROCM, "rocm"},
|
| 88 |
+
std::pair{DLDeviceType::kDLROCMHost, "rocm_host"},
|
| 89 |
+
std::pair{DLDeviceType::kDLExtDev, "ext_dev"},
|
| 90 |
+
std::pair{DLDeviceType::kDLCUDAManaged, "cuda_managed"},
|
| 91 |
+
std::pair{DLDeviceType::kDLOneAPI, "oneapi"},
|
| 92 |
+
std::pair{DLDeviceType::kDLWebGPU, "webgpu"},
|
| 93 |
+
std::pair{DLDeviceType::kDLHexagon, "hexagon"},
|
| 94 |
+
std::pair{DLDeviceType::kDLMAIA, "maia"},
|
| 95 |
+
std::pair{DLDeviceType::kDLTrn, "trn"},
|
| 96 |
+
};
|
| 97 |
+
constexpr auto max_type = stdr::max(map | stdv::keys);
|
| 98 |
+
auto result = std::array<std::string_view, max_type + 1>{};
|
| 99 |
+
for (const auto &[code, name] : map) {
|
| 100 |
+
result[static_cast<std::size_t>(code)] = name;
|
| 101 |
+
}
|
| 102 |
+
return result;
|
| 103 |
+
}();
|
| 104 |
+
|
| 105 |
+
struct PrintableDevice {
|
| 106 |
+
DLDevice device;
|
| 107 |
+
};
|
| 108 |
+
|
| 109 |
+
inline auto &operator<<(std::ostream &os, DLDevice device) {
|
| 110 |
+
const auto &mapping = kDeviceStringMap;
|
| 111 |
+
const auto entry = static_cast<std::size_t>(device.device_type);
|
| 112 |
+
RuntimeCheck(entry < mapping.size());
|
| 113 |
+
const auto name = mapping[entry];
|
| 114 |
+
RuntimeCheck(!name.empty(), "Unknown device: ", int(device.device_type));
|
| 115 |
+
os << name;
|
| 116 |
+
if (device.device_id != kAnyDeviceID)
|
| 117 |
+
os << "[" << device.device_id << "]";
|
| 118 |
+
return os;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
inline auto &operator<<(std::ostream &os, PrintableDevice pd) {
|
| 122 |
+
return os << pd.device;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
template <typename T>
|
| 126 |
+
inline auto &operator<<(std::ostream &os, PrintAbleSpan<T> span) {
|
| 127 |
+
os << "[";
|
| 128 |
+
for (const auto i : stdv::iota(std::size_t{0}, span.data.size())) {
|
| 129 |
+
if (i > 0) {
|
| 130 |
+
os << ", ";
|
| 131 |
+
}
|
| 132 |
+
os << span.data[i];
|
| 133 |
+
}
|
| 134 |
+
os << "]";
|
| 135 |
+
return os;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
} // namespace details
|
| 139 |
+
|
| 140 |
+
struct SymbolicSize {
|
| 141 |
+
public:
|
| 142 |
+
SymbolicSize(std::string_view annotation = {})
|
| 143 |
+
: m_value(details::kNullSize), m_annotation(annotation) {}
|
| 144 |
+
|
| 145 |
+
auto get_name() const -> std::string_view { return m_annotation; }
|
| 146 |
+
auto set_value(int64_t value) -> void {
|
| 147 |
+
RuntimeCheck(!this->has_value(), "Size value already set");
|
| 148 |
+
m_value = value;
|
| 149 |
+
}
|
| 150 |
+
auto has_value() const -> bool { return m_value != 0; }
|
| 151 |
+
auto get_value() const -> std::optional<int64_t> {
|
| 152 |
+
return this->has_value() ? std::optional{m_value} : std::nullopt;
|
| 153 |
+
}
|
| 154 |
+
auto unwrap() const -> int64_t {
|
| 155 |
+
RuntimeCheck(this->has_value(), "Size value is not set");
|
| 156 |
+
return m_value;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
SymbolicSize(const SymbolicSize &) = delete;
|
| 160 |
+
SymbolicSize &operator=(const SymbolicSize &) = delete;
|
| 161 |
+
|
| 162 |
+
auto verify(int64_t size, const char *prefix, int64_t dim) -> void {
|
| 163 |
+
if (this->has_value()) {
|
| 164 |
+
if (m_value != size) {
|
| 165 |
+
[[unlikely]];
|
| 166 |
+
Panic("Size mismatch for ", m_name(prefix, dim), ": expected ", m_value,
|
| 167 |
+
" but got ", size);
|
| 168 |
+
}
|
| 169 |
+
} else {
|
| 170 |
+
this->set_value(size);
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
auto value_or_name(const char *prefix, int64_t dim) const -> std::string {
|
| 175 |
+
if (const auto value = this->get_value()) {
|
| 176 |
+
return std::to_string(*value);
|
| 177 |
+
} else {
|
| 178 |
+
return m_name(prefix, dim);
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
private:
|
| 183 |
+
auto m_name(const char *prefix, int64_t dim) const -> std::string {
|
| 184 |
+
const auto annotation = this->get_name();
|
| 185 |
+
std::ostringstream os;
|
| 186 |
+
if (annotation.empty()) {
|
| 187 |
+
os << prefix << '#' << dim;
|
| 188 |
+
} else {
|
| 189 |
+
os << annotation << '(' << prefix << '#' << dim << ')';
|
| 190 |
+
}
|
| 191 |
+
return std::move(os).str();
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
std::int64_t m_value;
|
| 195 |
+
std::string_view m_annotation;
|
| 196 |
+
};
|
| 197 |
+
|
| 198 |
+
inline auto operator==(DLDevice lhs, DLDevice rhs) -> bool {
|
| 199 |
+
return lhs.device_type == rhs.device_type && lhs.device_id == rhs.device_id;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
struct SymbolicDType {
|
| 203 |
+
public:
|
| 204 |
+
SymbolicDType() : m_value({details::kNullDType, 0, 0}) {}
|
| 205 |
+
|
| 206 |
+
auto set_value(DLDataType value) -> void {
|
| 207 |
+
RuntimeCheck(!this->has_value(), "Dtype value already set");
|
| 208 |
+
RuntimeCheck(
|
| 209 |
+
m_check(value), "Dtype value [", value,
|
| 210 |
+
"] not in the allowed options: ", details::PrintAbleSpan{m_options});
|
| 211 |
+
m_value = value;
|
| 212 |
+
}
|
| 213 |
+
auto has_value() const -> bool { return m_value.code != details::kNullDType; }
|
| 214 |
+
auto get_value() const -> std::optional<DLDataType> {
|
| 215 |
+
return this->has_value() ? std::optional{m_value} : std::nullopt;
|
| 216 |
+
}
|
| 217 |
+
auto unwrap() const -> DLDataType {
|
| 218 |
+
RuntimeCheck(this->has_value(), "Dtype value is not set");
|
| 219 |
+
return m_value;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
auto set_options(std::span<const DLDataType> options) -> void {
|
| 223 |
+
m_options = options;
|
| 224 |
+
}
|
| 225 |
+
template <typename... Ts> auto set_options() -> void {
|
| 226 |
+
m_options = details::kDTypeList<Ts...>;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
auto verify(DLDataType dtype) -> void {
|
| 230 |
+
if (this->has_value()) {
|
| 231 |
+
RuntimeCheck(m_value == dtype, "DType mismatch: expected ", m_value,
|
| 232 |
+
" but got ", dtype);
|
| 233 |
+
} else {
|
| 234 |
+
this->set_value(dtype);
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
private:
|
| 239 |
+
auto m_check(DLDataType value) const -> bool {
|
| 240 |
+
return stdr::empty(m_options) ||
|
| 241 |
+
(stdr::find(m_options, value) != stdr::end(m_options));
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
std::span<const DLDataType> m_options;
|
| 245 |
+
DLDataType m_value;
|
| 246 |
+
};
|
| 247 |
+
|
| 248 |
+
struct SymbolicDevice {
|
| 249 |
+
public:
|
| 250 |
+
SymbolicDevice() : m_value({details::kNullDevice, details::kAnyDeviceID}) {}
|
| 251 |
+
|
| 252 |
+
auto set_value(DLDevice value) -> void {
|
| 253 |
+
RuntimeCheck(!this->has_value(), "Device value already set");
|
| 254 |
+
RuntimeCheck(
|
| 255 |
+
m_check(value), "Device value [", details::PrintableDevice{value},
|
| 256 |
+
"] not in the allowed options: ", details::PrintAbleSpan{m_options});
|
| 257 |
+
m_value = value;
|
| 258 |
+
}
|
| 259 |
+
auto has_value() const -> bool {
|
| 260 |
+
return m_value.device_type != details::kNullDevice;
|
| 261 |
+
}
|
| 262 |
+
auto get_value() const -> std::optional<DLDevice> {
|
| 263 |
+
return this->has_value() ? std::optional{m_value} : std::nullopt;
|
| 264 |
+
}
|
| 265 |
+
auto unwrap() const -> DLDevice {
|
| 266 |
+
RuntimeCheck(this->has_value(), "Device value is not set");
|
| 267 |
+
return m_value;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
auto set_options(std::span<const DLDevice> options) -> void {
|
| 271 |
+
m_options = options;
|
| 272 |
+
}
|
| 273 |
+
template <DLDeviceType... Codes> auto set_options() -> void {
|
| 274 |
+
m_options = details::kDeviceList<Codes...>;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
auto verify(DLDevice device) -> void {
|
| 278 |
+
if (this->has_value()) {
|
| 279 |
+
RuntimeCheck(m_value == device, "Device mismatch: expected ",
|
| 280 |
+
details::PrintableDevice{m_value}, " but got ",
|
| 281 |
+
details::PrintableDevice{device});
|
| 282 |
+
} else {
|
| 283 |
+
this->set_value(device);
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
private:
|
| 288 |
+
auto m_check(DLDevice value) const -> bool {
|
| 289 |
+
return stdr::empty(m_options) ||
|
| 290 |
+
(stdr::any_of(m_options, [value](const DLDevice &opt) {
|
| 291 |
+
// device type must exactly match
|
| 292 |
+
if (opt.device_type != value.device_type)
|
| 293 |
+
return false;
|
| 294 |
+
// device id can be wildcarded
|
| 295 |
+
return opt.device_id == details::kAnyDeviceID ||
|
| 296 |
+
opt.device_id == value.device_id;
|
| 297 |
+
}));
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
std::span<const DLDevice> m_options;
|
| 301 |
+
DLDevice m_value;
|
| 302 |
+
};
|
| 303 |
+
|
| 304 |
+
struct TensorMatcher;
|
| 305 |
+
|
| 306 |
+
namespace details {
|
| 307 |
+
|
| 308 |
+
template <typename T> struct BaseRef {
|
| 309 |
+
public:
|
| 310 |
+
BaseRef(const BaseRef &) = delete;
|
| 311 |
+
BaseRef &operator=(const BaseRef &) = delete;
|
| 312 |
+
|
| 313 |
+
auto operator->() const -> T * { return m_ref; }
|
| 314 |
+
auto operator*() const -> T & { return *m_ref; }
|
| 315 |
+
auto rebind(T &other) -> void { m_ref = &other; }
|
| 316 |
+
|
| 317 |
+
explicit BaseRef() : m_ref(&m_cache), m_cache() {}
|
| 318 |
+
BaseRef(T &size) : m_ref(&size), m_cache() {}
|
| 319 |
+
|
| 320 |
+
private:
|
| 321 |
+
T *m_ref;
|
| 322 |
+
T m_cache;
|
| 323 |
+
};
|
| 324 |
+
|
| 325 |
+
struct SizeRef : BaseRef<SymbolicSize> {
|
| 326 |
+
using BaseRef::BaseRef;
|
| 327 |
+
SizeRef(int64_t value) {
|
| 328 |
+
if (value != kAnySize) {
|
| 329 |
+
(**this).set_value(value);
|
| 330 |
+
} else {
|
| 331 |
+
// otherwise, we can match any size
|
| 332 |
+
}
|
| 333 |
+
}
|
| 334 |
+
};
|
| 335 |
+
|
| 336 |
+
struct DTypeRef : BaseRef<SymbolicDType> {
|
| 337 |
+
using BaseRef::BaseRef;
|
| 338 |
+
DTypeRef(DLDataType options) { (**this).set_value(options); }
|
| 339 |
+
DTypeRef(std::initializer_list<DLDataType> options) {
|
| 340 |
+
(**this).set_options(options);
|
| 341 |
+
}
|
| 342 |
+
DTypeRef(std::span<const DLDataType> options) {
|
| 343 |
+
(**this).set_options(options);
|
| 344 |
+
}
|
| 345 |
+
};
|
| 346 |
+
|
| 347 |
+
struct DeviceRef : BaseRef<SymbolicDevice> {
|
| 348 |
+
using BaseRef::BaseRef;
|
| 349 |
+
DeviceRef(DLDevice options) { (**this).set_value(options); }
|
| 350 |
+
DeviceRef(std::initializer_list<DLDevice> options) {
|
| 351 |
+
(**this).set_options(options);
|
| 352 |
+
}
|
| 353 |
+
DeviceRef(std::span<const DLDevice> options) {
|
| 354 |
+
(**this).set_options(options);
|
| 355 |
+
}
|
| 356 |
+
};
|
| 357 |
+
|
| 358 |
+
} // namespace details
|
| 359 |
+
|
| 360 |
+
struct TensorMatcher {
|
| 361 |
+
private:
|
| 362 |
+
using SizeRef = details::SizeRef;
|
| 363 |
+
using DTypeRef = details::DTypeRef;
|
| 364 |
+
using DeviceRef = details::DeviceRef;
|
| 365 |
+
using Loc_t = std::source_location;
|
| 366 |
+
|
| 367 |
+
public:
|
| 368 |
+
TensorMatcher(const TensorMatcher &) = delete;
|
| 369 |
+
TensorMatcher &operator=(const TensorMatcher &) = delete;
|
| 370 |
+
|
| 371 |
+
explicit TensorMatcher(std::initializer_list<SizeRef> shape)
|
| 372 |
+
: m_shape(shape), m_strides(), m_dtype() {}
|
| 373 |
+
|
| 374 |
+
auto with_strides(std::initializer_list<SizeRef> strides) && //
|
| 375 |
+
-> TensorMatcher && {
|
| 376 |
+
// no partial update allowed
|
| 377 |
+
RuntimeCheck(m_strides.size() == 0, "Strides already specified");
|
| 378 |
+
RuntimeCheck(m_shape.size() == strides.size(),
|
| 379 |
+
"Strides size must match shape size");
|
| 380 |
+
m_strides = strides;
|
| 381 |
+
return std::move(*this);
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
template <typename... Ts>
|
| 385 |
+
auto with_dtype(DTypeRef &&dtype) && -> TensorMatcher && {
|
| 386 |
+
m_init_dtype();
|
| 387 |
+
m_dtype.rebind(*dtype);
|
| 388 |
+
return std::move(*this);
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
template <typename... Ts>
|
| 392 |
+
requires(sizeof...(Ts) > 0)
|
| 393 |
+
auto with_dtype() && -> TensorMatcher && {
|
| 394 |
+
m_init_dtype();
|
| 395 |
+
m_dtype->set_options<Ts...>();
|
| 396 |
+
return std::move(*this);
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
template <DLDeviceType... Codes>
|
| 400 |
+
auto with_device(DeviceRef &&device) && -> TensorMatcher && {
|
| 401 |
+
m_init_device();
|
| 402 |
+
m_device.rebind(*device);
|
| 403 |
+
return std::move(*this);
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
template <DLDeviceType... Codes>
|
| 407 |
+
requires(sizeof...(Codes) > 0)
|
| 408 |
+
auto with_device() && -> TensorMatcher && {
|
| 409 |
+
m_init_device();
|
| 410 |
+
m_device->set_options<Codes...>();
|
| 411 |
+
return std::move(*this);
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
// once we start verification, we cannot modify anymore
|
| 415 |
+
auto verify(tvm::ffi::TensorView view,
|
| 416 |
+
Loc_t loc = Loc_t::current()) const && -> const TensorMatcher && {
|
| 417 |
+
try {
|
| 418 |
+
this->m_verify_impl(view);
|
| 419 |
+
} catch (PanicError &e) {
|
| 420 |
+
auto oss = std::ostringstream{};
|
| 421 |
+
oss << "Tensor match failed for " << this->debug_str() << " at "
|
| 422 |
+
<< loc.file_name() << ":" << loc.line()
|
| 423 |
+
<< "\n- Root cause: " << e.detail();
|
| 424 |
+
throw PanicError(std::move(oss).str());
|
| 425 |
+
}
|
| 426 |
+
return std::move(*this);
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
auto debug_str() const -> std::string {
|
| 430 |
+
auto oss = std::ostringstream{};
|
| 431 |
+
oss << "Tensor<";
|
| 432 |
+
std::size_t dim = 0;
|
| 433 |
+
for (const auto &size_ref : m_shape) {
|
| 434 |
+
if (dim > 0) {
|
| 435 |
+
oss << ", ";
|
| 436 |
+
}
|
| 437 |
+
oss << size_ref->value_or_name("shape", dim++);
|
| 438 |
+
}
|
| 439 |
+
oss << ">";
|
| 440 |
+
if (m_strides.size() > 0) {
|
| 441 |
+
oss << " [strides=<";
|
| 442 |
+
dim = 0;
|
| 443 |
+
for (const auto &stride_ref : m_strides) {
|
| 444 |
+
if (dim > 0) {
|
| 445 |
+
oss << ", ";
|
| 446 |
+
}
|
| 447 |
+
oss << stride_ref->value_or_name("stride", dim++);
|
| 448 |
+
}
|
| 449 |
+
oss << ">]";
|
| 450 |
+
}
|
| 451 |
+
return std::move(oss).str();
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
private:
|
| 455 |
+
auto m_verify_impl(tvm::ffi::TensorView view) const -> void {
|
| 456 |
+
const auto dim = static_cast<std::size_t>(view.dim());
|
| 457 |
+
RuntimeCheck(dim == m_shape.size(), "Tensor dimension mismatch: expected ",
|
| 458 |
+
m_shape.size(), " but got ", dim);
|
| 459 |
+
for (const auto i : stdv::iota(std::size_t{0}, dim)) {
|
| 460 |
+
m_shape[i]->verify(view.size(i), "shape", i);
|
| 461 |
+
}
|
| 462 |
+
if (this->m_has_strides()) {
|
| 463 |
+
for (const auto i : stdv::iota(std::size_t{0}, dim)) {
|
| 464 |
+
if (view.size(i) != 1 || !m_strides[i]->has_value()) {
|
| 465 |
+
// skip stride check for size 1 dimension
|
| 466 |
+
m_strides[i]->verify(view.stride(i), "stride", i);
|
| 467 |
+
}
|
| 468 |
+
}
|
| 469 |
+
} else {
|
| 470 |
+
RuntimeCheck(view.is_contiguous(),
|
| 471 |
+
"Tensor is not contiguous as expected");
|
| 472 |
+
}
|
| 473 |
+
// since we may double verify, we will force to check
|
| 474 |
+
m_dtype->verify(view.dtype());
|
| 475 |
+
m_device->verify(view.device());
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
auto m_init_dtype() -> void {
|
| 479 |
+
RuntimeCheck(!m_has_dtype, "DType already specified");
|
| 480 |
+
m_has_dtype = true;
|
| 481 |
+
}
|
| 482 |
+
auto m_init_device() -> void {
|
| 483 |
+
RuntimeCheck(!m_has_device, "Device already specified");
|
| 484 |
+
m_has_device = true;
|
| 485 |
+
}
|
| 486 |
+
auto m_has_strides() const -> bool { return !m_strides.empty(); }
|
| 487 |
+
|
| 488 |
+
std::span<const SizeRef> m_shape;
|
| 489 |
+
std::span<const SizeRef> m_strides;
|
| 490 |
+
DTypeRef m_dtype;
|
| 491 |
+
DeviceRef m_device;
|
| 492 |
+
bool m_has_dtype = false;
|
| 493 |
+
bool m_has_device = false;
|
| 494 |
+
};
|
| 495 |
+
|
| 496 |
+
} // namespace host
|
zonos2/kernel/csrc/include/zonos2/utils.cuh
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
#include <zonos2/utils.h>
|
| 4 |
+
|
| 5 |
+
#include <dlpack/dlpack.h>
|
| 6 |
+
#include <tvm/ffi/extra/c_env_api.h>
|
| 7 |
+
|
| 8 |
+
#include <concepts>
|
| 9 |
+
#include <cstddef>
|
| 10 |
+
#include <source_location>
|
| 11 |
+
#include <type_traits>
|
| 12 |
+
|
| 13 |
+
namespace device {
|
| 14 |
+
|
| 15 |
+
inline constexpr auto kWarpThreads = 32u;
|
| 16 |
+
|
| 17 |
+
template <std::integral T, std::integral U>
|
| 18 |
+
__always_inline __device__ constexpr auto div_ceil(T a, U b) {
|
| 19 |
+
return (a + b - 1) / b;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
namespace pointer {
|
| 23 |
+
|
| 24 |
+
// we only allow void * pointer arithmetic for safety
|
| 25 |
+
|
| 26 |
+
template <typename T, std::integral... U>
|
| 27 |
+
__always_inline __device__ auto offset(T *ptr, U... offset) -> void * {
|
| 28 |
+
static_assert(std::is_same_v<T, void>,
|
| 29 |
+
"Pointer arithmetic is only allowed for void* pointers");
|
| 30 |
+
return static_cast<char *>(ptr) + (... + offset);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
template <typename T, std::integral... U>
|
| 34 |
+
__always_inline __device__ auto offset(const T *ptr, U... offset) -> const
|
| 35 |
+
void * {
|
| 36 |
+
static_assert(std::is_same_v<T, void>,
|
| 37 |
+
"Pointer arithmetic is only allowed for void* pointers");
|
| 38 |
+
return static_cast<const char *>(ptr) + (... + offset);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
} // namespace pointer
|
| 42 |
+
|
| 43 |
+
namespace PDL {
|
| 44 |
+
|
| 45 |
+
template <bool kUsePDL> __always_inline __device__ void wait() {
|
| 46 |
+
if constexpr (kUsePDL) {
|
| 47 |
+
asm volatile("griddepcontrol.wait;" ::: "memory");
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
template <bool kUsePDL> __always_inline __device__ void launch() {
|
| 52 |
+
if constexpr (kUsePDL) {
|
| 53 |
+
asm volatile("griddepcontrol.launch_dependents;" :::);
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
} // namespace PDL
|
| 58 |
+
|
| 59 |
+
} // namespace device
|
| 60 |
+
|
| 61 |
+
namespace host {
|
| 62 |
+
|
| 63 |
+
inline auto
|
| 64 |
+
CUDA_CHECK(::cudaError_t error,
|
| 65 |
+
std::source_location location = std::source_location::current())
|
| 66 |
+
-> void {
|
| 67 |
+
if (error != ::cudaSuccess) {
|
| 68 |
+
[[unlikely]];
|
| 69 |
+
::host::panic(location, "CUDA error: ", ::cudaGetErrorString(error));
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
inline auto
|
| 74 |
+
CUDA_CHECK(std::source_location location = std::source_location::current())
|
| 75 |
+
-> void {
|
| 76 |
+
return CUDA_CHECK(::cudaGetLastError(), location);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
template <auto F> inline void set_smem_once(std::size_t smem_size) {
|
| 80 |
+
static const auto last_smem_size = [&] {
|
| 81 |
+
CUDA_CHECK(::cudaFuncSetAttribute(
|
| 82 |
+
F, ::cudaFuncAttributeMaxDynamicSharedMemorySize, smem_size));
|
| 83 |
+
return smem_size;
|
| 84 |
+
}();
|
| 85 |
+
RuntimeCheck(
|
| 86 |
+
smem_size <= last_smem_size,
|
| 87 |
+
"Dynamic shared memory size exceeds the previously set maximum size: ",
|
| 88 |
+
last_smem_size, " bytes");
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
struct LaunchKernel {
|
| 92 |
+
public:
|
| 93 |
+
explicit LaunchKernel(dim3 grid_dim, dim3 block_dim, DLDevice device,
|
| 94 |
+
std::size_t dynamic_shared_mem_bytes = 0) noexcept
|
| 95 |
+
: m_config(s_make_config(grid_dim, block_dim, resolve_device(device),
|
| 96 |
+
dynamic_shared_mem_bytes)) {}
|
| 97 |
+
|
| 98 |
+
explicit LaunchKernel(dim3 grid_dim, dim3 block_dim, cudaStream_t stream,
|
| 99 |
+
std::size_t dynamic_shared_mem_bytes = 0) noexcept
|
| 100 |
+
: m_config(s_make_config(grid_dim, block_dim, stream,
|
| 101 |
+
dynamic_shared_mem_bytes)) {}
|
| 102 |
+
|
| 103 |
+
static auto resolve_device(DLDevice device) -> cudaStream_t {
|
| 104 |
+
return static_cast<cudaStream_t>(
|
| 105 |
+
::TVMFFIEnvGetStream(device.device_type, device.device_id));
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
LaunchKernel(const LaunchKernel &) = delete;
|
| 109 |
+
LaunchKernel &operator=(const LaunchKernel &) = delete;
|
| 110 |
+
|
| 111 |
+
template <typename T, typename... Args>
|
| 112 |
+
auto operator()(T &&kernel, Args &&...args) const -> void {
|
| 113 |
+
CUDA_CHECK(
|
| 114 |
+
::cudaLaunchKernelEx(&m_config, kernel, std::forward<Args>(args)...));
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
auto with_attr(bool use_pdl) -> LaunchKernel & {
|
| 118 |
+
if (use_pdl) {
|
| 119 |
+
m_attr_cache.id = ::cudaLaunchAttributeProgrammaticStreamSerialization;
|
| 120 |
+
m_attr_cache.val.programmaticStreamSerializationAllowed = 1;
|
| 121 |
+
m_config.attrs = &m_attr_cache;
|
| 122 |
+
m_config.numAttrs = 1;
|
| 123 |
+
} else {
|
| 124 |
+
m_config.numAttrs = 0;
|
| 125 |
+
}
|
| 126 |
+
return *this;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
private:
|
| 130 |
+
static auto s_make_config(dim3 grid_dim, dim3 block_dim, cudaStream_t stream,
|
| 131 |
+
std::size_t smem) -> cudaLaunchConfig_t {
|
| 132 |
+
auto config = ::cudaLaunchConfig_t{};
|
| 133 |
+
config.gridDim = grid_dim;
|
| 134 |
+
config.blockDim = block_dim;
|
| 135 |
+
config.dynamicSmemBytes = smem;
|
| 136 |
+
config.stream = stream;
|
| 137 |
+
config.numAttrs = 0;
|
| 138 |
+
return config;
|
| 139 |
+
}
|
| 140 |
+
cudaLaunchConfig_t m_config;
|
| 141 |
+
cudaLaunchAttribute m_attr_cache;
|
| 142 |
+
};
|
| 143 |
+
|
| 144 |
+
} // namespace host
|
zonos2/kernel/csrc/include/zonos2/utils.h
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
// ref:
|
| 4 |
+
// https://forums.developer.nvidia.com/t/c-20s-source-location-compilation-error-when-using-nvcc-12-1/258026/3
|
| 5 |
+
#ifdef __CUDACC__
|
| 6 |
+
#pragma push_macro("__cpp_consteval")
|
| 7 |
+
#pragma push_macro("_NODISCARD")
|
| 8 |
+
#pragma push_macro("__builtin_LINE")
|
| 9 |
+
|
| 10 |
+
#pragma clang diagnostic push
|
| 11 |
+
#pragma clang diagnostic ignored "-Wbuiltin-macro-redefined"
|
| 12 |
+
#define __cpp_consteval 201811L
|
| 13 |
+
#pragma clang diagnostic pop
|
| 14 |
+
|
| 15 |
+
#ifdef _NODISCARD
|
| 16 |
+
#undef _NODISCARD
|
| 17 |
+
#define _NODISCARD
|
| 18 |
+
#endif
|
| 19 |
+
|
| 20 |
+
#define consteval constexpr
|
| 21 |
+
|
| 22 |
+
#include <source_location>
|
| 23 |
+
|
| 24 |
+
#undef consteval
|
| 25 |
+
#pragma pop_macro("__cpp_consteval")
|
| 26 |
+
#pragma pop_macro("_NODISCARD")
|
| 27 |
+
#else
|
| 28 |
+
#include <source_location>
|
| 29 |
+
#endif
|
| 30 |
+
|
| 31 |
+
#include <dlpack/dlpack.h>
|
| 32 |
+
|
| 33 |
+
#include <concepts>
|
| 34 |
+
#include <ostream>
|
| 35 |
+
#include <sstream>
|
| 36 |
+
#include <utility>
|
| 37 |
+
|
| 38 |
+
namespace host {
|
| 39 |
+
|
| 40 |
+
struct PanicError : public std::runtime_error {
|
| 41 |
+
public:
|
| 42 |
+
// copy and move constructors
|
| 43 |
+
PanicError(std::string msg) : runtime_error(msg), m_message(std::move(msg)) {}
|
| 44 |
+
auto detail() const -> std::string_view {
|
| 45 |
+
const auto sv = std::string_view{m_message};
|
| 46 |
+
const auto pos = sv.find(": ");
|
| 47 |
+
return pos == std::string_view::npos ? sv : sv.substr(pos + 2);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
private:
|
| 51 |
+
std::string m_message;
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
template <typename... Args>
|
| 55 |
+
[[noreturn]]
|
| 56 |
+
inline auto panic(std::source_location location, Args &&...args) -> void {
|
| 57 |
+
std::ostringstream os;
|
| 58 |
+
os << "Runtime check failed at " << location.file_name() << ":"
|
| 59 |
+
<< location.line();
|
| 60 |
+
if constexpr (sizeof...(args) > 0) {
|
| 61 |
+
os << ": ";
|
| 62 |
+
(os << ... << std::forward<Args>(args));
|
| 63 |
+
} else {
|
| 64 |
+
os << " in " << location.function_name();
|
| 65 |
+
}
|
| 66 |
+
throw PanicError(std::move(os).str());
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
template <typename... Args> struct Panic {
|
| 70 |
+
explicit Panic(Args &&...args, std::source_location location =
|
| 71 |
+
std::source_location::current()) {
|
| 72 |
+
[[unlikely]];
|
| 73 |
+
::host::panic(location, std::forward<Args>(args)...);
|
| 74 |
+
}
|
| 75 |
+
[[noreturn]] ~Panic() { std::terminate(); }
|
| 76 |
+
};
|
| 77 |
+
|
| 78 |
+
template <typename... Args> struct RuntimeCheck {
|
| 79 |
+
template <typename T>
|
| 80 |
+
explicit RuntimeCheck(
|
| 81 |
+
T &&condition, Args &&...args,
|
| 82 |
+
std::source_location location = std::source_location::current()) {
|
| 83 |
+
if (!condition) {
|
| 84 |
+
[[unlikely]];
|
| 85 |
+
::host::panic(location, std::forward<Args>(args)...);
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
};
|
| 89 |
+
|
| 90 |
+
template <typename T, typename... Args>
|
| 91 |
+
explicit RuntimeCheck(T &&, Args &&...) -> RuntimeCheck<Args...>;
|
| 92 |
+
|
| 93 |
+
template <typename... Args> explicit Panic(Args &&...) -> Panic<Args...>;
|
| 94 |
+
|
| 95 |
+
template <std::integral T, std::integral U>
|
| 96 |
+
inline constexpr auto div_ceil(T a, U b) {
|
| 97 |
+
return (a + b - 1) / b;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
inline auto dtype_bytes(DLDataType dtype) -> std::size_t {
|
| 101 |
+
return static_cast<std::size_t>(dtype.bits / 8);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
namespace pointer {
|
| 105 |
+
|
| 106 |
+
template <typename T, std::integral... U>
|
| 107 |
+
inline auto offset(T *ptr, U... offset) -> void * {
|
| 108 |
+
static_assert(std::is_same_v<T, void>,
|
| 109 |
+
"Pointer arithmetic is only allowed for void* pointers");
|
| 110 |
+
return static_cast<char *>(ptr) + (... + offset);
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
template <typename T, std::integral... U>
|
| 114 |
+
inline auto offset(const T *ptr, U... offset) -> const void * {
|
| 115 |
+
static_assert(std::is_same_v<T, void>,
|
| 116 |
+
"Pointer arithmetic is only allowed for void* pointers");
|
| 117 |
+
return static_cast<const char *>(ptr) + (... + offset);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
} // namespace pointer
|
| 121 |
+
|
| 122 |
+
} // namespace host
|
zonos2/kernel/csrc/include/zonos2/warp.cuh
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
#include <zonos2/utils.cuh>
|
| 3 |
+
|
| 4 |
+
#include <sys/cdefs.h>
|
| 5 |
+
|
| 6 |
+
#include <cstddef>
|
| 7 |
+
|
| 8 |
+
namespace device::warp {
|
| 9 |
+
|
| 10 |
+
namespace details {
|
| 11 |
+
|
| 12 |
+
template <std::size_t kUnit> inline constexpr auto get_mem_package() {
|
| 13 |
+
if constexpr (kUnit == 16) {
|
| 14 |
+
return uint4{};
|
| 15 |
+
} else if constexpr (kUnit == 8) {
|
| 16 |
+
return uint2{};
|
| 17 |
+
} else if constexpr (kUnit == 4) {
|
| 18 |
+
return uint1{};
|
| 19 |
+
} else {
|
| 20 |
+
static_assert(kUnit == 16 || kUnit == 8 || kUnit == 4,
|
| 21 |
+
"Unsupported memory package size");
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
inline constexpr auto resolve_unit_size(std::size_t x) -> std::size_t {
|
| 26 |
+
if (x % (16 * kWarpThreads) == 0)
|
| 27 |
+
return 16;
|
| 28 |
+
if (x % (8 * kWarpThreads) == 0)
|
| 29 |
+
return 8;
|
| 30 |
+
if (x % (4 * kWarpThreads) == 0)
|
| 31 |
+
return 4;
|
| 32 |
+
return 0; // trigger static assert in _get_mem_package
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
template <std::size_t kBytes, std::size_t kUnit>
|
| 36 |
+
using mem_package_t = decltype(get_mem_package<kUnit>());
|
| 37 |
+
|
| 38 |
+
} // namespace details
|
| 39 |
+
|
| 40 |
+
template <std::size_t kBytes,
|
| 41 |
+
std::size_t kUnit = details::resolve_unit_size(kBytes)>
|
| 42 |
+
__always_inline __device__ void copy(void *__restrict__ dst,
|
| 43 |
+
const void *__restrict__ src) {
|
| 44 |
+
using Package = details::mem_package_t<kBytes, kUnit>;
|
| 45 |
+
constexpr auto kBytesPerLoop = sizeof(Package) * kWarpThreads;
|
| 46 |
+
constexpr auto kLoopCount = kBytes / kBytesPerLoop;
|
| 47 |
+
static_assert(kBytes % kBytesPerLoop == 0,
|
| 48 |
+
"kBytes must be multiple of 128 bytes");
|
| 49 |
+
|
| 50 |
+
const auto dst_packed = static_cast<Package *>(dst);
|
| 51 |
+
const auto src_packed = static_cast<const Package *>(src);
|
| 52 |
+
const auto lane_id = threadIdx.x % kWarpThreads;
|
| 53 |
+
|
| 54 |
+
#pragma unroll kLoopCount
|
| 55 |
+
for (std::size_t i = 0; i < kLoopCount; ++i) {
|
| 56 |
+
const auto j = i * kWarpThreads + lane_id;
|
| 57 |
+
dst_packed[j] = src_packed[j];
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
template <std::size_t kBytes,
|
| 62 |
+
std::size_t kUnit = details::resolve_unit_size(kBytes)>
|
| 63 |
+
__always_inline __device__ void reset(void *__restrict__ dst) {
|
| 64 |
+
using Package = details::mem_package_t<kBytes, kUnit>;
|
| 65 |
+
constexpr auto kBytesPerLoop = sizeof(Package) * kWarpThreads;
|
| 66 |
+
constexpr auto kLoopCount = kBytes / kBytesPerLoop;
|
| 67 |
+
static_assert(kBytes % kBytesPerLoop == 0,
|
| 68 |
+
"warp_copy: kBytes must be multiple of 128 bytes");
|
| 69 |
+
|
| 70 |
+
const auto dst_ = static_cast<Package *>(dst);
|
| 71 |
+
const auto lane_id = threadIdx.x % kWarpThreads;
|
| 72 |
+
const auto zero_value = Package{};
|
| 73 |
+
|
| 74 |
+
#pragma unroll kLoopCount
|
| 75 |
+
for (std::size_t i = 0; i < kLoopCount; ++i) {
|
| 76 |
+
dst_[i * kWarpThreads + lane_id] = zero_value;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
} // namespace device::warp
|
zonos2/kernel/csrc/jit/index.cu
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <zonos2/tensor.h>
|
| 2 |
+
#include <zonos2/utils.cuh>
|
| 3 |
+
#include <zonos2/utils.h>
|
| 4 |
+
#include <zonos2/warp.cuh>
|
| 5 |
+
|
| 6 |
+
#include <dlpack/dlpack.h>
|
| 7 |
+
#include <tvm/ffi/container/array.h>
|
| 8 |
+
#include <tvm/ffi/container/tensor.h>
|
| 9 |
+
#include <tvm/ffi/container/tuple.h>
|
| 10 |
+
|
| 11 |
+
#include <bit>
|
| 12 |
+
#include <concepts>
|
| 13 |
+
#include <cstddef>
|
| 14 |
+
#include <cstdint>
|
| 15 |
+
|
| 16 |
+
namespace {
|
| 17 |
+
|
| 18 |
+
struct IndexKernelParams {
|
| 19 |
+
void *__restrict__ output;
|
| 20 |
+
const void *__restrict__ weight;
|
| 21 |
+
const void *__restrict__ indice;
|
| 22 |
+
std::size_t num_warps;
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
struct MaskedKernelParams {
|
| 26 |
+
IndexKernelParams params;
|
| 27 |
+
std::size_t start;
|
| 28 |
+
std::size_t length;
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
template <std::size_t kNumThreads, std::size_t kMaxOccupancy, bool kUsePDL,
|
| 32 |
+
std::size_t kElementSize, std::size_t kNumSplits, std::integral T>
|
| 33 |
+
__global__ __launch_bounds__(kNumThreads, kMaxOccupancy) void //
|
| 34 |
+
index_kernel(const __grid_constant__ IndexKernelParams params) {
|
| 35 |
+
using namespace device;
|
| 36 |
+
constexpr auto kSize = kElementSize;
|
| 37 |
+
constexpr auto kSizePerWarp = kSize / kNumSplits;
|
| 38 |
+
constexpr auto kWarpPerBlock = static_cast<unsigned>(kNumThreads / 32);
|
| 39 |
+
|
| 40 |
+
static_assert(kNumThreads % 32 == 0);
|
| 41 |
+
static_assert(std::has_single_bit(kNumSplits));
|
| 42 |
+
static_assert(kElementSize % kNumSplits == 0);
|
| 43 |
+
|
| 44 |
+
const auto &[output, weight, indices_, num_warps] = params;
|
| 45 |
+
const auto indices = static_cast<const T *>(indices_);
|
| 46 |
+
const auto warp_id =
|
| 47 |
+
(threadIdx.x / kWarpThreads) + blockIdx.x * kWarpPerBlock;
|
| 48 |
+
PDL::wait<kUsePDL>();
|
| 49 |
+
|
| 50 |
+
if (warp_id < num_warps) {
|
| 51 |
+
const auto pos = indices[warp_id / kNumSplits];
|
| 52 |
+
const auto dst = pointer::offset(output, warp_id * kSizePerWarp);
|
| 53 |
+
const auto src = pointer::offset(weight, pos * kSize,
|
| 54 |
+
(warp_id % kNumSplits) * kSizePerWarp);
|
| 55 |
+
warp::copy<kSizePerWarp>(dst, src);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
PDL::launch<kUsePDL>();
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
template <std::size_t kNumThreads, std::size_t kMaxOccupancy, bool kUsePDL,
|
| 62 |
+
std::size_t kElementSize, std::size_t kNumSplits, std::integral T>
|
| 63 |
+
__global__ __launch_bounds__(kNumThreads, kMaxOccupancy) void //
|
| 64 |
+
masked_index_kernel(
|
| 65 |
+
const __grid_constant__ MaskedKernelParams mask_params) {
|
| 66 |
+
using namespace device;
|
| 67 |
+
constexpr auto kSize = kElementSize;
|
| 68 |
+
constexpr auto kSizePerWarp = kSize / kNumSplits;
|
| 69 |
+
constexpr auto kWarpPerBlock = static_cast<unsigned>(kNumThreads / 32);
|
| 70 |
+
|
| 71 |
+
static_assert(kNumThreads % 32 == 0);
|
| 72 |
+
static_assert(std::has_single_bit(kNumSplits));
|
| 73 |
+
static_assert(kElementSize % kNumSplits == 0);
|
| 74 |
+
|
| 75 |
+
const auto &[params, start, length] = mask_params;
|
| 76 |
+
const auto &[output, weight, indices_, num_warps] = params;
|
| 77 |
+
const auto indices = static_cast<const T *>(indices_);
|
| 78 |
+
const auto warp_id =
|
| 79 |
+
(threadIdx.x / kWarpThreads) + blockIdx.x * kWarpPerBlock;
|
| 80 |
+
|
| 81 |
+
PDL::wait<kUsePDL>();
|
| 82 |
+
|
| 83 |
+
if (warp_id < num_warps) {
|
| 84 |
+
const auto pos = indices[warp_id / kNumSplits] - start;
|
| 85 |
+
const auto dst = pointer::offset(output, warp_id * kSizePerWarp);
|
| 86 |
+
if (pos < length) {
|
| 87 |
+
const auto src = pointer::offset(weight, pos * kSize,
|
| 88 |
+
(warp_id % kNumSplits) * kSizePerWarp);
|
| 89 |
+
warp::copy<kSizePerWarp>(dst, src);
|
| 90 |
+
} else {
|
| 91 |
+
warp::reset<kSizePerWarp>(dst);
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
PDL::launch<kUsePDL>();
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
template <std::size_t element_size, // depends on data type and embedding dim
|
| 99 |
+
std::size_t num_splits = 1, // how many warps handles one element
|
| 100 |
+
std::size_t num_threads = 128, // number of threads per block
|
| 101 |
+
std::size_t max_concurrency = 1, // max blocks per SM
|
| 102 |
+
bool use_pdl = false>
|
| 103 |
+
struct IndexKernel {
|
| 104 |
+
static void run(const tvm::ffi::TensorView weights,
|
| 105 |
+
const tvm::ffi::TensorView indices,
|
| 106 |
+
const tvm::ffi::TensorView output,
|
| 107 |
+
tvm::ffi::Optional<tvm::ffi::Tuple<int, int>> mask_opts) {
|
| 108 |
+
using namespace host;
|
| 109 |
+
auto D = SymbolicSize{"D"}; // embedding size
|
| 110 |
+
auto L = SymbolicSize{"L"}; // num indices
|
| 111 |
+
auto device_ = SymbolicDevice{};
|
| 112 |
+
auto weights_dtype_ = SymbolicDType{};
|
| 113 |
+
auto indices_dtype_ = SymbolicDType{};
|
| 114 |
+
|
| 115 |
+
TensorMatcher({-1, D}) //
|
| 116 |
+
.with_dtype(weights_dtype_)
|
| 117 |
+
.with_device<kDLCUDA>(device_)
|
| 118 |
+
.verify(weights);
|
| 119 |
+
TensorMatcher({L, D}) //
|
| 120 |
+
.with_dtype(weights_dtype_)
|
| 121 |
+
.with_device<kDLCUDA>(device_)
|
| 122 |
+
.verify(output);
|
| 123 |
+
TensorMatcher({L}) //
|
| 124 |
+
.with_dtype<int32_t, int64_t>(indices_dtype_)
|
| 125 |
+
.with_device<kDLCUDA>(device_)
|
| 126 |
+
.verify(indices);
|
| 127 |
+
|
| 128 |
+
const auto device = device_.unwrap();
|
| 129 |
+
const auto use_int32 = indices_dtype_.unwrap().bits == 32;
|
| 130 |
+
const auto num_indices = L.unwrap();
|
| 131 |
+
const auto entry_size = dtype_bytes(weights_dtype_.unwrap()) * D.unwrap();
|
| 132 |
+
RuntimeCheck(entry_size == element_size,
|
| 133 |
+
"IndexKernel: element_size mismatch.");
|
| 134 |
+
|
| 135 |
+
constexpr auto kWarpPerBlock = num_threads / 32;
|
| 136 |
+
const auto num_warps = num_splits * num_indices;
|
| 137 |
+
const auto num_blocks = div_ceil(num_warps, kWarpPerBlock);
|
| 138 |
+
const auto params = IndexKernelParams{
|
| 139 |
+
.output = static_cast<char *>(output.data_ptr()),
|
| 140 |
+
.weight = static_cast<const char *>(weights.data_ptr()),
|
| 141 |
+
.indice = indices.data_ptr(),
|
| 142 |
+
.num_warps = num_warps,
|
| 143 |
+
};
|
| 144 |
+
|
| 145 |
+
if (mask_opts.has_value()) {
|
| 146 |
+
const auto &obj = mask_opts.value();
|
| 147 |
+
const auto [start, length] = obj;
|
| 148 |
+
const auto m_params = MaskedKernelParams{
|
| 149 |
+
.params = params,
|
| 150 |
+
.start = static_cast<std::size_t>(start),
|
| 151 |
+
.length = static_cast<std::size_t>(length),
|
| 152 |
+
};
|
| 153 |
+
const auto kernel =
|
| 154 |
+
use_int32 ? masked_index_kernel<num_threads, max_concurrency, use_pdl,
|
| 155 |
+
element_size, num_splits, int32_t>
|
| 156 |
+
: masked_index_kernel<num_threads, max_concurrency, use_pdl,
|
| 157 |
+
element_size, num_splits, int64_t>;
|
| 158 |
+
LaunchKernel(num_blocks, num_threads, device)
|
| 159 |
+
.with_attr(use_pdl)(kernel, m_params);
|
| 160 |
+
} else {
|
| 161 |
+
const auto kernel =
|
| 162 |
+
use_int32 ? index_kernel<num_threads, max_concurrency, use_pdl,
|
| 163 |
+
element_size, num_splits, int32_t>
|
| 164 |
+
: index_kernel<num_threads, max_concurrency, use_pdl,
|
| 165 |
+
element_size, num_splits, int64_t>;
|
| 166 |
+
LaunchKernel(num_blocks, num_threads, device)
|
| 167 |
+
.with_attr(use_pdl)(kernel, params);
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
};
|
| 171 |
+
|
| 172 |
+
} // namespace
|
zonos2/kernel/csrc/jit/store.cu
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <zonos2/tensor.h>
|
| 2 |
+
#include <zonos2/utils.cuh>
|
| 3 |
+
#include <zonos2/utils.h>
|
| 4 |
+
#include <zonos2/warp.cuh>
|
| 5 |
+
|
| 6 |
+
#include <tvm/ffi/container/tensor.h>
|
| 7 |
+
|
| 8 |
+
#include <concepts>
|
| 9 |
+
#include <cstddef>
|
| 10 |
+
#include <cstdint>
|
| 11 |
+
|
| 12 |
+
namespace {
|
| 13 |
+
|
| 14 |
+
struct StoreKernelParams {
|
| 15 |
+
void *__restrict__ k_cache;
|
| 16 |
+
void *__restrict__ v_cache;
|
| 17 |
+
const void *__restrict__ indices;
|
| 18 |
+
const void *__restrict__ k;
|
| 19 |
+
const void *__restrict__ v;
|
| 20 |
+
std::size_t kv_cache_stride;
|
| 21 |
+
std::size_t kv_input_stride;
|
| 22 |
+
std::size_t length;
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
template <std::size_t kNumThreads, std::size_t kMaxOccupancy, bool kUsePDL,
|
| 26 |
+
std::size_t kElementSize, std::integral T>
|
| 27 |
+
__global__ __launch_bounds__(kNumThreads, kMaxOccupancy) void //
|
| 28 |
+
store_kv_cache(const __grid_constant__ StoreKernelParams params) {
|
| 29 |
+
using namespace device;
|
| 30 |
+
|
| 31 |
+
constexpr auto kWarpPerBlock =
|
| 32 |
+
static_cast<unsigned>(kNumThreads / kWarpThreads);
|
| 33 |
+
static_assert(kNumThreads % kWarpThreads == 0);
|
| 34 |
+
|
| 35 |
+
const auto &[k_cache, v_cache, indices, k, v, kv_cache_stride,
|
| 36 |
+
kv_input_stride, length] = params;
|
| 37 |
+
const auto warp_id =
|
| 38 |
+
(threadIdx.x / kWarpThreads) + blockIdx.x * kWarpPerBlock;
|
| 39 |
+
PDL::wait<kUsePDL>();
|
| 40 |
+
|
| 41 |
+
// each warp handles one element
|
| 42 |
+
if (warp_id < length) {
|
| 43 |
+
const auto pos = static_cast<const T *>(indices)[warp_id];
|
| 44 |
+
const auto dst_k = pointer::offset(k_cache, pos * kv_cache_stride);
|
| 45 |
+
const auto src_k = pointer::offset(k, warp_id * kv_input_stride);
|
| 46 |
+
warp::copy<kElementSize>(dst_k, src_k);
|
| 47 |
+
const auto dst_v = pointer::offset(v_cache, pos * kv_cache_stride);
|
| 48 |
+
const auto src_v = pointer::offset(v, warp_id * kv_input_stride);
|
| 49 |
+
warp::copy<kElementSize>(dst_v, src_v);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
PDL::launch<kUsePDL>();
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
template <std::size_t element_size, // depends on data type and embedding dim
|
| 56 |
+
std::size_t num_threads = 128, // number of threads per block
|
| 57 |
+
std::size_t max_concurrency = 1, // max blocks per SM
|
| 58 |
+
bool use_pdl = false>
|
| 59 |
+
struct StoreKernel {
|
| 60 |
+
static void run(const tvm::ffi::TensorView k_cache,
|
| 61 |
+
const tvm::ffi::TensorView v_cache,
|
| 62 |
+
const tvm::ffi::TensorView indices,
|
| 63 |
+
const tvm::ffi::TensorView k, const tvm::ffi::TensorView v) {
|
| 64 |
+
using namespace host;
|
| 65 |
+
auto D = SymbolicSize{"D"}; // element size
|
| 66 |
+
auto L = SymbolicSize{"L"}; // length
|
| 67 |
+
auto X = SymbolicSize{"X"}; // stride kv cache
|
| 68 |
+
auto Y = SymbolicSize{"Y"}; // stride kv input
|
| 69 |
+
auto indices_dtype_ = SymbolicDType{};
|
| 70 |
+
auto dtype_ = SymbolicDType{};
|
| 71 |
+
auto device_ = SymbolicDevice{};
|
| 72 |
+
|
| 73 |
+
TensorMatcher({-1, D}) //
|
| 74 |
+
.with_strides({X, 1})
|
| 75 |
+
.with_device<kDLCUDA>(device_)
|
| 76 |
+
.with_dtype(dtype_)
|
| 77 |
+
.verify(k_cache)
|
| 78 |
+
.verify(v_cache);
|
| 79 |
+
TensorMatcher({L, D}) //
|
| 80 |
+
.with_strides({Y, 1})
|
| 81 |
+
.with_device<kDLCUDA>(device_)
|
| 82 |
+
.with_dtype(dtype_)
|
| 83 |
+
.verify(k)
|
| 84 |
+
.verify(v);
|
| 85 |
+
TensorMatcher({L}) //
|
| 86 |
+
.with_device<kDLCUDA>(device_)
|
| 87 |
+
.with_dtype<int32_t, int64_t>(indices_dtype_)
|
| 88 |
+
.verify(indices);
|
| 89 |
+
|
| 90 |
+
const auto dtype_size = dtype_bytes(dtype_.unwrap());
|
| 91 |
+
RuntimeCheck(element_size == dtype_size * D.unwrap());
|
| 92 |
+
|
| 93 |
+
const auto device = device_.unwrap();
|
| 94 |
+
const auto use_int32 = indices_dtype_.unwrap().bits == 32;
|
| 95 |
+
const auto length = static_cast<std::size_t>(L.unwrap());
|
| 96 |
+
const auto kv_cache_stride = X.unwrap() * dtype_size;
|
| 97 |
+
const auto kv_input_stride = Y.unwrap() * dtype_size;
|
| 98 |
+
|
| 99 |
+
const auto params = StoreKernelParams{
|
| 100 |
+
.k_cache = k_cache.data_ptr(),
|
| 101 |
+
.v_cache = v_cache.data_ptr(),
|
| 102 |
+
.indices = indices.data_ptr(),
|
| 103 |
+
.k = k.data_ptr(),
|
| 104 |
+
.v = v.data_ptr(),
|
| 105 |
+
.kv_cache_stride = kv_cache_stride,
|
| 106 |
+
.kv_input_stride = kv_input_stride,
|
| 107 |
+
.length = length,
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
constexpr auto kWarpPerBlock = num_threads / 32;
|
| 111 |
+
static_assert(num_threads % 32 == 0);
|
| 112 |
+
const auto num_blocks = div_ceil(length, kWarpPerBlock);
|
| 113 |
+
const auto kernel = use_int32
|
| 114 |
+
? store_kv_cache<num_threads, max_concurrency,
|
| 115 |
+
use_pdl, element_size, int32_t>
|
| 116 |
+
: store_kv_cache<num_threads, max_concurrency,
|
| 117 |
+
use_pdl, element_size, int64_t>;
|
| 118 |
+
LaunchKernel(num_blocks, num_threads, device)
|
| 119 |
+
.with_attr(use_pdl)(kernel, params);
|
| 120 |
+
}
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
} // namespace
|
zonos2/kernel/csrc/src/pynccl.cu
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <zonos2/nccl227.h>
|
| 2 |
+
#include <zonos2/tensor.h>
|
| 3 |
+
#include <zonos2/utils.cuh>
|
| 4 |
+
#include <zonos2/utils.h>
|
| 5 |
+
|
| 6 |
+
#include <dlpack/dlpack.h>
|
| 7 |
+
#include <tvm/ffi/container/array.h>
|
| 8 |
+
#include <tvm/ffi/container/tensor.h>
|
| 9 |
+
#include <tvm/ffi/function.h>
|
| 10 |
+
#include <tvm/ffi/reflection/registry.h>
|
| 11 |
+
|
| 12 |
+
#include <bit>
|
| 13 |
+
#include <cstdint>
|
| 14 |
+
#include <memory>
|
| 15 |
+
#include <string>
|
| 16 |
+
#include <string_view>
|
| 17 |
+
#include <unordered_map>
|
| 18 |
+
|
| 19 |
+
namespace {
|
| 20 |
+
|
| 21 |
+
using NCCLIDList = tvm::ffi::Array<char>;
|
| 22 |
+
|
| 23 |
+
auto NCCL_CHECK(::ncclResult_t result) -> void {
|
| 24 |
+
if (result != ::ncclSuccess) {
|
| 25 |
+
host::RuntimeCheck(false, ::ncclGetErrorString(result));
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
auto get_uid(const NCCLIDList &wrapper) -> ncclUniqueId {
|
| 30 |
+
host::RuntimeCheck(wrapper.size() == NCCL_UNIQUE_ID_BYTES,
|
| 31 |
+
"Invalid NCCL ID wrapper size");
|
| 32 |
+
ncclUniqueId id;
|
| 33 |
+
std::copy(wrapper.begin(), wrapper.end(), id.internal);
|
| 34 |
+
return id;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
auto create_uid() -> NCCLIDList {
|
| 38 |
+
ncclUniqueId id;
|
| 39 |
+
NCCL_CHECK(::ncclGetUniqueId(&id));
|
| 40 |
+
return NCCLIDList(id.internal, id.internal + NCCL_UNIQUE_ID_BYTES);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const auto kNCCLReduceOPMap = std::unordered_map<std::string_view, ncclRedOp_t>{
|
| 44 |
+
{"sum", ::ncclSum}, {"prod", ::ncclProd}, {"max", ::ncclMax},
|
| 45 |
+
{"min", ::ncclMin}, {"avg", ::ncclAvg},
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
struct DLDataTypeHash {
|
| 49 |
+
auto operator()(const DLDataType &dtype) const noexcept -> std::size_t {
|
| 50 |
+
return std::bit_cast<std::uint32_t>(dtype);
|
| 51 |
+
}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
template <typename = void>
|
| 55 |
+
auto operator==(const DLDataType &a, const DLDataType &b) -> bool {
|
| 56 |
+
return a.code == b.code && a.bits == b.bits && a.lanes == b.lanes;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
const auto kNCCLDtypeMap =
|
| 60 |
+
std::unordered_map<DLDataType, ncclDataType_t, DLDataTypeHash>{
|
| 61 |
+
{{DLDataTypeCode::kDLFloat, 16, 1}, ncclFloat16},
|
| 62 |
+
{{DLDataTypeCode::kDLBfloat, 16, 1}, ncclBfloat16},
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
using std::shared_ptr;
|
| 66 |
+
|
| 67 |
+
template <typename T> using shared_obj = shared_ptr<std::remove_pointer_t<T>>;
|
| 68 |
+
template <auto Fn>
|
| 69 |
+
inline constexpr auto template_fn =
|
| 70 |
+
[](auto &&...args) { return Fn(std::forward<decltype(args)>(args)...); };
|
| 71 |
+
|
| 72 |
+
struct NCCLWrapper : public tvm::ffi::Object {
|
| 73 |
+
public:
|
| 74 |
+
NCCLWrapper(int rank, int world_size, const size_t max_bytes, NCCLIDList uid)
|
| 75 |
+
: m_rank(rank), m_world_size(world_size), m_max_bytes(max_bytes) {
|
| 76 |
+
ncclUniqueId id = get_uid(uid);
|
| 77 |
+
ncclComm_t comm;
|
| 78 |
+
NCCL_CHECK(::ncclCommInitRank(&comm, m_world_size, id, m_rank));
|
| 79 |
+
m_comm = {comm, template_fn<::ncclCommDestroy>};
|
| 80 |
+
|
| 81 |
+
void *buf;
|
| 82 |
+
NCCL_CHECK(::ncclMemAlloc(&buf, max_bytes));
|
| 83 |
+
m_sym_mem = {buf, template_fn<::ncclMemFree>};
|
| 84 |
+
|
| 85 |
+
ncclWindow_t win;
|
| 86 |
+
NCCL_CHECK(::ncclCommWindowRegister(comm, buf, max_bytes, &win,
|
| 87 |
+
NCCL_WIN_COLL_SYMMETRIC));
|
| 88 |
+
m_win = {win, [comm = m_comm](ncclWindow_t w) {
|
| 89 |
+
return NCCL_CHECK(::ncclCommWindowDeregister(comm.get(), w));
|
| 90 |
+
}};
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
auto all_reduce(tvm::ffi::TensorView t, std::string op) const -> void {
|
| 94 |
+
using namespace host;
|
| 95 |
+
RuntimeCheck(t.device().device_type == kDLCUDA,
|
| 96 |
+
"Tensor must be on CUDA device");
|
| 97 |
+
RuntimeCheck(t.is_contiguous(), "Tensor must be contiguous");
|
| 98 |
+
const auto size_dim = static_cast<size_t>(t.shape().Product());
|
| 99 |
+
const auto dtype = kNCCLDtypeMap.at(t.dtype());
|
| 100 |
+
const auto size_bytes = size_dim * (t.dtype().bits / 8);
|
| 101 |
+
const auto data_ptr = t.data_ptr();
|
| 102 |
+
const auto reduce_op = kNCCLReduceOPMap.at(op);
|
| 103 |
+
const auto stream = LaunchKernel::resolve_device(t.device());
|
| 104 |
+
|
| 105 |
+
if (size_bytes <= m_max_bytes) { // use internal buffer
|
| 106 |
+
const auto buf_ptr = m_sym_mem.get();
|
| 107 |
+
const auto need_memcpy = (buf_ptr != data_ptr);
|
| 108 |
+
if (need_memcpy) {
|
| 109 |
+
CUDA_CHECK(::cudaMemcpyAsync(buf_ptr, data_ptr, size_bytes,
|
| 110 |
+
::cudaMemcpyDeviceToDevice, stream));
|
| 111 |
+
}
|
| 112 |
+
NCCL_CHECK(::ncclAllReduce(
|
| 113 |
+
/*sendbuff=*/buf_ptr,
|
| 114 |
+
/*recvbuff=*/buf_ptr,
|
| 115 |
+
/*count=*/size_dim,
|
| 116 |
+
/*datatype=*/dtype,
|
| 117 |
+
/*op=*/reduce_op,
|
| 118 |
+
/*comm=*/m_comm.get(),
|
| 119 |
+
/*stream=*/stream));
|
| 120 |
+
if (need_memcpy) {
|
| 121 |
+
CUDA_CHECK(::cudaMemcpyAsync(data_ptr, buf_ptr, size_bytes,
|
| 122 |
+
::cudaMemcpyDeviceToDevice, stream));
|
| 123 |
+
}
|
| 124 |
+
} else {
|
| 125 |
+
NCCL_CHECK(::ncclAllReduce(
|
| 126 |
+
/*sendbuff=*/data_ptr,
|
| 127 |
+
/*recvbuff=*/data_ptr,
|
| 128 |
+
/*count=*/size_dim,
|
| 129 |
+
/*datatype=*/dtype,
|
| 130 |
+
/*op=*/reduce_op,
|
| 131 |
+
/*comm=*/m_comm.get(),
|
| 132 |
+
/*stream=*/stream));
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
auto all_gather(tvm::ffi::TensorView dst, tvm::ffi::TensorView src) const
|
| 137 |
+
-> void {
|
| 138 |
+
using namespace host;
|
| 139 |
+
RuntimeCheck(src.device().device_type == kDLCUDA,
|
| 140 |
+
"Tensor must be on CUDA device");
|
| 141 |
+
RuntimeCheck(src.is_contiguous(), "Tensor must be contiguous");
|
| 142 |
+
RuntimeCheck(dst.device().device_type == kDLCUDA,
|
| 143 |
+
"Tensor must be on CUDA device");
|
| 144 |
+
RuntimeCheck(dst.is_contiguous(), "Tensor must be contiguous");
|
| 145 |
+
RuntimeCheck(dst.size(0) == src.size(0) * m_world_size,
|
| 146 |
+
"Destination tensor has incorrect size");
|
| 147 |
+
const auto size_dim = static_cast<size_t>(src.shape().Product());
|
| 148 |
+
const auto dtype = kNCCLDtypeMap.at(src.dtype());
|
| 149 |
+
const auto src_ptr = src.data_ptr();
|
| 150 |
+
const auto dst_ptr = dst.data_ptr();
|
| 151 |
+
const auto stream = LaunchKernel::resolve_device(src.device());
|
| 152 |
+
// do not use internal buffer for all_gather, directly gather to output
|
| 153 |
+
// tensor
|
| 154 |
+
NCCL_CHECK(::ncclAllGather(
|
| 155 |
+
/*sendbuff=*/src_ptr,
|
| 156 |
+
/*recvbuff=*/dst_ptr,
|
| 157 |
+
/*sendcount=*/size_dim,
|
| 158 |
+
/*datatype=*/dtype,
|
| 159 |
+
/*comm=*/m_comm.get(),
|
| 160 |
+
/*stream=*/stream));
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
auto get_buffer() const -> void * { return m_sym_mem.get(); }
|
| 164 |
+
|
| 165 |
+
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("zonos2.NCCLWrapper", NCCLWrapper,
|
| 166 |
+
tvm::ffi::Object);
|
| 167 |
+
|
| 168 |
+
private:
|
| 169 |
+
int m_rank;
|
| 170 |
+
int m_world_size;
|
| 171 |
+
size_t m_max_bytes;
|
| 172 |
+
shared_obj<ncclComm_t> m_comm;
|
| 173 |
+
shared_ptr<void> m_sym_mem;
|
| 174 |
+
shared_obj<ncclWindow_t> m_win;
|
| 175 |
+
};
|
| 176 |
+
|
| 177 |
+
TVM_FFI_STATIC_INIT_BLOCK() {
|
| 178 |
+
namespace refl = tvm::ffi::reflection;
|
| 179 |
+
refl::ObjectDef<NCCLWrapper>()
|
| 180 |
+
.def(refl::init<int, int, size_t, NCCLIDList>(), "__init__")
|
| 181 |
+
.def("all_reduce", &NCCLWrapper::all_reduce)
|
| 182 |
+
.def("all_gather", &NCCLWrapper::all_gather)
|
| 183 |
+
.def("get_buffer", &NCCLWrapper::get_buffer);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
TVM_FFI_DLL_EXPORT_TYPED_FUNC(create_nccl_uid, &create_uid);
|
| 187 |
+
|
| 188 |
+
} // namespace
|
zonos2/kernel/csrc/src/radix.cpp
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <zonos2/utils.h>
|
| 2 |
+
|
| 3 |
+
#include <dlpack/dlpack.h>
|
| 4 |
+
#include <tvm/ffi/container/tensor.h>
|
| 5 |
+
#include <tvm/ffi/dtype.h>
|
| 6 |
+
#include <tvm/ffi/extra/c_env_api.h>
|
| 7 |
+
#include <tvm/ffi/function.h>
|
| 8 |
+
#include <tvm/ffi/object.h>
|
| 9 |
+
|
| 10 |
+
namespace {
|
| 11 |
+
|
| 12 |
+
auto _is_1d_cpu_int_tensor(const tvm::ffi::TensorView tensor) -> bool {
|
| 13 |
+
return tensor.ndim() == 1 && tensor.is_contiguous() &&
|
| 14 |
+
tensor.device().device_type == kDLCPU &&
|
| 15 |
+
(tensor.dtype().code == kDLInt) &&
|
| 16 |
+
(tensor.dtype().bits == 32 || tensor.dtype().bits == 64);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
auto fast_compare_key(const tvm::ffi::TensorView a,
|
| 20 |
+
const tvm::ffi::TensorView b) -> size_t {
|
| 21 |
+
host::RuntimeCheck(_is_1d_cpu_int_tensor(a) && _is_1d_cpu_int_tensor(b),
|
| 22 |
+
"Both tensors must be 1D CPU int tensors.");
|
| 23 |
+
host::RuntimeCheck(a.dtype() == b.dtype());
|
| 24 |
+
const auto a_ptr = a.data_ptr();
|
| 25 |
+
const auto b_ptr = b.data_ptr();
|
| 26 |
+
const auto common_len = std::min(a.size(0), b.size(0));
|
| 27 |
+
if (a.dtype().bits == 64) {
|
| 28 |
+
const auto a_ptr_64 = static_cast<const int64_t *>(a_ptr);
|
| 29 |
+
const auto b_ptr_64 = static_cast<const int64_t *>(b_ptr);
|
| 30 |
+
const auto diff_pos =
|
| 31 |
+
std::mismatch(a_ptr_64, a_ptr_64 + common_len, b_ptr_64);
|
| 32 |
+
return static_cast<size_t>(diff_pos.first - a_ptr_64);
|
| 33 |
+
} else {
|
| 34 |
+
const auto a_ptr_32 = static_cast<const int32_t *>(a_ptr);
|
| 35 |
+
const auto b_ptr_32 = static_cast<const int32_t *>(b_ptr);
|
| 36 |
+
const auto diff_pos =
|
| 37 |
+
std::mismatch(a_ptr_32, a_ptr_32 + common_len, b_ptr_32);
|
| 38 |
+
return static_cast<size_t>(diff_pos.first - a_ptr_32);
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
} // namespace
|
| 43 |
+
|
| 44 |
+
TVM_FFI_DLL_EXPORT_TYPED_FUNC(fast_compare_key, fast_compare_key);
|
zonos2/kernel/csrc/src/tensor.cpp
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <zonos2/tensor.h>
|
| 2 |
+
#include <zonos2/utils.h>
|
| 3 |
+
|
| 4 |
+
#include <dlpack/dlpack.h>
|
| 5 |
+
#include <tvm/ffi/container/array.h>
|
| 6 |
+
#include <tvm/ffi/container/tensor.h>
|
| 7 |
+
#include <tvm/ffi/dtype.h>
|
| 8 |
+
#include <tvm/ffi/extra/c_env_api.h>
|
| 9 |
+
#include <tvm/ffi/function.h>
|
| 10 |
+
#include <tvm/ffi/object.h>
|
| 11 |
+
|
| 12 |
+
namespace {
|
| 13 |
+
|
| 14 |
+
auto test(tvm::ffi::TensorView x, tvm::ffi::TensorView y) -> void {
|
| 15 |
+
auto N = host::SymbolicSize{"N"};
|
| 16 |
+
const auto M = 1024;
|
| 17 |
+
host::TensorMatcher({N, M})
|
| 18 |
+
.with_strides({-1, 1}) // -1 means any
|
| 19 |
+
.with_dtype<int, float>()
|
| 20 |
+
.with_device<kDLCPU>()
|
| 21 |
+
.verify(x);
|
| 22 |
+
host::TensorMatcher({N, M}) // default contiguous
|
| 23 |
+
.with_dtype({{kDLInt, 32, 1}, {kDLInt, 64, 1}})
|
| 24 |
+
.with_device({{kDLCUDA, 1}})
|
| 25 |
+
.verify(y);
|
| 26 |
+
host::RuntimeCheck(N.unwrap() % 4 == 0);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
} // namespace
|
| 30 |
+
|
| 31 |
+
TVM_FFI_DLL_EXPORT_TYPED_FUNC(test, test);
|
zonos2/kernel/index.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from functools import lru_cache
|
| 4 |
+
from typing import TYPE_CHECKING, Tuple
|
| 5 |
+
|
| 6 |
+
from .utils import KernelConfig, load_jit, make_cpp_args
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
import torch
|
| 10 |
+
from tvm_ffi import Module
|
| 11 |
+
|
| 12 |
+
DEFAULT_INDEX_KERNEL_CONFIG = KernelConfig(num_threads=128, max_occupancy=1, use_pdl=False)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@lru_cache(maxsize=None)
|
| 16 |
+
def _jit_index_module(
|
| 17 |
+
element_size: int,
|
| 18 |
+
*,
|
| 19 |
+
num_splits: int = 1,
|
| 20 |
+
config: KernelConfig = DEFAULT_INDEX_KERNEL_CONFIG,
|
| 21 |
+
) -> Module:
|
| 22 |
+
args = make_cpp_args(element_size, num_splits, *config)
|
| 23 |
+
return load_jit(
|
| 24 |
+
"index",
|
| 25 |
+
*args,
|
| 26 |
+
cuda_files=["index.cu"],
|
| 27 |
+
cuda_wrappers=[("launch", f"IndexKernel<{args}>::run")],
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def indexing(
|
| 32 |
+
weights: torch.Tensor,
|
| 33 |
+
indices: torch.Tensor,
|
| 34 |
+
*,
|
| 35 |
+
output: torch.Tensor | None = None,
|
| 36 |
+
vocab_range: Tuple[int, int] | None = None, # (start, length)
|
| 37 |
+
) -> torch.Tensor:
|
| 38 |
+
if output is None:
|
| 39 |
+
output = weights.new_empty(indices.shape[0], weights.shape[1])
|
| 40 |
+
|
| 41 |
+
element_size = weights.shape[1] * weights.element_size()
|
| 42 |
+
if element_size % 2048 == 0:
|
| 43 |
+
num_splits = 4
|
| 44 |
+
elif element_size % 1024 == 0:
|
| 45 |
+
num_splits = 2
|
| 46 |
+
else:
|
| 47 |
+
num_splits = 1
|
| 48 |
+
module = _jit_index_module(element_size, num_splits=num_splits)
|
| 49 |
+
module.launch(weights, indices, output, vocab_range)
|
| 50 |
+
return output
|
zonos2/kernel/moe_impl.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, Dict
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import triton
|
| 5 |
+
import triton.language as tl
|
| 6 |
+
from zonos2.kernel.triton.fused_moe import fused_moe_kernel
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def fused_moe_kernel_triton(
|
| 10 |
+
A: torch.Tensor,
|
| 11 |
+
B: torch.Tensor,
|
| 12 |
+
C: torch.Tensor,
|
| 13 |
+
topk_weights: torch.Tensor,
|
| 14 |
+
topk_ids: torch.Tensor,
|
| 15 |
+
sorted_token_ids: torch.Tensor,
|
| 16 |
+
expert_ids: torch.Tensor,
|
| 17 |
+
num_tokens_post_padded: torch.Tensor,
|
| 18 |
+
mul_routed_weight: bool,
|
| 19 |
+
top_k: int,
|
| 20 |
+
config: Dict[str, Any],
|
| 21 |
+
compute_type: tl.dtype,
|
| 22 |
+
) -> None:
|
| 23 |
+
assert topk_weights.stride(1) == 1
|
| 24 |
+
assert sorted_token_ids.stride(0) == 1
|
| 25 |
+
padded_size = 0
|
| 26 |
+
grid = lambda META: (
|
| 27 |
+
triton.cdiv(sorted_token_ids.shape[0], META["BLOCK_SIZE_M"])
|
| 28 |
+
* triton.cdiv(B.shape[1], META["BLOCK_SIZE_N"]),
|
| 29 |
+
)
|
| 30 |
+
K = B.shape[2] - padded_size
|
| 31 |
+
if K % config["BLOCK_SIZE_K"] == 0:
|
| 32 |
+
even_Ks = True
|
| 33 |
+
else:
|
| 34 |
+
even_Ks = False
|
| 35 |
+
fused_moe_kernel[grid](
|
| 36 |
+
A,
|
| 37 |
+
B,
|
| 38 |
+
C,
|
| 39 |
+
topk_weights,
|
| 40 |
+
sorted_token_ids,
|
| 41 |
+
expert_ids,
|
| 42 |
+
num_tokens_post_padded,
|
| 43 |
+
B.shape[1],
|
| 44 |
+
B.shape[2] - padded_size,
|
| 45 |
+
sorted_token_ids.shape[0],
|
| 46 |
+
topk_ids.numel(),
|
| 47 |
+
A.stride(0),
|
| 48 |
+
A.stride(1),
|
| 49 |
+
B.stride(0),
|
| 50 |
+
B.stride(2),
|
| 51 |
+
B.stride(1),
|
| 52 |
+
C.stride(1),
|
| 53 |
+
C.stride(2),
|
| 54 |
+
MUL_ROUTED_WEIGHT=mul_routed_weight,
|
| 55 |
+
top_k=top_k,
|
| 56 |
+
compute_type=compute_type,
|
| 57 |
+
even_Ks=even_Ks,
|
| 58 |
+
**config,
|
| 59 |
+
)
|
zonos2/kernel/pynccl.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from functools import lru_cache
|
| 4 |
+
from typing import TYPE_CHECKING, Any, Literal
|
| 5 |
+
|
| 6 |
+
from zonos2.env import ENV
|
| 7 |
+
|
| 8 |
+
from .utils import load_aot
|
| 9 |
+
|
| 10 |
+
if TYPE_CHECKING:
|
| 11 |
+
from abc import abstractmethod
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
from tvm_ffi import Module
|
| 15 |
+
|
| 16 |
+
class PyNCCLCommunicator:
|
| 17 |
+
@abstractmethod
|
| 18 |
+
def all_reduce(self, input: torch.Tensor, op: Literal["sum"]) -> None: ...
|
| 19 |
+
@abstractmethod
|
| 20 |
+
def all_gather(self, output: torch.Tensor, input: torch.Tensor) -> None: ...
|
| 21 |
+
@abstractmethod
|
| 22 |
+
def get_buffer(self) -> int: ...
|
| 23 |
+
|
| 24 |
+
else:
|
| 25 |
+
PyNCCLCommunicator = Any
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@lru_cache(maxsize=None)
|
| 29 |
+
def _load_nccl_module() -> Module:
|
| 30 |
+
return load_aot("pynccl", cuda_files=["pynccl.cu"], extra_ldflags=["-lnccl"])
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@lru_cache(maxsize=None)
|
| 34 |
+
def _get_pynccl_wrapper_cls():
|
| 35 |
+
import tvm_ffi
|
| 36 |
+
|
| 37 |
+
@tvm_ffi.register_object("zonos2.NCCLWrapper")
|
| 38 |
+
class PyNCCLImpl(tvm_ffi.Object):
|
| 39 |
+
def __init__(self, *args):
|
| 40 |
+
self.__ffi_init__(*args)
|
| 41 |
+
|
| 42 |
+
return PyNCCLImpl
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def init_pynccl(
|
| 46 |
+
*,
|
| 47 |
+
tp_rank: int,
|
| 48 |
+
tp_size: int,
|
| 49 |
+
tp_cpu_group: torch.distributed.ProcessGroup,
|
| 50 |
+
max_size_bytes: int = 0,
|
| 51 |
+
) -> PyNCCLCommunicator:
|
| 52 |
+
import torch
|
| 53 |
+
|
| 54 |
+
max_size_bytes = min(max_size_bytes, ENV.PYNCCL_MAX_BUFFER_SIZE.value)
|
| 55 |
+
|
| 56 |
+
module = _load_nccl_module()
|
| 57 |
+
cls = _get_pynccl_wrapper_cls()
|
| 58 |
+
|
| 59 |
+
if tp_rank == 0:
|
| 60 |
+
id_list = [module.create_nccl_uid()]
|
| 61 |
+
torch.distributed.broadcast_object_list(
|
| 62 |
+
id_list,
|
| 63 |
+
src=0,
|
| 64 |
+
group=tp_cpu_group,
|
| 65 |
+
)
|
| 66 |
+
else:
|
| 67 |
+
id_list = [None]
|
| 68 |
+
torch.distributed.broadcast_object_list(
|
| 69 |
+
id_list,
|
| 70 |
+
src=0,
|
| 71 |
+
group=tp_cpu_group,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
nccl_id = id_list[0]
|
| 75 |
+
assert not nccl_id is None, f"Failed to get NCCL unique ID on {tp_rank = }"
|
| 76 |
+
|
| 77 |
+
# bypass type checking for the FFI object
|
| 78 |
+
return cls(tp_rank, tp_size, max_size_bytes, nccl_id) # type: ignore
|
zonos2/kernel/radix.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from functools import lru_cache
|
| 4 |
+
from typing import TYPE_CHECKING
|
| 5 |
+
|
| 6 |
+
from .utils import load_aot
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
import torch
|
| 10 |
+
from tvm_ffi import Module
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@lru_cache(maxsize=None)
|
| 14 |
+
def _load_radix_module() -> Module:
|
| 15 |
+
return load_aot("radix", cpp_files=["radix.cpp"])
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def fast_compare_key(x: torch.Tensor, y: torch.Tensor) -> int:
|
| 19 |
+
# compare 2 1-D int cpu tensors for equality
|
| 20 |
+
return _load_radix_module().fast_compare_key(x, y)
|
zonos2/kernel/store.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from functools import lru_cache
|
| 4 |
+
from typing import TYPE_CHECKING
|
| 5 |
+
|
| 6 |
+
from .utils import KernelConfig, load_jit, make_cpp_args
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
import torch
|
| 10 |
+
from tvm_ffi import Module
|
| 11 |
+
|
| 12 |
+
DEFAULT_INDEX_KERNEL_CONFIG = KernelConfig(num_threads=128, max_occupancy=1, use_pdl=False)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@lru_cache(maxsize=None)
|
| 16 |
+
def _jit_store_module(
|
| 17 |
+
element_size: int,
|
| 18 |
+
*,
|
| 19 |
+
config: KernelConfig = DEFAULT_INDEX_KERNEL_CONFIG,
|
| 20 |
+
) -> Module:
|
| 21 |
+
args = make_cpp_args(element_size, *config)
|
| 22 |
+
return load_jit(
|
| 23 |
+
"store",
|
| 24 |
+
*args,
|
| 25 |
+
cuda_files=["store.cu"],
|
| 26 |
+
cuda_wrappers=[("launch", f"StoreKernel<{args}>::run")],
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def store_cache(
|
| 31 |
+
k_cache: torch.Tensor,
|
| 32 |
+
v_cache: torch.Tensor,
|
| 33 |
+
indices: torch.Tensor,
|
| 34 |
+
k: torch.Tensor,
|
| 35 |
+
v: torch.Tensor,
|
| 36 |
+
) -> None:
|
| 37 |
+
num_tokens = k_cache.shape[0]
|
| 38 |
+
k_cache = k_cache.view(num_tokens, -1)
|
| 39 |
+
v_cache = v_cache.view(num_tokens, -1)
|
| 40 |
+
element_size = k_cache.shape[1] * k_cache.element_size()
|
| 41 |
+
module = _jit_store_module(element_size)
|
| 42 |
+
module.launch(k_cache, v_cache, indices, k, v)
|
zonos2/kernel/tensor.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from functools import lru_cache
|
| 4 |
+
from typing import TYPE_CHECKING
|
| 5 |
+
|
| 6 |
+
from .utils import load_aot
|
| 7 |
+
|
| 8 |
+
if TYPE_CHECKING:
|
| 9 |
+
import torch
|
| 10 |
+
from tvm_ffi import Module
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@lru_cache(maxsize=None)
|
| 14 |
+
def _load_test_tensor_module() -> Module:
|
| 15 |
+
return load_aot("test_tensor", cpp_files=["tensor.cpp"])
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_tensor(x: torch.Tensor, y: torch.Tensor) -> int:
|
| 19 |
+
return _load_test_tensor_module().test(x, y)
|
zonos2/kernel/triton/fused_moe.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import triton
|
| 3 |
+
import triton.language as tl
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@triton.jit
|
| 7 |
+
def _moe_sum_reduce_kernel(
|
| 8 |
+
input_ptr,
|
| 9 |
+
input_stride_0,
|
| 10 |
+
input_stride_1,
|
| 11 |
+
input_stride_2,
|
| 12 |
+
output_ptr,
|
| 13 |
+
output_stride_0,
|
| 14 |
+
output_stride_1,
|
| 15 |
+
token_num: int,
|
| 16 |
+
topk_num: int,
|
| 17 |
+
hidden_dim: int,
|
| 18 |
+
routed_scaling_factor: tl.constexpr,
|
| 19 |
+
BLOCK_M: tl.constexpr,
|
| 20 |
+
BLOCK_DIM: tl.constexpr,
|
| 21 |
+
NUM_STAGE: tl.constexpr,
|
| 22 |
+
):
|
| 23 |
+
input_stride_0 = tl.cast(input_stride_0, dtype=tl.int64)
|
| 24 |
+
input_stride_1 = tl.cast(input_stride_1, dtype=tl.int64)
|
| 25 |
+
output_stride_0 = tl.cast(output_stride_0, dtype=tl.int64)
|
| 26 |
+
|
| 27 |
+
token_block_id = tl.program_id(0)
|
| 28 |
+
dim_block_id = tl.program_id(1)
|
| 29 |
+
|
| 30 |
+
token_start = token_block_id * BLOCK_M
|
| 31 |
+
token_end = min((token_block_id + 1) * BLOCK_M, token_num)
|
| 32 |
+
|
| 33 |
+
dim_start = dim_block_id * BLOCK_DIM
|
| 34 |
+
dim_end = min((dim_block_id + 1) * BLOCK_DIM, hidden_dim)
|
| 35 |
+
|
| 36 |
+
offs_dim = dim_start + tl.arange(0, BLOCK_DIM)
|
| 37 |
+
|
| 38 |
+
for token_index in range(token_start, token_end):
|
| 39 |
+
accumulator = tl.zeros((BLOCK_DIM,), dtype=tl.float32)
|
| 40 |
+
input_t_ptr = input_ptr + token_index * input_stride_0 + offs_dim
|
| 41 |
+
for i in tl.range(0, topk_num, num_stages=NUM_STAGE):
|
| 42 |
+
tmp = tl.load(input_t_ptr + i * input_stride_1, mask=offs_dim < dim_end, other=0.0)
|
| 43 |
+
accumulator += tmp
|
| 44 |
+
accumulator = accumulator * routed_scaling_factor
|
| 45 |
+
store_t_ptr = output_ptr + token_index * output_stride_0 + offs_dim
|
| 46 |
+
tl.store(
|
| 47 |
+
store_t_ptr,
|
| 48 |
+
accumulator.to(input_ptr.dtype.element_ty),
|
| 49 |
+
mask=offs_dim < dim_end,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@triton.jit
|
| 54 |
+
def fused_moe_kernel(
|
| 55 |
+
# Pointers to matrices
|
| 56 |
+
a_ptr,
|
| 57 |
+
b_ptr,
|
| 58 |
+
c_ptr,
|
| 59 |
+
topk_weights_ptr,
|
| 60 |
+
sorted_token_ids_ptr,
|
| 61 |
+
expert_ids_ptr,
|
| 62 |
+
num_tokens_post_padded_ptr,
|
| 63 |
+
# Matrix dimensions
|
| 64 |
+
N,
|
| 65 |
+
K,
|
| 66 |
+
EM,
|
| 67 |
+
num_valid_tokens,
|
| 68 |
+
# The stride variables represent how much to increase the ptr by when
|
| 69 |
+
# moving by 1 element in a particular dimension. E.g. `stride_am` is
|
| 70 |
+
# how much to increase `a_ptr` by to get the element one row down
|
| 71 |
+
# (A has M rows).
|
| 72 |
+
stride_am,
|
| 73 |
+
stride_ak,
|
| 74 |
+
stride_be,
|
| 75 |
+
stride_bk,
|
| 76 |
+
stride_bn,
|
| 77 |
+
stride_cm,
|
| 78 |
+
stride_cn,
|
| 79 |
+
# Meta-parameters
|
| 80 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 81 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 82 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 83 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 84 |
+
MUL_ROUTED_WEIGHT: tl.constexpr,
|
| 85 |
+
top_k: tl.constexpr,
|
| 86 |
+
compute_type: tl.constexpr,
|
| 87 |
+
even_Ks: tl.constexpr,
|
| 88 |
+
):
|
| 89 |
+
"""
|
| 90 |
+
Implements the fused computation for a Mixture of Experts (MOE) using
|
| 91 |
+
token and expert matrices.
|
| 92 |
+
|
| 93 |
+
Key Parameters:
|
| 94 |
+
- A: The input tensor representing tokens with shape (*, K), where '*' can
|
| 95 |
+
be any shape representing batches and K is the feature dimension of
|
| 96 |
+
each token.
|
| 97 |
+
- B: The stacked MOE weight tensor with shape (E, N, K), where E is
|
| 98 |
+
the number of experts, K is the input feature dimension, and N is
|
| 99 |
+
the output feature dimension.
|
| 100 |
+
- C: The output cache tensor with shape (M, topk, N), where M is the
|
| 101 |
+
total number of tokens post padding, topk is the number of times
|
| 102 |
+
each token is repeated, and N is the output feature dimension.
|
| 103 |
+
- sorted_token_ids: A tensor containing the sorted indices of tokens,
|
| 104 |
+
repeated topk times and arranged by the expert index they are
|
| 105 |
+
assigned to.
|
| 106 |
+
- expert_ids: A tensor containing the indices of the expert for each
|
| 107 |
+
block. It determines which expert matrix from B should be used for
|
| 108 |
+
each block in A.
|
| 109 |
+
|
| 110 |
+
This kernel performs the multiplication of a token by its corresponding
|
| 111 |
+
expert matrix as determined by `expert_ids`. The sorting of
|
| 112 |
+
`sorted_token_ids` by expert index and padding ensures divisibility by
|
| 113 |
+
BLOCK_SIZE_M, which is necessary to maintain consistency in block matrix
|
| 114 |
+
multiplication across different blocks processed by the same expert.
|
| 115 |
+
"""
|
| 116 |
+
# -----------------------------------------------------------
|
| 117 |
+
# Map program ids `pid` to the block of C it should compute.
|
| 118 |
+
# This is done in a grouped ordering to promote L2 data reuse.
|
| 119 |
+
pid = tl.program_id(axis=0)
|
| 120 |
+
num_pid_m = tl.cdiv(EM, BLOCK_SIZE_M)
|
| 121 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 122 |
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
| 123 |
+
group_id = pid // num_pid_in_group
|
| 124 |
+
first_pid_m = group_id * GROUP_SIZE_M
|
| 125 |
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
| 126 |
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
| 127 |
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
| 128 |
+
|
| 129 |
+
# ----------------------------------------------------------
|
| 130 |
+
# Create pointers for the first blocks of A and B.
|
| 131 |
+
# We will advance this pointer as we move in the K direction
|
| 132 |
+
# and accumulate
|
| 133 |
+
# `a_ptrs` is a block of [BLOCK_SIZE_M, BLOCK_SIZE_K] pointers
|
| 134 |
+
# `b_ptrs` is a block of [BLOCK_SIZE_K, BLOCK_SIZE_N] pointers
|
| 135 |
+
num_tokens_post_padded = tl.load(num_tokens_post_padded_ptr)
|
| 136 |
+
if pid_m * BLOCK_SIZE_M >= num_tokens_post_padded:
|
| 137 |
+
return
|
| 138 |
+
offs_token_id = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
| 139 |
+
offs_token = tl.load(sorted_token_ids_ptr + offs_token_id)
|
| 140 |
+
offs_token = offs_token.to(tl.int64)
|
| 141 |
+
token_mask = offs_token < num_valid_tokens
|
| 142 |
+
|
| 143 |
+
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N
|
| 144 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 145 |
+
a_ptrs = a_ptr + (offs_token[:, None] // top_k * stride_am + offs_k[None, :] * stride_ak)
|
| 146 |
+
|
| 147 |
+
off_experts = tl.load(expert_ids_ptr + pid_m)
|
| 148 |
+
b_ptrs = (
|
| 149 |
+
b_ptr
|
| 150 |
+
+ off_experts * stride_be
|
| 151 |
+
+ (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
# -----------------------------------------------------------
|
| 155 |
+
# Iterate to compute a block of the C matrix.
|
| 156 |
+
# We accumulate into a `[BLOCK_SIZE_M, BLOCK_SIZE_N]` block
|
| 157 |
+
# of fp32 values for higher accuracy.
|
| 158 |
+
# `accumulator` will be converted back to fp16 after the loop.
|
| 159 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 160 |
+
|
| 161 |
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 162 |
+
# Load the next block of A and B, generate a mask by checking the
|
| 163 |
+
# K dimension.
|
| 164 |
+
if even_Ks:
|
| 165 |
+
a = tl.load(
|
| 166 |
+
a_ptrs,
|
| 167 |
+
mask=token_mask[:, None],
|
| 168 |
+
other=0.0,
|
| 169 |
+
)
|
| 170 |
+
b = tl.load(b_ptrs)
|
| 171 |
+
else:
|
| 172 |
+
a = tl.load(
|
| 173 |
+
a_ptrs,
|
| 174 |
+
mask=token_mask[:, None] & (offs_k[None, :] < K - k * BLOCK_SIZE_K),
|
| 175 |
+
other=0.0,
|
| 176 |
+
)
|
| 177 |
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0)
|
| 178 |
+
|
| 179 |
+
# We accumulate along the K dimension.
|
| 180 |
+
|
| 181 |
+
accumulator += tl.dot(a, b)
|
| 182 |
+
# Advance the ptrs to the next K block.
|
| 183 |
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
| 184 |
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
| 185 |
+
|
| 186 |
+
if MUL_ROUTED_WEIGHT:
|
| 187 |
+
moe_weight = tl.load(topk_weights_ptr + offs_token, mask=token_mask, other=0)
|
| 188 |
+
accumulator = accumulator * moe_weight[:, None]
|
| 189 |
+
|
| 190 |
+
accumulator = accumulator.to(compute_type)
|
| 191 |
+
# -----------------------------------------------------------
|
| 192 |
+
# Write back the block of the output
|
| 193 |
+
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
| 194 |
+
c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_cn[None, :]
|
| 195 |
+
c_mask = token_mask[:, None] & (offs_cn[None, :] < N)
|
| 196 |
+
tl.store(c_ptrs, accumulator, mask=c_mask)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def moe_sum_reduce_triton(input: torch.Tensor, output: torch.Tensor, routed_scaling_factor: float):
|
| 200 |
+
assert input.is_contiguous()
|
| 201 |
+
assert output.is_contiguous()
|
| 202 |
+
|
| 203 |
+
token_num, topk_num, hidden_dim = input.shape
|
| 204 |
+
assert output.shape[0] == token_num and output.shape[1] == hidden_dim
|
| 205 |
+
|
| 206 |
+
BLOCK_M = 1
|
| 207 |
+
BLOCK_DIM = 2048
|
| 208 |
+
NUM_STAGE = 1
|
| 209 |
+
num_warps = 8
|
| 210 |
+
|
| 211 |
+
grid = (
|
| 212 |
+
triton.cdiv(token_num, BLOCK_M),
|
| 213 |
+
triton.cdiv(hidden_dim, BLOCK_DIM),
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
_moe_sum_reduce_kernel[grid](
|
| 217 |
+
input,
|
| 218 |
+
*input.stride(),
|
| 219 |
+
output,
|
| 220 |
+
*output.stride(),
|
| 221 |
+
token_num=token_num,
|
| 222 |
+
topk_num=topk_num,
|
| 223 |
+
hidden_dim=hidden_dim,
|
| 224 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 225 |
+
BLOCK_M=BLOCK_M,
|
| 226 |
+
BLOCK_DIM=BLOCK_DIM,
|
| 227 |
+
NUM_STAGE=NUM_STAGE,
|
| 228 |
+
num_warps=num_warps,
|
| 229 |
+
)
|
| 230 |
+
return
|
zonos2/kernel/utils.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import pathlib
|
| 4 |
+
from typing import TYPE_CHECKING, List, NamedTuple, Tuple, TypeAlias, Union
|
| 5 |
+
|
| 6 |
+
if TYPE_CHECKING:
|
| 7 |
+
from tvm_ffi import Module
|
| 8 |
+
|
| 9 |
+
KERNEL_PATH = pathlib.Path(__file__).parent / "csrc"
|
| 10 |
+
DEFAULT_INCLUDE = [str(KERNEL_PATH / "include")]
|
| 11 |
+
DEFAULT_CFLAGS = ["-std=c++20", "-O3"]
|
| 12 |
+
DEFAULT_CUDA_CFLAGS = ["-std=c++20", "-O3", "--expt-relaxed-constexpr"]
|
| 13 |
+
DEFAULT_LDFLAGS = []
|
| 14 |
+
CPP_TEMPLATE_TYPE: TypeAlias = Union[int, float, bool]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class CppArgList(list[str]):
|
| 18 |
+
def __str__(self) -> str:
|
| 19 |
+
return ", ".join(self)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class KernelConfig(NamedTuple):
|
| 23 |
+
num_threads: int
|
| 24 |
+
max_occupancy: int
|
| 25 |
+
use_pdl: bool
|
| 26 |
+
|
| 27 |
+
@property
|
| 28 |
+
def template_args(self) -> str:
|
| 29 |
+
pdl = "true" if self.use_pdl else "false"
|
| 30 |
+
return f"{self.num_threads},{self.max_occupancy},{pdl}"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _make_name(*args: str) -> str:
|
| 34 |
+
return "zonos2__" + "_".join(str(arg) for arg in args)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _make_wrapper(tup: Tuple[str, str]) -> str:
|
| 38 |
+
export_name, kernel_name = tup
|
| 39 |
+
return f"TVM_FFI_DLL_EXPORT_TYPED_FUNC({export_name}, ({kernel_name}));"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def make_cpp_args(*args: CPP_TEMPLATE_TYPE) -> CppArgList:
|
| 43 |
+
def _convert(arg: CPP_TEMPLATE_TYPE) -> str:
|
| 44 |
+
if isinstance(arg, bool):
|
| 45 |
+
return "true" if arg else "false"
|
| 46 |
+
if isinstance(arg, (int, float)):
|
| 47 |
+
return str(arg)
|
| 48 |
+
raise TypeError(f"Unsupported argument type for cpp template: {type(arg)}")
|
| 49 |
+
|
| 50 |
+
return CppArgList(_convert(arg) for arg in args)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def load_aot(
|
| 54 |
+
*args: str,
|
| 55 |
+
cpp_files: List[str] | None = None,
|
| 56 |
+
cuda_files: List[str] | None = None,
|
| 57 |
+
extra_cflags: List[str] | None = None,
|
| 58 |
+
extra_cuda_cflags: List[str] | None = None,
|
| 59 |
+
extra_ldflags: List[str] | None = None,
|
| 60 |
+
extra_include_paths: List[str] | None = None,
|
| 61 |
+
build_directory: str | None = None,
|
| 62 |
+
) -> Module:
|
| 63 |
+
from tvm_ffi.cpp import load
|
| 64 |
+
|
| 65 |
+
cpp_files = cpp_files or []
|
| 66 |
+
cuda_files = cuda_files or []
|
| 67 |
+
extra_cflags = extra_cflags or []
|
| 68 |
+
extra_cuda_cflags = extra_cuda_cflags or []
|
| 69 |
+
extra_ldflags = extra_ldflags or []
|
| 70 |
+
extra_include_paths = extra_include_paths or []
|
| 71 |
+
|
| 72 |
+
cpp_files = [str((KERNEL_PATH / "src" / f).resolve()) for f in cpp_files]
|
| 73 |
+
cuda_files = [str((KERNEL_PATH / "src" / f).resolve()) for f in cuda_files]
|
| 74 |
+
|
| 75 |
+
return load(
|
| 76 |
+
_make_name(*args),
|
| 77 |
+
cpp_files=cpp_files,
|
| 78 |
+
cuda_files=cuda_files,
|
| 79 |
+
extra_cflags=DEFAULT_CFLAGS + extra_cflags,
|
| 80 |
+
extra_cuda_cflags=DEFAULT_CUDA_CFLAGS + extra_cuda_cflags,
|
| 81 |
+
extra_ldflags=DEFAULT_LDFLAGS + extra_ldflags,
|
| 82 |
+
extra_include_paths=DEFAULT_INCLUDE + extra_include_paths,
|
| 83 |
+
build_directory=build_directory,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def load_jit(
|
| 88 |
+
*args: str,
|
| 89 |
+
cpp_files: List[str] | None = None,
|
| 90 |
+
cuda_files: List[str] | None = None,
|
| 91 |
+
cpp_wrappers: List[Tuple[str, str]] | None = None,
|
| 92 |
+
cuda_wrappers: List[Tuple[str, str]] | None = None,
|
| 93 |
+
extra_cflags: List[str] | None = None,
|
| 94 |
+
extra_cuda_cflags: List[str] | None = None,
|
| 95 |
+
extra_ldflags: List[str] | None = None,
|
| 96 |
+
extra_include_paths: List[str] | None = None,
|
| 97 |
+
build_directory: str | None = None,
|
| 98 |
+
) -> Module:
|
| 99 |
+
from tvm_ffi.cpp import load_inline
|
| 100 |
+
|
| 101 |
+
cpp_files = cpp_files or []
|
| 102 |
+
cuda_files = cuda_files or []
|
| 103 |
+
cpp_wrappers = cpp_wrappers or []
|
| 104 |
+
cuda_wrappers = cuda_wrappers or []
|
| 105 |
+
extra_cflags = extra_cflags or []
|
| 106 |
+
extra_cuda_cflags = extra_cuda_cflags or []
|
| 107 |
+
extra_ldflags = extra_ldflags or []
|
| 108 |
+
extra_include_paths = extra_include_paths or []
|
| 109 |
+
|
| 110 |
+
# include cpp files
|
| 111 |
+
cpp_paths = [(KERNEL_PATH / "jit" / f).resolve() for f in cpp_files]
|
| 112 |
+
cpp_sources = [f'#include "{path}"' for path in cpp_paths]
|
| 113 |
+
cpp_sources += [_make_wrapper(tup) for tup in cpp_wrappers]
|
| 114 |
+
|
| 115 |
+
# include cuda files
|
| 116 |
+
cuda_paths = [(KERNEL_PATH / "jit" / f).resolve() for f in cuda_files]
|
| 117 |
+
cuda_sources = [f'#include "{path}"' for path in cuda_paths]
|
| 118 |
+
cuda_sources += [_make_wrapper(tup) for tup in cuda_wrappers]
|
| 119 |
+
|
| 120 |
+
return load_inline(
|
| 121 |
+
_make_name(*args),
|
| 122 |
+
cpp_sources=cpp_sources,
|
| 123 |
+
cuda_sources=cuda_sources,
|
| 124 |
+
extra_cflags=DEFAULT_CFLAGS + extra_cflags,
|
| 125 |
+
extra_cuda_cflags=DEFAULT_CUDA_CFLAGS + extra_cuda_cflags,
|
| 126 |
+
extra_ldflags=DEFAULT_LDFLAGS + extra_ldflags,
|
| 127 |
+
extra_include_paths=DEFAULT_INCLUDE + extra_include_paths,
|
| 128 |
+
build_directory=build_directory,
|
| 129 |
+
)
|
zonos2/kvcache/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import TYPE_CHECKING, Protocol
|
| 4 |
+
|
| 5 |
+
from zonos2.utils import Registry
|
| 6 |
+
|
| 7 |
+
if TYPE_CHECKING:
|
| 8 |
+
import torch
|
| 9 |
+
from zonos2.models import ModelConfig
|
| 10 |
+
|
| 11 |
+
from .base import (
|
| 12 |
+
BaseCacheHandle,
|
| 13 |
+
BaseCacheManager,
|
| 14 |
+
BaseKVCache,
|
| 15 |
+
KVCacheLayout,
|
| 16 |
+
SizeInfo,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class CacheManagerCreator(Protocol):
|
| 21 |
+
def __call__(self, device: torch.device) -> BaseCacheManager: ...
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
SUPPORTED_CACHE_MANAGER = Registry[CacheManagerCreator]("Cache Manager")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def create_kvcache(
|
| 28 |
+
model_config: ModelConfig,
|
| 29 |
+
num_pages: int,
|
| 30 |
+
dtype: torch.dtype,
|
| 31 |
+
device: torch.device,
|
| 32 |
+
cache_layout: KVCacheLayout = KVCacheLayout.LayerFirst,
|
| 33 |
+
) -> BaseKVCache:
|
| 34 |
+
from .mha_pool import MHAKVCache # TODO: support other variants (e.g. MLA)
|
| 35 |
+
|
| 36 |
+
return MHAKVCache(
|
| 37 |
+
num_kv_heads=model_config.num_kv_heads,
|
| 38 |
+
num_pages=num_pages,
|
| 39 |
+
kv_layout=cache_layout,
|
| 40 |
+
num_layers=model_config.num_layers,
|
| 41 |
+
head_dim=model_config.head_dim,
|
| 42 |
+
device=device,
|
| 43 |
+
dtype=dtype,
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@SUPPORTED_CACHE_MANAGER.register("naive")
|
| 48 |
+
def create_naive_cache_manager(device: torch.device):
|
| 49 |
+
from .naive_manager import NaiveCacheManager
|
| 50 |
+
|
| 51 |
+
return NaiveCacheManager(device=device)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@SUPPORTED_CACHE_MANAGER.register("radix")
|
| 55 |
+
def create_radix_cache_manager(device: torch.device):
|
| 56 |
+
from .radix_manager import RadixCacheManager
|
| 57 |
+
|
| 58 |
+
return RadixCacheManager(device=device)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def create_cache_manager(device: torch.device, type: str) -> BaseCacheManager:
|
| 62 |
+
return SUPPORTED_CACHE_MANAGER[type](device)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
__all__ = [
|
| 66 |
+
"create_kvcache",
|
| 67 |
+
"create_cache_manager",
|
| 68 |
+
"BaseKVCache",
|
| 69 |
+
"KVCacheLayout",
|
| 70 |
+
"BaseCacheHandle",
|
| 71 |
+
"BaseCacheManager",
|
| 72 |
+
"SizeInfo",
|
| 73 |
+
"SUPPORTED_CACHE_MANAGER",
|
| 74 |
+
]
|
zonos2/kvcache/base.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import enum
|
| 4 |
+
from abc import ABC, abstractmethod
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import NamedTuple, Tuple
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class BaseKVCache(ABC):
|
| 12 |
+
"""
|
| 13 |
+
Base class for key-value caches.
|
| 14 |
+
This class defines the interface for key-value caches used.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
@abstractmethod
|
| 18 |
+
def k_cache(self, index: int) -> torch.Tensor: ...
|
| 19 |
+
|
| 20 |
+
@abstractmethod
|
| 21 |
+
def v_cache(self, index: int) -> torch.Tensor: ...
|
| 22 |
+
|
| 23 |
+
@abstractmethod
|
| 24 |
+
def store_kv(
|
| 25 |
+
self, k: torch.Tensor, v: torch.Tensor, out_loc: torch.Tensor, layer_id: int
|
| 26 |
+
) -> None: ...
|
| 27 |
+
|
| 28 |
+
@property
|
| 29 |
+
@abstractmethod
|
| 30 |
+
def device(self) -> torch.device: ...
|
| 31 |
+
|
| 32 |
+
@property
|
| 33 |
+
@abstractmethod
|
| 34 |
+
def dtype(self) -> torch.dtype: ...
|
| 35 |
+
|
| 36 |
+
@property
|
| 37 |
+
@abstractmethod
|
| 38 |
+
def num_layers(self) -> int: ...
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class KVCacheLayout(enum.Enum):
|
| 42 |
+
LayerFirst = enum.auto()
|
| 43 |
+
PageFirst = enum.auto()
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@dataclass(frozen=True)
|
| 47 |
+
class BaseCacheHandle(ABC):
|
| 48 |
+
cached_len: int
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class SizeInfo(NamedTuple):
|
| 52 |
+
evictable_size: int
|
| 53 |
+
protected_size: int
|
| 54 |
+
|
| 55 |
+
@property
|
| 56 |
+
def total_size(self) -> int:
|
| 57 |
+
return self.evictable_size + self.protected_size
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class BaseCacheManager(ABC):
|
| 61 |
+
@abstractmethod
|
| 62 |
+
def match_prefix(self, input_ids: torch.Tensor) -> Tuple[BaseCacheHandle, torch.Tensor]:
|
| 63 |
+
"""
|
| 64 |
+
Match prefix and return the indices of the matched prefix in the cache.
|
| 65 |
+
This operation will not modify the cache.
|
| 66 |
+
The returned indices is only safe to use when the handle is locked.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
input_ids (torch.Tensor): The input ids to match. Shape: (seq_len,)
|
| 70 |
+
Returns:
|
| 71 |
+
handle (BaseCacheHandle): The handle to the matched prefix.
|
| 72 |
+
indices (torch.Tensor): The indices of the longest-matched prefix in the cache.
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
@abstractmethod
|
| 76 |
+
def lock_handle(self, handle: BaseCacheHandle, unlock: bool = False) -> None:
|
| 77 |
+
"""
|
| 78 |
+
Lock or unlock a cache handle.
|
| 79 |
+
This operation will not modify the cache, but change the size info only.
|
| 80 |
+
When a handle is locked, it cannot be evicted.
|
| 81 |
+
Handles must be locked before the previously-returned tensor of `match_prefix` is used.
|
| 82 |
+
Otherwise it may be evicted by calling evict.
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
handle (BaseCacheHandle): The cache handle to lock or unlock.
|
| 86 |
+
unlock (bool): Whether to unlock the handle. Defaults to False.
|
| 87 |
+
"""
|
| 88 |
+
|
| 89 |
+
@abstractmethod
|
| 90 |
+
def insert_prefix(self, input_ids: torch.Tensor, indices: torch.Tensor) -> int:
|
| 91 |
+
"""
|
| 92 |
+
Insert a new prefix into the cache.
|
| 93 |
+
This operation will modify the cache.
|
| 94 |
+
Args:
|
| 95 |
+
input_ids (torch.Tensor): The input ids to insert. Shape: (seq_len,)
|
| 96 |
+
indices (torch.Tensor): The indices to store the new prefix. Shape: (seq_len,)
|
| 97 |
+
|
| 98 |
+
Returns:
|
| 99 |
+
int: The length of prefix that is already in the cache. This part is not
|
| 100 |
+
inserted, so the caller should free these indices.
|
| 101 |
+
"""
|
| 102 |
+
|
| 103 |
+
@abstractmethod
|
| 104 |
+
def evict(self, size: int) -> torch.Tensor:
|
| 105 |
+
"""
|
| 106 |
+
Evict some prefixes from the cache to free up space.
|
| 107 |
+
This operation will modify the cache.
|
| 108 |
+
Note that evict 0 is always safe and does nothing.
|
| 109 |
+
Note that the actual evict size may be larger than the requested size.
|
| 110 |
+
Args:
|
| 111 |
+
size (int): The size to evict.
|
| 112 |
+
|
| 113 |
+
Returns:
|
| 114 |
+
torch.Tensor: The indices evicted. Shape: (evict_size,)
|
| 115 |
+
Raises:
|
| 116 |
+
RuntimeError: If the requested size is larger than the evictable size.
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
@abstractmethod
|
| 120 |
+
def reset(self) -> None:
|
| 121 |
+
"""Reset the cache manager and the underlying cache."""
|
| 122 |
+
|
| 123 |
+
@property
|
| 124 |
+
@abstractmethod
|
| 125 |
+
def size_info(self) -> SizeInfo:
|
| 126 |
+
"""Get the size information of the cache."""
|
| 127 |
+
|
| 128 |
+
@abstractmethod
|
| 129 |
+
def check_integrity(self) -> None:
|
| 130 |
+
"""Check the integrity of the cache. Raise an error if the cache is corrupted."""
|
zonos2/kvcache/mha_pool.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from zonos2.distributed import get_tp_info
|
| 5 |
+
from zonos2.utils import divide_even
|
| 6 |
+
|
| 7 |
+
from .base import BaseKVCache, KVCacheLayout
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class MHAKVCache(BaseKVCache):
|
| 11 |
+
"""
|
| 12 |
+
Base class for key-value caches.
|
| 13 |
+
This class defines the interface for key-value caches used in LLMs.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
num_kv_heads: int,
|
| 19 |
+
num_layers: int,
|
| 20 |
+
head_dim: int,
|
| 21 |
+
num_pages: int,
|
| 22 |
+
dtype: torch.dtype,
|
| 23 |
+
kv_layout: KVCacheLayout,
|
| 24 |
+
device: torch.device,
|
| 25 |
+
):
|
| 26 |
+
tp_info = get_tp_info()
|
| 27 |
+
local_kv_heads = divide_even(num_kv_heads, tp_info.size)
|
| 28 |
+
match kv_layout:
|
| 29 |
+
case KVCacheLayout.PageFirst:
|
| 30 |
+
kv_buffer = torch.empty(
|
| 31 |
+
(2, num_pages, num_layers, local_kv_heads, head_dim),
|
| 32 |
+
device=device,
|
| 33 |
+
dtype=dtype,
|
| 34 |
+
).permute(0, 2, 1, 3, 4)
|
| 35 |
+
case KVCacheLayout.LayerFirst:
|
| 36 |
+
kv_buffer = torch.empty(
|
| 37 |
+
(2, num_layers, num_pages, local_kv_heads, head_dim),
|
| 38 |
+
device=device,
|
| 39 |
+
dtype=dtype,
|
| 40 |
+
)
|
| 41 |
+
case _:
|
| 42 |
+
raise ValueError(f"Unsupported kv_layout: {kv_layout}")
|
| 43 |
+
self._kv_buffer = kv_buffer.view(2, num_layers, num_pages, 1, local_kv_heads, head_dim)
|
| 44 |
+
self._num_layers = num_layers
|
| 45 |
+
self._k_buffer = self._kv_buffer[0]
|
| 46 |
+
self._v_buffer = self._kv_buffer[1]
|
| 47 |
+
self._device = device
|
| 48 |
+
self._storage_shape = (num_pages, local_kv_heads, head_dim)
|
| 49 |
+
|
| 50 |
+
def k_cache(self, index: int) -> torch.Tensor:
|
| 51 |
+
return self._k_buffer[index]
|
| 52 |
+
|
| 53 |
+
def v_cache(self, index: int) -> torch.Tensor:
|
| 54 |
+
return self._v_buffer[index]
|
| 55 |
+
|
| 56 |
+
def store_kv(
|
| 57 |
+
self, k: torch.Tensor, v: torch.Tensor, out_loc: torch.Tensor, layer_id: int
|
| 58 |
+
) -> None:
|
| 59 |
+
from zonos2.kernel import store_cache
|
| 60 |
+
|
| 61 |
+
store_cache(
|
| 62 |
+
k_cache=self._k_buffer[layer_id].view(self._storage_shape),
|
| 63 |
+
v_cache=self._v_buffer[layer_id].view(self._storage_shape),
|
| 64 |
+
indices=out_loc,
|
| 65 |
+
k=k,
|
| 66 |
+
v=v,
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
@property
|
| 70 |
+
def device(self) -> torch.device:
|
| 71 |
+
return self._device
|
| 72 |
+
|
| 73 |
+
@property
|
| 74 |
+
def dtype(self) -> torch.dtype:
|
| 75 |
+
return self._kv_buffer.dtype
|
| 76 |
+
|
| 77 |
+
@property
|
| 78 |
+
def num_layers(self) -> int:
|
| 79 |
+
return self._num_layers
|
zonos2/kvcache/naive_manager.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Tuple
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from .base import BaseCacheHandle, BaseCacheManager, SizeInfo
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class NaiveCacheHandle(BaseCacheHandle):
|
| 11 |
+
pass
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class NaiveCacheManager(BaseCacheManager):
|
| 15 |
+
def __init__(self, device: torch.device):
|
| 16 |
+
self.device = device
|
| 17 |
+
self.empty_tensor = torch.empty(0, dtype=torch.int32, device=device)
|
| 18 |
+
super().__init__()
|
| 19 |
+
|
| 20 |
+
def match_prefix(self, input_ids: torch.Tensor) -> Tuple[NaiveCacheHandle, torch.Tensor]:
|
| 21 |
+
_ = input_ids # unused
|
| 22 |
+
return NaiveCacheHandle(0), self.empty_tensor
|
| 23 |
+
|
| 24 |
+
def lock_handle(self, handle: BaseCacheHandle, unlock: bool = False) -> None:
|
| 25 |
+
_ = handle, unlock # unused
|
| 26 |
+
|
| 27 |
+
def insert_prefix(self, input_ids: torch.Tensor, indices: torch.Tensor) -> int:
|
| 28 |
+
assert len(indices) == len(input_ids)
|
| 29 |
+
return len(indices)
|
| 30 |
+
|
| 31 |
+
def evict(self, size: int) -> torch.Tensor:
|
| 32 |
+
if size == 0:
|
| 33 |
+
return self.empty_tensor
|
| 34 |
+
raise NotImplementedError("NaiveCacheManager does not support eviction.")
|
| 35 |
+
|
| 36 |
+
def reset(self) -> None:
|
| 37 |
+
pass
|
| 38 |
+
|
| 39 |
+
@property
|
| 40 |
+
def size_info(self) -> SizeInfo:
|
| 41 |
+
return SizeInfo(evictable_size=0, protected_size=0)
|
| 42 |
+
|
| 43 |
+
def check_integrity(self) -> None:
|
| 44 |
+
pass
|
zonos2/kvcache/radix_manager.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import heapq
|
| 4 |
+
import time
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Dict, List, Tuple
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from .base import BaseCacheHandle, BaseCacheManager, SizeInfo
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class RadixTreeNode:
|
| 14 |
+
counter: int = 0
|
| 15 |
+
|
| 16 |
+
def __init__(self, tic: int | None = None) -> None:
|
| 17 |
+
self.children: Dict[int, RadixTreeNode] = {}
|
| 18 |
+
self._parent: RadixTreeNode | None = None
|
| 19 |
+
self.ref_count: int = 0
|
| 20 |
+
self.uuid = RadixTreeNode.counter
|
| 21 |
+
RadixTreeNode.counter += 1
|
| 22 |
+
self.timestamp = tic or time.monotonic_ns()
|
| 23 |
+
|
| 24 |
+
# these fields should be updated later
|
| 25 |
+
self._key: torch.Tensor
|
| 26 |
+
self._value: torch.Tensor
|
| 27 |
+
self._length: int
|
| 28 |
+
|
| 29 |
+
def set_key_value(self, key: torch.Tensor, value: torch.Tensor) -> None:
|
| 30 |
+
assert len(key) == len(value)
|
| 31 |
+
self._key = key
|
| 32 |
+
self._value = value
|
| 33 |
+
self._length = len(key)
|
| 34 |
+
|
| 35 |
+
def set_parent(self, parent: RadixTreeNode) -> None:
|
| 36 |
+
self._parent = parent
|
| 37 |
+
parent.children[int(self._key[0].item())] = self
|
| 38 |
+
|
| 39 |
+
@property
|
| 40 |
+
def length(self) -> int:
|
| 41 |
+
return self._length
|
| 42 |
+
|
| 43 |
+
@property
|
| 44 |
+
def parent(self) -> RadixTreeNode:
|
| 45 |
+
assert self._parent is not None
|
| 46 |
+
return self._parent
|
| 47 |
+
|
| 48 |
+
@property
|
| 49 |
+
def value(self) -> torch.Tensor:
|
| 50 |
+
return self._value
|
| 51 |
+
|
| 52 |
+
def is_root(self) -> bool:
|
| 53 |
+
return self._parent is None
|
| 54 |
+
|
| 55 |
+
def is_leaf(self) -> bool:
|
| 56 |
+
return len(self.children) == 0
|
| 57 |
+
|
| 58 |
+
def get_match_len(self, input_ids: torch.Tensor) -> int:
|
| 59 |
+
from zonos2.kernel import fast_compare_key
|
| 60 |
+
|
| 61 |
+
# compare key and input_ids, find the first diff
|
| 62 |
+
return fast_compare_key(self._key, input_ids)
|
| 63 |
+
|
| 64 |
+
def _split_at(self, pos: int) -> RadixTreeNode:
|
| 65 |
+
assert 0 < pos < self.length
|
| 66 |
+
parent = self.parent
|
| 67 |
+
|
| 68 |
+
new_node = RadixTreeNode(self.timestamp)
|
| 69 |
+
new_node.set_key_value(self._key[:pos], self._value[:pos])
|
| 70 |
+
new_node.set_parent(parent)
|
| 71 |
+
new_node.ref_count = self.ref_count
|
| 72 |
+
|
| 73 |
+
self.set_key_value(self._key[pos:], self._value[pos:])
|
| 74 |
+
self.set_parent(new_node)
|
| 75 |
+
|
| 76 |
+
return new_node
|
| 77 |
+
|
| 78 |
+
def __lt__(self, other: RadixTreeNode) -> bool:
|
| 79 |
+
return self.timestamp < other.timestamp
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@dataclass(frozen=True)
|
| 83 |
+
class RadixCacheHandle(BaseCacheHandle):
|
| 84 |
+
node: RadixTreeNode
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class RadixCacheManager(BaseCacheManager):
|
| 88 |
+
def __init__(self, device: torch.device):
|
| 89 |
+
self.device = device
|
| 90 |
+
self.empty_tensor = torch.empty(0, dtype=torch.int32, device=device)
|
| 91 |
+
super().__init__()
|
| 92 |
+
self.root_node = RadixTreeNode()
|
| 93 |
+
self.root_node.ref_count = 1 # root is always protected
|
| 94 |
+
self.evictable_size = 0
|
| 95 |
+
self.protected_size = 0
|
| 96 |
+
|
| 97 |
+
def lock_handle(self, handle: BaseCacheHandle, unlock: bool = False) -> None:
|
| 98 |
+
assert isinstance(handle, RadixCacheHandle)
|
| 99 |
+
node = handle.node
|
| 100 |
+
if unlock:
|
| 101 |
+
while not node.is_root():
|
| 102 |
+
node.ref_count -= 1
|
| 103 |
+
assert node.ref_count >= 0
|
| 104 |
+
if node.ref_count == 0:
|
| 105 |
+
self.evictable_size += node.length
|
| 106 |
+
self.protected_size -= node.length
|
| 107 |
+
node = node.parent
|
| 108 |
+
else:
|
| 109 |
+
while not node.is_root():
|
| 110 |
+
if node.ref_count == 0:
|
| 111 |
+
self.evictable_size -= node.length
|
| 112 |
+
self.protected_size += node.length
|
| 113 |
+
node.ref_count += 1
|
| 114 |
+
node = node.parent
|
| 115 |
+
|
| 116 |
+
def match_prefix(self, input_ids: torch.Tensor) -> Tuple[RadixCacheHandle, torch.Tensor]:
|
| 117 |
+
node, prefix_len = self._walk(input_ids)
|
| 118 |
+
if prefix_len == 0:
|
| 119 |
+
assert node.is_root() and node is self.root_node and prefix_len == 0
|
| 120 |
+
return RadixCacheHandle(prefix_len, node), self.empty_tensor
|
| 121 |
+
value_list: List[torch.Tensor] = []
|
| 122 |
+
matched_node = node
|
| 123 |
+
while not node.is_root():
|
| 124 |
+
value_list.append(node.value)
|
| 125 |
+
node = node.parent
|
| 126 |
+
value_list.reverse()
|
| 127 |
+
return RadixCacheHandle(prefix_len, matched_node), torch.cat(value_list)
|
| 128 |
+
|
| 129 |
+
def insert_prefix(self, input_ids: torch.Tensor, indices: torch.Tensor) -> int:
|
| 130 |
+
node, prefix_len = self._walk(input_ids)
|
| 131 |
+
assert prefix_len <= len(input_ids)
|
| 132 |
+
if prefix_len < len(input_ids):
|
| 133 |
+
new_node = RadixTreeNode()
|
| 134 |
+
new_node.set_key_value(input_ids[prefix_len:], indices[prefix_len:])
|
| 135 |
+
new_node.set_parent(node)
|
| 136 |
+
self.evictable_size += new_node.length
|
| 137 |
+
return prefix_len
|
| 138 |
+
|
| 139 |
+
def _walk(self, input_ids: torch.Tensor) -> Tuple[RadixTreeNode, int]:
|
| 140 |
+
prefix_len = 0
|
| 141 |
+
indice_len = len(input_ids)
|
| 142 |
+
node = self.root_node
|
| 143 |
+
tic = time.monotonic_ns()
|
| 144 |
+
|
| 145 |
+
while prefix_len < indice_len:
|
| 146 |
+
this_id = int(input_ids[prefix_len].item())
|
| 147 |
+
if this_id not in node.children:
|
| 148 |
+
return node, prefix_len
|
| 149 |
+
|
| 150 |
+
node = node.children[this_id]
|
| 151 |
+
|
| 152 |
+
# NOTE: at least 1 char is matched, so match_len >= 1
|
| 153 |
+
match_len = node.get_match_len(input_ids[prefix_len:])
|
| 154 |
+
prefix_len += match_len
|
| 155 |
+
|
| 156 |
+
# need to split the node if not fully matched
|
| 157 |
+
if match_len != node.length:
|
| 158 |
+
node = node._split_at(match_len)
|
| 159 |
+
return node, prefix_len
|
| 160 |
+
|
| 161 |
+
# update timestamp for accessed node
|
| 162 |
+
node.timestamp = tic
|
| 163 |
+
|
| 164 |
+
return node, prefix_len
|
| 165 |
+
|
| 166 |
+
def evict(self, size: int) -> torch.Tensor:
|
| 167 |
+
if size == 0:
|
| 168 |
+
return self.empty_tensor
|
| 169 |
+
assert (
|
| 170 |
+
size <= self.evictable_size
|
| 171 |
+
), f"Cannot evict {size}, only {self.evictable_size} is evictable"
|
| 172 |
+
|
| 173 |
+
leave_nodes = self._collect_leave_nodes_for_evict()
|
| 174 |
+
heapq.heapify(leave_nodes)
|
| 175 |
+
evicted_indices: List[torch.Tensor] = []
|
| 176 |
+
evicted_size = 0
|
| 177 |
+
|
| 178 |
+
while evicted_size < size:
|
| 179 |
+
assert (
|
| 180 |
+
leave_nodes
|
| 181 |
+
), f"Cannot evict enough cache, need {size}, only {evicted_size} evicted"
|
| 182 |
+
node = heapq.heappop(leave_nodes)
|
| 183 |
+
assert node.ref_count == 0 and node.is_leaf() and not node.is_root()
|
| 184 |
+
evicted_size += node.length
|
| 185 |
+
evicted_indices.append(node.value)
|
| 186 |
+
self.evictable_size -= node.length
|
| 187 |
+
parent = node.parent
|
| 188 |
+
del parent.children[int(node._key[0].item())]
|
| 189 |
+
# NOTE: root is always protected, so won't be evicted
|
| 190 |
+
if parent.is_leaf() and parent.ref_count == 0:
|
| 191 |
+
heapq.heappush(leave_nodes, parent)
|
| 192 |
+
|
| 193 |
+
return torch.cat(evicted_indices)
|
| 194 |
+
|
| 195 |
+
def _collect_leave_nodes_for_evict(self) -> List[RadixTreeNode]:
|
| 196 |
+
nodes: List[RadixTreeNode] = [self.root_node]
|
| 197 |
+
leave_nodes: List[RadixTreeNode] = []
|
| 198 |
+
|
| 199 |
+
while len(nodes) > 0:
|
| 200 |
+
node = nodes.pop()
|
| 201 |
+
if node.is_leaf():
|
| 202 |
+
if node.ref_count == 0:
|
| 203 |
+
leave_nodes.append(node)
|
| 204 |
+
else:
|
| 205 |
+
for child in node.children.values():
|
| 206 |
+
nodes.append(child)
|
| 207 |
+
|
| 208 |
+
return leave_nodes
|
| 209 |
+
|
| 210 |
+
def reset(self) -> None:
|
| 211 |
+
raise NotImplementedError("RadixManager.reset is not implemented")
|
| 212 |
+
|
| 213 |
+
@property
|
| 214 |
+
def size_info(self) -> SizeInfo:
|
| 215 |
+
return SizeInfo(
|
| 216 |
+
evictable_size=self.evictable_size,
|
| 217 |
+
protected_size=self.protected_size,
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
def check_integrity(self) -> None:
|
| 221 |
+
pass
|
zonos2/layers/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .activation import silu_and_mul
|
| 2 |
+
from .attention import AttentionLayer
|
| 3 |
+
from .base import BaseOP, OPList, StateLessOP
|
| 4 |
+
from .embedding import ParallelLMHead, VocabParallelEmbedding
|
| 5 |
+
from .linear import (
|
| 6 |
+
ChunkedLinear,
|
| 7 |
+
LinearColParallelMerged,
|
| 8 |
+
LinearOProj,
|
| 9 |
+
LinearQKVMerged,
|
| 10 |
+
LinearRowParallel,
|
| 11 |
+
)
|
| 12 |
+
from .norm import RMSNorm, RMSNormFused
|
| 13 |
+
from .rotary import get_rope, set_rope_device
|
| 14 |
+
|
| 15 |
+
__all__ = [
|
| 16 |
+
"silu_and_mul",
|
| 17 |
+
"AttentionLayer",
|
| 18 |
+
"BaseOP",
|
| 19 |
+
"StateLessOP",
|
| 20 |
+
"OPList",
|
| 21 |
+
"VocabParallelEmbedding",
|
| 22 |
+
"ParallelLMHead",
|
| 23 |
+
"ChunkedLinear",
|
| 24 |
+
"LinearColParallelMerged",
|
| 25 |
+
"LinearRowParallel",
|
| 26 |
+
"LinearOProj",
|
| 27 |
+
"LinearQKVMerged",
|
| 28 |
+
"RMSNorm",
|
| 29 |
+
"RMSNormFused",
|
| 30 |
+
"get_rope",
|
| 31 |
+
"set_rope_device",
|
| 32 |
+
]
|
zonos2/layers/activation.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import TYPE_CHECKING
|
| 4 |
+
|
| 5 |
+
if TYPE_CHECKING:
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def silu_and_mul(x: torch.Tensor) -> torch.Tensor:
|
| 10 |
+
from flashinfer import silu_and_mul
|
| 11 |
+
|
| 12 |
+
return silu_and_mul(x)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
__all__ = ["silu_and_mul"]
|