# Mac Studio Ornith Think-On Baseline

측정 목적: Mac Studio Ornith runtime의 `think off` 실험 전 기준 성능을 보존한다.

## Runtime Identity

- 측정 시각: 2026-07-13 17:47:59 KST
- Host: `dc-devui-MacStudio.local`
- Endpoint: `http://127.0.0.1:8007/v1`
- Model alias: `ornith:35b`
- Current runtime path: `ornith35b_alias_proxy.py` on `0.0.0.0:8007` -> `mlx_lm.server` on `127.0.0.1:8008`
- Upstream model path: `/Users/dc_dev/.cache/huggingface/manual/mlx-community/Ornith-1.0-35B-8bit`
- Runtime note: the active `vllm_mlx.cli serve` process on this host is currently Gemma4 on `8004`; this Ornith baseline uses the current Mac Studio Ornith alias path above.
- Thinking mode: current default. Upstream `mlx_lm.server` was started with `--chat-template-args '{"enable_thinking":true}'`; benchmark requests did not pass `think=false`.
- API shape: OpenAI-compatible streaming `/v1/chat/completions`
- Token counting: `Qwen2Tokenizer` over concatenated streaming `reasoning`, `reasoning_content`, and `content` deltas.

## Request Parameters

- `max_tokens`: `256`
- `temperature`: `0.2`
- `top_p`: `0.95`
- Warmup: one short warmup request, excluded from the table.
- Prompt: `Benchmark generation task. Do not use tools. Produce a compact Korean report with exactly 24 numbered lines about reliable agent runtime operations. Keep each line short, but continue until the list is complete.`

## Metric Definitions

- `avg_ttft_s`: average seconds from request start to first non-empty streaming delta. This includes `reasoning` deltas and is not the user-visible content latency.
- `avg_per_call_tok_s`: average per-call decode throughput, computed as output tokens divided by time from first non-empty delta to completion.
- `total_tok_s`: aggregate group throughput, computed as total output tokens divided by wall-clock time from earliest request start to latest completion.
- `group_wall_s`: wall-clock duration for the concurrency group.

## Content-First Baseline

This corrected measurement records when the first non-empty `content` delta appears. It is the user-visible answer latency requested for the think-off comparison.

- 측정 시각: 2026-07-13 17:55:18 KST
- Endpoint: `http://127.0.0.1:8007/v1`
- Model alias: `ornith:35b`
- Thinking mode: current default. Upstream `mlx_lm.server` was started with `--chat-template-args '{"enable_thinking":true}'`; benchmark requests did not pass `think=false`.
- `max_tokens`: `1024`
- `temperature`: `0.2`
- `top_p`: `0.95`
- Prompt: `Benchmark generation task. Produce exactly 8 short numbered Korean lines about reliable agent runtime operations.`

| Concurrent calls | Success | Avg first reasoning/any delta (s) | Avg first content delta (s) | Avg reasoning tokens/call | Avg content tokens/call | Avg all tok/s per call | Avg content tok/s per call | Total all tok/s | Total content tok/s | Group wall (s) | Finish reason |
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|
| 1 | 1/1 | 0.227 | 17.830 | 919.0 | 104.0 | 52.29 | 53.00 | 51.69 | 5.25 | 19.792 | length |
| 2 | 2/2 | 0.339 | 22.193 | 978.0 | 45.0 | 44.74 | 44.44 | 88.17 | 3.88 | 23.206 | length |
| 3 | 3/3 | 0.462 | 26.260 | 978.0 | 45.0 | 37.93 | 38.26 | 111.85 | 4.92 | 27.439 | length |
| 4 | 4/4 | 0.598 | 30.638 | 978.0 | 45.0 | 32.55 | 32.34 | 127.74 | 5.62 | 32.035 | length |
| 5 | 5/5 | 0.728 | 38.788 | 978.0 | 45.0 | 25.69 | 25.64 | 126.13 | 5.55 | 40.553 | length |

### Content-First Per-Call Detail

| Concurrent calls | Call | First content delta (s) | Reasoning tokens | Content tokens | All tok/s | Content tok/s | Duration (s) |
|---:|---:|---:|---:|---:|---:|---:|---:|
| 1 | 1 | 17.830 | 919 | 104 | 52.29 | 53.00 | 19.792 |
| 2 | 1 | 22.195 | 978 | 45 | 44.74 | 44.49 | 23.206 |
| 2 | 2 | 22.191 | 978 | 45 | 44.73 | 44.38 | 23.205 |
| 3 | 1 | 26.258 | 978 | 45 | 37.93 | 38.33 | 27.432 |
| 3 | 2 | 26.263 | 978 | 45 | 37.93 | 38.29 | 27.439 |
| 3 | 3 | 26.258 | 978 | 45 | 37.92 | 38.15 | 27.437 |
| 4 | 1 | 30.637 | 978 | 45 | 32.55 | 32.47 | 32.023 |
| 4 | 2 | 30.637 | 978 | 45 | 32.54 | 32.22 | 32.034 |
| 4 | 3 | 30.642 | 978 | 45 | 32.55 | 32.31 | 32.035 |
| 4 | 4 | 30.637 | 978 | 45 | 32.55 | 32.34 | 32.028 |
| 5 | 1 | 38.787 | 978 | 45 | 25.69 | 25.50 | 40.551 |
| 5 | 2 | 38.787 | 978 | 45 | 25.70 | 25.70 | 40.538 |
| 5 | 3 | 38.787 | 978 | 45 | 25.69 | 25.61 | 40.544 |
| 5 | 4 | 38.793 | 978 | 45 | 25.69 | 25.58 | 40.552 |
| 5 | 5 | 38.788 | 978 | 45 | 25.70 | 25.81 | 40.531 |

### Content-First Observations

- The user-visible first content delta was much later than the first reasoning delta.
- At concurrency 1, first reasoning/any delta arrived at `0.227s`, but first content arrived at `17.830s`.
- At concurrency 5, first reasoning/any delta arrived at `0.728s`, but first content arrived at `38.788s`.
- The run consistently spent most of the `1024` token budget on reasoning before content. Most calls produced about `978` reasoning tokens and `45` content tokens before `finish_reason=length`.
- For think-off comparison, `Avg first content delta (s)` is the latency column to compare first.

## Think-Off Result

### Compatibility Note

The alias proxy currently maps request-level `think:false` to upstream `chat_template_args.enable_thinking=false`. The installed `mlx_lm.server` request parser reads `chat_template_kwargs`, not request-level `chat_template_args` (`mlx_lm/server.py` request parse path stores `self.chat_template_kwargs = self.body.get("chat_template_kwargs")`). Because of that mismatch, plain `think:false` did not produce a real think-off result in this runtime path.

Observed `think:false` compatibility probe:

| Mode | First content delta (s) | Reasoning chars | Content chars | Finish reason | Interpretation |
|---|---:|---:|---:|---|---|
| `think:false` via alias proxy | 17.975 | 3300 | 203 | length | Not effective; still generated reasoning first |

The effective think-off measurement below used request `chat_template_kwargs: {"enable_thinking": false}`.

### Effective Think-Off Content-First Table

- 측정 시각: 2026-07-13 18:05:09 KST
- Endpoint: `http://127.0.0.1:8007/v1`
- Model alias: `ornith:35b`
- Thinking mode: effective request-level think-off via `chat_template_kwargs.enable_thinking=false`
- `max_tokens`: `1024`
- `temperature`: `0.2`
- `top_p`: `0.95`
- Prompt: same as the Content-First Baseline.

| Concurrent calls | Success | Avg first content delta (s) | Avg reasoning tokens/call | Avg content tokens/call | Avg content tok/s per call | Total content tok/s | Group wall (s) | Finish reason |
|---:|---:|---:|---:|---:|---:|---:|---:|---|
| 1 | 1/1 | 0.208 | 0.0 | 104.0 | 52.77 | 47.74 | 2.179 | stop |
| 2 | 2/2 | 0.333 | 0.0 | 113.0 | 44.81 | 79.15 | 2.855 | stop |
| 3 | 3/3 | 0.457 | 0.0 | 113.0 | 37.79 | 98.27 | 3.450 | stop |
| 4 | 4/4 | 0.580 | 0.0 | 113.0 | 32.47 | 111.20 | 4.065 | stop |
| 5 | 5/5 | 0.703 | 0.0 | 113.0 | 25.82 | 111.03 | 5.089 | stop |

### Think-On vs Effective Think-Off

| Concurrent calls | Think-on first content (s) | Effective think-off first content (s) | Delta (s) | Speedup |
|---:|---:|---:|---:|---:|
| 1 | 17.830 | 0.208 | 17.622 | 85.9x |
| 2 | 22.193 | 0.333 | 21.860 | 66.7x |
| 3 | 26.260 | 0.457 | 25.803 | 57.5x |
| 4 | 30.638 | 0.580 | 30.058 | 52.9x |
| 5 | 38.788 | 0.703 | 38.085 | 55.2x |

### Effective Think-Off Observations

- Effective think-off eliminated reasoning output in this benchmark (`0` reasoning tokens for all calls).
- First user-visible content became the first non-empty stream delta.
- Total content throughput peaked around concurrency 4-5 at about `111 tok/s`.
- The largest improvement is first content latency, not single-stream decode tok/s.
- To make `think:false` work through the alias endpoint, update the proxy to map `think` to `chat_template_kwargs.enable_thinking`, not only `chat_template_args.enable_thinking`.

## First-Content Optimization Candidates

| Priority | Candidate | Expected effect | Risk / note |
|---:|---|---|---|
| 1 | Fix alias proxy `think` mapping to upstream `chat_template_kwargs.enable_thinking` | Makes simple `think:false` clients get the measured `0.2-0.7s` first-content path | Very small code change, but verify `/v1/chat/completions` streaming and existing `chat_template_kwargs` passthrough |
| 2 | Start a separate always-non-thinking Ornith endpoint with `mlx_lm.server --chat-template-args '{"enable_thinking":false}'` | Makes non-thinking the default without per-request kwargs | Keep on a separate port first; quality/tool-call behavior may differ |
| 3 | Use `vllm_mlx` Ornith path if stable, with `--default-chat-template-kwargs '{"enable_thinking": false}'` or request `enable_thinking=false` | Better OpenAI-compatible semantics, continuous batching, prefix cache, warm prompts, and request-level thinking controls | This changes runtime stack from current `mlx_lm.server`; test as a parallel endpoint before replacing `8007` |
| 4 | If reasoning must stay on, enforce a small thinking budget where supported | Bounds reasoning delay before content instead of fully disabling reasoning | Current `mlx_lm.server` path has no observed thinking budget control; `vllm_mlx`/vLLM-style serving has this concept |
| 5 | Warm repeated prompt prefixes / prompt cache | Reduces prefill/TTFT for repeated system prompts and agent templates | Helps prefill, not the long reasoning-before-content delay. Think-off gives the bigger win here |
| 6 | Tune `decode-concurrency`, `prompt-concurrency`, and `prefill-step-size` on `mlx_lm.server` | May improve batch scheduling and prefill behavior | Current content-first bottleneck was reasoning generation. Tune only after think-off route is correct |

References checked:

- Local `mlx_lm.server --help`: supports `--chat-template-args`, `--decode-concurrency`, `--prompt-concurrency`, `--prefill-step-size`, `--prompt-cache-size`, `--prompt-cache-bytes`, and draft-model options.
- Local installed `mlx_lm/server.py`: request body uses `chat_template_kwargs`; startup CLI uses `chat_template_args`.
- Qwen docs: previous Qwen3-style hybrid thinking can be controlled by `enable_thinking=False`; newer Qwen3 Instruct/Thinking variants may be mode-specific.
- vLLM docs: request-level `chat_template_kwargs` can override server defaults, and thinking budgets can bound reasoning tokens.
- `vllm-mlx` docs: continuous batching, prefix cache, SSD-tiered cache, and warm prompts are intended Apple Silicon serving optimizations.

## Reasoning-Inclusive First-Delta Table

| Concurrent calls | Success | Avg output tokens/call | Total output tokens | Avg TTFT (s) | Avg duration (s) | Avg decode duration (s) | Avg tok/s per call | Total tok/s | Group wall (s) | Finish reason |
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|
| 1 | 1/1 | 256.0 | 256 | 0.221 | 5.085 | 4.864 | 52.63 | 50.34 | 5.085 | length |
| 2 | 2/2 | 256.0 | 512 | 0.340 | 6.072 | 5.732 | 44.66 | 84.31 | 6.073 | length |
| 3 | 3/3 | 256.0 | 768 | 0.462 | 7.213 | 6.751 | 37.92 | 106.42 | 7.217 | length |
| 4 | 4/4 | 256.0 | 1024 | 0.570 | 8.445 | 7.875 | 32.51 | 121.17 | 8.451 | length |
| 5 | 5/5 | 256.0 | 1280 | 0.698 | 10.639 | 9.940 | 25.75 | 120.20 | 10.649 | length |

## Per-Call Detail

| Concurrent calls | Call | Output tokens | TTFT (s) | Duration (s) | Decode duration (s) | Tok/s |
|---:|---:|---:|---:|---:|---:|---:|
| 1 | 1 | 256 | 0.221 | 5.085 | 4.864 | 52.63 |
| 2 | 1 | 256 | 0.339 | 6.072 | 5.734 | 44.65 |
| 2 | 2 | 256 | 0.342 | 6.072 | 5.730 | 44.68 |
| 3 | 1 | 256 | 0.465 | 7.216 | 6.751 | 37.92 |
| 3 | 2 | 256 | 0.461 | 7.216 | 6.755 | 37.90 |
| 3 | 3 | 256 | 0.460 | 7.208 | 6.748 | 37.94 |
| 4 | 1 | 256 | 0.569 | 8.443 | 7.874 | 32.51 |
| 4 | 2 | 256 | 0.569 | 8.438 | 7.869 | 32.53 |
| 4 | 3 | 256 | 0.569 | 8.450 | 7.881 | 32.48 |
| 4 | 4 | 256 | 0.574 | 8.451 | 7.877 | 32.50 |
| 5 | 1 | 256 | 0.697 | 10.647 | 9.950 | 25.73 |
| 5 | 2 | 256 | 0.697 | 10.632 | 9.935 | 25.77 |
| 5 | 3 | 256 | 0.696 | 10.639 | 9.943 | 25.75 |
| 5 | 4 | 256 | 0.704 | 10.648 | 9.944 | 25.74 |
| 5 | 5 | 256 | 0.698 | 10.626 | 9.929 | 25.78 |

## Observations

- Every measured request completed successfully and stopped at `finish_reason=length`.
- Aggregate throughput increased from concurrency 1 to 4, then flattened at concurrency 5.
- In this baseline, total throughput was `121.17 tok/s` at concurrency 4 and `120.20 tok/s` at concurrency 5.
- Average TTFT rose with concurrency, from `0.221s` at concurrency 1 to `0.698s` at concurrency 5.
