Views
No views yet
tepirale/Ornith-Agents-A1-3.6-35B-A3B-dare_ties, a dare_ties merge on top of Qwen3.5-35B-A3B (MoE), with a Multi-Token Prediction (MTP) block grafted in from the official Qwen3.6-35B-A3B GGUF. This lets the model use its own MTP block as a draft model for speculative decoding, with no need for a separate external draft model.💡 TL;DR: download a quant + the MTP block, launchllama-serverwith--spec-type draft-mtp, and get native speculative decoding with ~45% draft acceptance and up to ~260 tok/s measured on an H100 (25 GB VRAM in use). GPU NVIDIA RTX 6000 Ada Generation 96GB V-RAM
| Quant | Size | Draft head (MTP) precision | Recommended use |
|---|---|---|---|
Q4_K_M | 20.5 GiB | Q8_0 | General use / limited VRAM |
Q5_K_M | 23.9 GiB | Q8_0 | Quality/size balance |
Q6_K | 27.4 GiB | Q8_0 | High fidelity |
Q8_0 | 35.2 GiB | Q8_0 | Maximum fidelity |
f16 | 69.4 GiB | F16 | Reference / debugging |
blk.40). Also distributed separately:Ornith-Agents-A1-3.6-35B-A3B-dare_ties-mtp-sidecar.gguf — the raw MTP block, extracted from the official Qwen3.6-35B-A3B GGUF, for anyone who wants to graft it onto other quants or custom builds.block_count=41, nextn_predict_layers=1 (the MTP block is added as layer 41, on top of the base model's 40 layers).1apt-get update && apt-get install -y build-essential cmake git libcurl4-openssl-dev
2pip install -U -q openai
3git clone https://github.com/ggml-org/llama.cpp
4pip install -U --force-reinstall "huggingface_hub[hf_transfer]"
5
6cd llama.cpp
7cmake -B build \
8 -DGGML_CUDA=ON \
9 -DCMAKE_CUDA_ARCHITECTURES=90 \
10 -DCMAKE_BUILD_TYPE=Release
11cmake --build build --config Release -j$(nproc) \
12 --target llama-server llama-cli llama-quantize llama-gguf-splitAdjustCMAKE_CUDA_ARCHITECTURESfor your GPU (90 = Hopper/H100; use 89 for Ada/L40S, 86 for Ampere consumer, etc.).
1hf download tepirale/Ornith-Agents-A1-3.6-35B-A3B-MTP-GGUF \
2 --include "Ornith-Agents-A1-3.6-35B-A3B-dare_ties-Q4_K_M.gguf" \
3 --include "Ornith-Agents-A1-3.6-35B-A3B-dare_ties-mtp-sidecar.gguf" \
4 --include "mmproj-F32.gguf" \
5 --local-dir ./models/ornith-a11# configuration with 250 tok/s acceptable and depending on (spec-draft-n-max)
2./build/bin/llama-server \
3 -m ./models/ornith-a1/Ornith-Agents-A1-3.6-35B-A3B-dare_ties-Q4_K_M.gguf \
4 --spec-type draft-mtp \
5 --spec-draft-n-max 4 \
6 --spec-draft-n-min 1 \
7 --host 0.0.0.0 --port 8888 \
8 --n-gpu-layers 999 \
9 --ctx-size 131072 \
10 --flash-attn on \
11 --cont-batching \
12 --parallel 4 \
13 --cache-type-k q8_0 \
14 --cache-type-v q8_0 \
15 --metrics1# configuration with 190 tok/s but gives better results (more VRAM)
2./build/bin/llama-server \
3 -m ./models/ornith-a1/Ornith-Agents-A1-3.6-35B-A3B-dare_ties-Q8_0.gguf \
4 --spec-type draft-mtp \
5 --spec-draft-n-max 3 \
6 --spec-draft-n-min 1 \
7 --spec-draft-p-min 0.92 \
8 --host 0.0.0.0 --port 8888 \
9 --n-gpu-layers 999 \
10 --ctx-size 254000 \
11 --flash-attn on \
12 --cont-batching \
13 --parallel 4 \
14 --batch-size 4096 \
15 --ubatch-size 1024 \
16 --reasoning-preserve \
17 --metrics1# MTP DISABLED | 150-200 tok/s
2./build/bin/llama-server \
3 -m ./models/ornith-a1/Ornith-Agents-A1-3.6-35B-A3B-dare_ties-Q8_0.gguf \
4 --host 0.0.0.0 --port 8888 \
5 --n-gpu-layers 999 \
6 --ctx-size 254000 \
7 --flash-attn on \
8 --cont-batching \
9 --parallel 4 \
10 --batch-size 8192 \
11 --ubatch-size 1024 \
12 --reasoning-preserve \
13 --temp 0.7 \
14 --top-p 0.95 \
15 --top-k 20 \
16 --min-p 0.0 \
17 --metrics1# mtp act - inference image act
2
3# mmproj-F32.gguf -> https://huggingface.co/Jackrong/Qwopus3.6-35B-A3B-Coder-MTP-GGUF/tree/main
4
5./build/bin/llama-server \
6 -m ./models/ornith-a1/Ornith-Agents-A1-3.6-35B-A3B-dare_ties-Q4_K_M.gguf \
7 --mmproj /models/ornith-a1/mmproj-F32.gguf \
8 --image-min-tokens 1024 \
9 --chat-template-file /content/chat_template.jinja \
10 --reasoning-preserve \
11 --spec-type draft-mtp \
12 --spec-draft-n-max 3 \
13 --spec-draft-n-min 1 \
14 --host 0.0.0.0 --port 8080 \
15 --n-gpu-layers 999 \
16 --ctx-size 5000 \
17 --flash-attn on \
18 --cont-batching \
19 --parallel 4 \
20 --cache-type-k q8_0 \
21 --cache-type-v q8_0 \
22 --metrics--spec-type draft-mtp: enables the internal MTP block as the draft model (no need for -md with an external model) | Enabling MTP uses ~2GB..--spec-draft-n-min/max: range of draft tokens per speculative step.--cache-type-k/v q8_0: quantizes the KV cache to reduce VRAM usage with long context (131k tokens).http://localhost:8888/v1, so any client based on openai / AsyncOpenAI works out of the box. See the benchmarking section below for a full example with tok/s metrics and MTP draft acceptance rate.1import asyncio
2import time
3from openai import AsyncOpenAI
4
5client = AsyncOpenAI(
6 base_url="http://localhost:8888/v1",
7 api_key="sk-no-key-required"
8)
9
10MODEL = "ornith-a1"
11
12# --- Prompts escalados por tamaño de max_tokens ---
13# Cada nivel pide más subtemas/profundidad para forzar que el modelo
14# necesite ese espacio y no termine antes de tiempo (finish_reason='stop' prematuro).
15
16def build_prompt(max_tokens: int) -> str:
17 # Escala el número de subtemas según el presupuesto de tokens.
18 # ~1 subtema profundo consume aprox 250-350 tokens en español con razonamiento incluido.
19 n_subtemas = max(2, round(max_tokens / 300))
20
21 subtemas_pool = [
22 "la formulación matemática de DARE (Drop And REscale) y su relación con task_arithmetic",
23 "cómo TIES resuelve conflictos de signo entre parámetros de distintos modelos fuente",
24 "ventajas de dare_ties frente a un merge lineal simple (linear merge)",
25 "desventajas y casos donde dare_ties degrada el rendimiento del modelo resultante",
26 "el rol del parámetro de densidad (density) en la retención de pesos",
27 "cómo afecta el número de modelos fuente combinados a la estabilidad del merge",
28 "comparación entre dare_ties y model soups en términos de generalización",
29 "el impacto de mergear modelos con arquitecturas MoE como Qwen3.6-A3B",
30 "estrategias de evaluación post-merge (benchmarks recomendados y por qué)",
31 "buenas prácticas al elegir los pesos de mezcla (weight) por modelo fuente",
32 "cómo dare_ties interactúa con fine-tuning posterior (SFT/GRPO) del modelo mergeado",
33 "riesgos de catastrophic forgetting al combinar modelos con dominios muy distintos",
34 ]
35
36 subtemas = subtemas_pool[:n_subtemas] if n_subtemas <= len(subtemas_pool) else (
37 subtemas_pool * (n_subtemas // len(subtemas_pool) + 1)
38 )[:n_subtemas]
39
40 lista = "\n".join(f"{i+1}. {t}" for i, t in enumerate(subtemas))
41
42 return (
43 f"Escribe una explicación técnica exhaustiva sobre model merging con la técnica dare_ties, "
44 f"cubriendo en detalle y con profundidad cada uno de los siguientes {n_subtemas} puntos "
45 f"(desarrolla cada uno en varios párrafos, con ejemplos y justificación técnica):\n\n{lista}\n\n"
46 f"No resumas ni omitas puntos. Desarrolla cada sección completamente."
47 )
48
49
50async def run_test(max_tokens: int) -> dict:
51 prompt = build_prompt(max_tokens)
52
53 start = time.perf_counter()
54 response = await client.chat.completions.create(
55 model=MODEL,
56 messages=[
57 {"role": "system", "content": "Eres un experto en machine learning que explica con profundidad técnica."},
58 {"role": "user", "content": prompt}
59 ],
60 temperature=0.7,
61 max_tokens=max_tokens,
62 extra_body={"chat_template_kwargs": {"enable_thinking": True}}
63 )
64 elapsed = time.perf_counter() - start
65
66 usage = response.usage
67 raw = response.model_dump()
68 timings = raw.get("timings", {})
69
70 return {
71 "max_tokens": max_tokens,
72 "prompt_tokens": usage.prompt_tokens,
73 "completion_tokens": usage.completion_tokens,
74 "elapsed_s": round(elapsed, 2),
75 "tok_s_medido": round(usage.completion_tokens / elapsed, 2) if elapsed > 0 else 0,
76 "tok_s_nativo": timings.get("predicted_per_second"),
77 "prompt_tok_s": timings.get("prompt_per_second"),
78 "draft_n": timings.get("draft_n"),
79 "draft_accepted": timings.get("draft_n_accepted"),
80 "finish_reason": response.choices[0].finish_reason,
81 }
82
83
84async def main():
85 max_tokens_list = [512 * i for i in range(1, 10)] # 512 .. 4608
86 results = []
87
88 for mt in max_tokens_list:
89 print(f"Probando max_tokens={mt}...")
90 r = await run_test(mt)
91 results.append(r)
92
93 # --- Tabla comparativa ---
94 header = (f"{'max_tok':>8} | {'compl_tok':>9} | {'time(s)':>8} | "
95 f"{'tok/s medido':>13} | {'tok/s nativo':>13} | {'draft acc%':>10} | {'finish':>8}")
96 print("\n" + header)
97 print("-" * len(header))
98 for r in results:
99 if r["draft_n"] and r["draft_accepted"] is not None:
100 acc_pct = f"{100 * r['draft_accepted'] / r['draft_n']:.1f}%"
101 else:
102 acc_pct = "-"
103 print(f"{r['max_tokens']:>8} | {r['completion_tokens']:>9} | {r['elapsed_s']:>8} | "
104 f"{r['tok_s_medido']:>13} | {str(r['tok_s_nativo']):>13} | {acc_pct:>10} | {r['finish_reason']:>8}")
105
106 return results
107
108
109if __name__ == "__main__":
110 # asyncio.run(main())
111 await main()finish_reason, measured vs. native tok/s (llama.cpp timings), and MTP draft acceptance rate.| max_tokens | tokens generated | time (s) | tok/s measured | tok/s native | draft acceptance | finish |
|---|---|---|---|---|---|---|
| 512 | 512 | 2.33 | 219.95 | 263.31 | 48.2% | length |
| 1024 | 1024 | 4.04 | 253.50 | 260.19 | 46.9% | length |
| 1536 | 1536 | 6.33 | 242.67 | 247.87 | 43.5% | length |
| 2048 | 2048 | 8.47 | 241.72 | 245.74 | 43.0% | length |
| 2560 | 2560 | 10.10 | 253.35 | 257.73 | 45.9% | length |
| 3072 | 3072 | 12.16 | 252.57 | 256.01 | 44.7% | length |
| 3584 | 3584 | 14.62 | 245.08 | 248.14 | 42.3% | length |
| 4096 | 4096 | 15.60 | 262.53 | 266.24 | 47.9% | length |
| 4608 | 4608 | 18.00 | 256.04 | 259.80 | 46.2% | length |
Q4_K_M, GPU with 95.6 GB VRAM (~25 GB used by the model), --parallel 4, context 131072.tepirale/Ornith-Agents-A1-3.6-35B-A3B-dare_tiesdare_ties (mergekit) on top of Qwen3.6-35B-A3B1from openai import OpenAI
2
3def chat(
4 prompt,
5 system="Eres un asistente útil.",
6 enable_thinking=True,
7 stream=True,
8 max_tokens=300,
9 temperature=0.7,
10 model="ornith-a1",
11 verbose=True,
12 show_metrics=False,
13):
14 """
15 Wrapper unificado para llama-server (OpenAI-compatible) que maneja:
16 - stream=True/False
17 - enable_thinking=True/False
18 - separación de reasoning_content vs content en ambos modos
19 - métricas de tokens/velocidad (show_metrics=True)
20 """
21 extra_body = {
22 "chat_template_kwargs": {"enable_thinking": enable_thinking}
23 }
24
25 create_kwargs = dict(
26 model=model,
27 messages=[
28 {"role": "system", "content": system},
29 {"role": "user", "content": prompt},
30 ],
31 temperature=temperature,
32 max_tokens=max_tokens,
33 stream=stream,
34 extra_body=extra_body,
35 )
36
37 if stream:
38 # Pide que el usage venga en el último chunk (soportado por llama-server reciente)
39 create_kwargs["stream_options"] = {"include_usage": True}
40
41 response = client.chat.completions.create(**create_kwargs)
42
43 reasoning_buf, content_buf = "", ""
44 usage = None
45 timings = None
46
47 if not stream:
48 msg = response.choices[0].message
49 reasoning_buf = getattr(msg, "reasoning_content", None) or ""
50 content_buf = msg.content or ""
51 finish_reason = response.choices[0].finish_reason
52 usage = response.usage
53 timings = getattr(response, "timings", None)
54
55 if verbose:
56 if reasoning_buf:
57 print("🧠 Razonamiento:\n" + reasoning_buf)
58 print("\n" + "-" * 50 + "\n")
59 if content_buf:
60 print("💬 Respuesta:\n" + content_buf)
61 if not content_buf and reasoning_buf:
62 print(f"⚠️ finish_reason='{finish_reason}' — se acabaron los tokens pensando. Sube max_tokens.")
63
64 else:
65 finish_reason = None
66 printed_separator = False
67
68 for chunk in response:
69 if not chunk.choices:
70 # chunk final solo con usage (cuando include_usage=True)
71 if getattr(chunk, "usage", None):
72 usage = chunk.usage
73 timings = getattr(chunk, "timings", None) or timings
74 continue
75
76 choice = chunk.choices[0]
77 delta = choice.delta
78 finish_reason = choice.finish_reason or finish_reason
79
80 r = getattr(delta, "reasoning_content", None)
81 c = delta.content
82
83 if r:
84 reasoning_buf += r
85 if verbose:
86 print(r, end="", flush=True)
87 if c:
88 if verbose and reasoning_buf and not printed_separator:
89 print("\n" + "-" * 50 + "\n")
90 printed_separator = True
91 content_buf += c
92 if verbose:
93 print(c, end="", flush=True)
94
95 if getattr(chunk, "usage", None):
96 usage = chunk.usage
97 timings = getattr(chunk, "timings", None) or timings
98
99 if verbose:
100 print()
101 if not content_buf and reasoning_buf:
102 print(f"⚠️ finish_reason='{finish_reason}' — se acabaron los tokens pensando. Sube max_tokens.")
103
104 metrics = _build_metrics(reasoning_buf, content_buf, usage, timings)
105
106 if show_metrics:
107 _print_metrics(metrics)
108
109 return {
110 "reasoning": reasoning_buf,
111 "content": content_buf,
112 "finish_reason": finish_reason,
113 "usage": usage,
114 "timings": timings,
115 "metrics": metrics,
116 "raw": response if not stream else None,
117 }
118
119
120def _build_metrics(reasoning_buf, content_buf, usage, timings):
121 """Calcula métricas de tokens y velocidad. La división razonamiento/respuesta
122 es una ESTIMACIÓN proporcional por caracteres, ya que la API no separa
123 completion_tokens_details por ahora (viene como None)."""
124 metrics = {
125 "prompt_tokens": None,
126 "completion_tokens": None,
127 "cached_tokens": None,
128 "reasoning_tokens_est": None,
129 "response_tokens_est": None,
130 "prompt_tokens_per_sec": None,
131 "gen_tokens_per_sec": None,
132 "draft_n": None,
133 "draft_n_accepted": None,
134 "draft_accept_rate": None,
135 }
136
137 if usage:
138 metrics["prompt_tokens"] = usage.prompt_tokens
139 metrics["completion_tokens"] = usage.completion_tokens
140 cached = getattr(usage.prompt_tokens_details, "cached_tokens", None) if usage.prompt_tokens_details else None
141 metrics["cached_tokens"] = cached
142
143 total_chars = len(reasoning_buf) + len(content_buf)
144 if total_chars > 0 and usage.completion_tokens:
145 reasoning_ratio = len(reasoning_buf) / total_chars
146 metrics["reasoning_tokens_est"] = round(usage.completion_tokens * reasoning_ratio)
147 metrics["response_tokens_est"] = usage.completion_tokens - metrics["reasoning_tokens_est"]
148
149 if timings:
150 metrics["prompt_tokens_per_sec"] = timings.get("prompt_per_second")
151 metrics["gen_tokens_per_sec"] = timings.get("predicted_per_second")
152 metrics["draft_n"] = timings.get("draft_n")
153 metrics["draft_n_accepted"] = timings.get("draft_n_accepted")
154 if timings.get("draft_n"):
155 metrics["draft_accept_rate"] = round(
156 100 * timings.get("draft_n_accepted", 0) / timings["draft_n"], 1
157 )
158
159 return metrics
160
161
162def _print_metrics(m):
163 print("\n" + "=" * 50)
164 print("📊 MÉTRICAS")
165 print("=" * 50)
166 print(f" Tokens prompt (entrada): {m['prompt_tokens']}")
167 print(f" Tokens prompt cacheados: {m['cached_tokens']}")
168 print(f" Tokens completion (total): {m['completion_tokens']}")
169 print(f" ├─ Razonamiento (est.): {m['reasoning_tokens_est']}")
170 print(f" └─ Respuesta (est.): {m['response_tokens_est']}")
171 print(f" Velocidad prompt: {m['prompt_tokens_per_sec']:.1f} tok/s" if m['prompt_tokens_per_sec'] else " Velocidad prompt: N/A")
172 print(f" Velocidad generación: {m['gen_tokens_per_sec']:.1f} tok/s" if m['gen_tokens_per_sec'] else " Velocidad generación: N/A")
173 if m['draft_n'] is not None:
174 print(f" Draft tokens propuestos: {m['draft_n']}")
175 print(f" Draft tokens aceptados: {m['draft_n_accepted']} ({m['draft_accept_rate']}% acierto MTP)")
176 print("=" * 50)
177
178q = """
179Genera una conversación larga y realista entre un usuario y un asistente de IA, en español.
180
181TEMA: [describe aquí el tema/dominio de la conversación, ej: soporte técnico, tutoría de matemáticas, asistente de cocina, etc.]
182
183FORMATO DE SALIDA (obligatorio):
184- Responde ÚNICAMENTE con un array JSON válido. Nada de texto antes o después, nada de explicaciones, nada de markdown, nada de ```json.
185- El array debe tener exactamente 500 elementos.
186- Cada elemento es un objeto con esta estructura exacta:
187 {
188 "index": <entero, empieza en 0 y sube de 1 en 1>,
189 "role": "user" | "assistant",
190 "content": "<texto en español>"
191 }
192- Los roles deben alternar estrictamente: index par = "user", index impar = "assistant" (o el patrón que definas).
193- La conversación debe tener coherencia temática de principio a fin: el asistente debe recordar y referenciar cosas mencionadas antes por el usuario, y el usuario debe hacer preguntas de seguimiento naturales.
194- Varía la longitud y el estilo de los mensajes (algunos cortos, algunos más elaborados) para que se sienta natural, no repetitivo.
195- No repitas la misma pregunta o respuesta con palabras distintas más de una vez.
196
197REGLAS DE VALIDEZ JSON (muy importante):
198- Usa comillas dobles para todas las claves y strings.
199- Escapa correctamente comillas internas, saltos de línea (\\n) y backslashes dentro de "content".
200- No dejes comas finales (trailing commas) después del último elemento de un objeto o array.
201- No incluyas comentarios dentro del JSON.
202- Verifica mentalmente que el JSON cierre correctamente todos los corchetes y llaves antes de terminar tu respuesta.
203
204Genera los 500 elementos completos, sin cortar la respuesta a la mitad.
205"""
206
207result = chat(
208 q,
209 enable_thinking=True,
210 stream=True,
211 max_tokens=110000,
212 show_metrics=True,
213)
214
215R.
216...
217
218==================================================
219📊 MÉTRICAS
220==================================================
221 Tokens prompt (entrada): 145
222 Tokens prompt cacheados: 12
223 Tokens completion (total): 4454
224 ├─ Razonamiento (est.): 3242
225 └─ Respuesta (est.): 1212
226 Velocidad prompt: 2048.6 tok/s
227 Velocidad generación: 273.8 tok/s
228 Draft tokens propuestos: 5172
229 Draft tokens aceptados: 3163 (61.2% acierto MTP)
230==================================================
231'''
232
233# ---
234
235q= """
236escribe una conversacion tipo usuario y asistente en formato lista de json sin errores y buen formato, la lista debe de contener 500 elementos y solo responde en formato de lista de json.
237
238[
239 {
240 "index": 0,
241 "role": "user",
242 "content": "Esto es una pregunta o consulta de un usuario"
243 },
244 {
245 "index": 1,
246 "role": "assistant",
247 "content": "respuesta que da el assitant al usuario"
248 },
249 ...
250]
251"""
252result = chat(
253 q,
254 enable_thinking=True,
255 stream=True,
256 max_tokens=110000,
257 show_metrics=True,
258)
259
260R.
261...
262
263==================================================
264📊 MÉTRICAS
265==================================================
266 Tokens prompt (entrada): 147
267 Tokens prompt cacheados: 12
268 Tokens completion (total): 26007
269 ├─ Razonamiento (est.): 7839
270 └─ Respuesta (est.): 18168
271 Velocidad prompt: 2055.0 tok/s
272 Velocidad generación: 292.4 tok/s
273 Draft tokens propuestos: 27080
274 Draft tokens aceptados: 19238 (71.0% acierto MTP)
275==================================================
276
277
278
279--spec-type draft-mtp requires an llama.cpp build with MTP support; verify your build includes this flag before reporting issues.Ornith-Agents-A1-3.6-35B-A3B-dare_ties) for commercial use.