Compare commits
30 Commits
d0c89dac48
...
feat/two-s
| Author | SHA1 | Date | |
|---|---|---|---|
| f2ff0faebd | |||
| 4a64196a99 | |||
| b17b213575 | |||
| 93ce339d61 | |||
| b1b74318f6 | |||
| 2fcaf80450 | |||
| 3541065675 | |||
| 7678c7d5e8 | |||
| ed2bbe0323 | |||
| 77af4ded2a | |||
| 4f45b546a1 | |||
| 90c3eb51c8 | |||
| 0b6beafeb8 | |||
| d4aff81691 | |||
| f31e9ccfd5 | |||
| 03e556f0ab | |||
| dfc823f972 | |||
| 9f52812753 | |||
| 958739027a | |||
| 0f57ee96a9 | |||
| 43125f48cf | |||
| 3af1d84ac0 | |||
| 08e53fd897 | |||
| a8f903498d | |||
| 51a9e4a007 | |||
| 0f15bbc3f1 | |||
| 6f8e3c95c1 | |||
| 8b4116fad0 | |||
| 27d1c8fa92 | |||
| 984eb1f325 |
177
configs/examples/dash0_qwen27b_stopB_loop.json
Normal file
177
configs/examples/dash0_qwen27b_stopB_loop.json
Normal file
@@ -0,0 +1,177 @@
|
||||
{
|
||||
"study_id": "dash0-qwen27b-stopB-loop-chat-0-8k",
|
||||
"hardware": {
|
||||
"gpu_count": 8,
|
||||
"gpu_model": "H20",
|
||||
"host_candidates": [
|
||||
"dash0"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"model_id": "qwen3.5-27b-256k-0223-internal",
|
||||
"served_model_name": "qwen35-27b-aituner"
|
||||
},
|
||||
"engine": {
|
||||
"engine_name": "vllm",
|
||||
"engine_version": "latest-release-on-dash0",
|
||||
"exec_path": "/usr/local/bin/vllm",
|
||||
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
|
||||
"host": "127.0.0.1",
|
||||
"port": 18082,
|
||||
"healthcheck_path": "/v1/models",
|
||||
"ready_timeout_s": 900,
|
||||
"request_timeout_s": 180,
|
||||
"launch_args": [
|
||||
"serve",
|
||||
"/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal"
|
||||
],
|
||||
"base_envs": {
|
||||
"VLLM_DISABLE_COMPILE_CACHE": "1",
|
||||
"DS_LLM_IGNORE_WARMUP": "1",
|
||||
"DS_LLM_IGNORE_CHECK_WARMUP": "1",
|
||||
"VLLM_ENABLE_MODEL_RUNNER_WARMUP": "1",
|
||||
"VLLM_GDN_USE_FUSED_QKVZBA_KERNEL": "0",
|
||||
"PARAM_TOTAL_MAX": "262144",
|
||||
"PARAM_IN_LENGTH_MAX": "262144",
|
||||
"PARAM_MAX_LENGTH_MAX": "131072",
|
||||
"DS_LLM_MAX_THINK_TOKENS": "81920",
|
||||
"DS_LLM_GRACEFUL_SHUTDOWN_WAIT_SECONDS": "600",
|
||||
"VLLM_FP8_USE_BLADNN": "1",
|
||||
"VLLM_MOE_USE_BLADNN": "1",
|
||||
"VLLM_GDN_USE_BLADNN": "0",
|
||||
"VLLM_USE_V1": "1",
|
||||
"VLLM_IS_HYBRID_MODEL": "1",
|
||||
"VLLM_ENABLE_TORCH_COMPILE": "1",
|
||||
"VLLM_ATTENTION_BACKEND": "FLASH_ATTN",
|
||||
"VLLM_QUANTIZE_ROUTED_EXPERTS_ONLY": "1",
|
||||
"VLLM_USE_FLASHINFER_SAMPLER": "0",
|
||||
"VLLM_DP_MASTER_PORT": "9528",
|
||||
"VLLM_RESPONSE_TIMEOUT": "300",
|
||||
"VLLM_LOG_REQ_KV_LENS": "1",
|
||||
"DS_LLM_GRACEFUL_SHUTDOWN_KEEP_SECONDS": "600",
|
||||
"CUDA_VISIBLE_DEVICES": "2,3,4,5,6,7"
|
||||
},
|
||||
"base_flags": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 18082,
|
||||
"served-model-name": "qwen35-27b-aituner",
|
||||
"trust-remote-code": true,
|
||||
"dtype": "bfloat16",
|
||||
"gpu-memory-utilization": 0.9,
|
||||
"enable-prefix-caching": true,
|
||||
"mamba-cache-mode": "light",
|
||||
"distributed-executor-backend": "mp",
|
||||
"block-size": 64,
|
||||
"enable-chunked-prefill": true,
|
||||
"max-num-batched-tokens": 8192,
|
||||
"disable-cascade-attn": true,
|
||||
"max-model-len": 262144,
|
||||
"speculative-config": "{\"method\":\"qwen3_next_vl_mtp\",\"num_speculative_tokens\":3}",
|
||||
"mm-processor-cache-gb": 0,
|
||||
"limit-mm-per-prompt": "{\"image\":256,\"video\":64}",
|
||||
"compilation-config": "{\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"use_inductor\":false,\"pass_config\":{\"fuse_norm_quant\":false,\"fuse_act_quant\":false,\"fuse_attn_quant\":false}}",
|
||||
"mamba-cache-dtype": "float32",
|
||||
"skip-mm-profiling": true,
|
||||
"quantization": "fp8",
|
||||
"tensor-parallel-size": 1,
|
||||
"disable-log-requests": true
|
||||
},
|
||||
"tunable_envs": [
|
||||
"VLLM_ENABLE_TORCH_COMPILE"
|
||||
],
|
||||
"tunable_flags": [
|
||||
"tensor-parallel-size",
|
||||
"data-parallel-size",
|
||||
"expert-parallel-size",
|
||||
"gpu-memory-utilization",
|
||||
"block-size",
|
||||
"max-num-batched-tokens",
|
||||
"max-num-seqs",
|
||||
"enable-prefix-caching",
|
||||
"enable-chunked-prefill"
|
||||
],
|
||||
"topology_constraints": {
|
||||
"require_tp_dp_product_equals_gpu_count": false,
|
||||
"require_ep_size_leq_tp_dp_product": true,
|
||||
"require_ep_size_divides_tp_dp_product": true,
|
||||
"require_enable_expert_parallel_when_ep_gt_one": true,
|
||||
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
|
||||
"allowed_tp_dp_products": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_tensor_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_data_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_expert_parallel_sizes": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"python_executable": "python3"
|
||||
},
|
||||
"trace": {
|
||||
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
|
||||
"window_id": "chat_w20260311_1000",
|
||||
"u_field": "sampling_u",
|
||||
"timestamp_field": "timestamp",
|
||||
"max_concurrency": 32,
|
||||
"input_length_filter": {
|
||||
"min_input_tokens": 0,
|
||||
"max_input_tokens": 8192
|
||||
},
|
||||
"replay_time_scale": 1.0,
|
||||
"early_stop_max_lag_s": 120.0,
|
||||
"early_stop_max_elapsed_s": 900.0,
|
||||
"adaptive_stop": {
|
||||
"enabled": true,
|
||||
"tau": 0.9,
|
||||
"tau_c": 0.9,
|
||||
"stable_checks": 3,
|
||||
"max_checks": 20,
|
||||
"min_fraction": 0.1,
|
||||
"boundary_delta": 0.02
|
||||
}
|
||||
},
|
||||
"slo": {
|
||||
"target_pass_rate": 0.95,
|
||||
"ttft_rule": {
|
||||
"kind": "linear_ms",
|
||||
"intercept_ms": 4000,
|
||||
"per_token_ms": 0.125
|
||||
},
|
||||
"tpot_rule": {
|
||||
"kind": "fixed_ms",
|
||||
"threshold_ms": 50
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"low": 0.0,
|
||||
"high": 0.25,
|
||||
"tolerance": 0.001,
|
||||
"max_probes": 6,
|
||||
"sample_seed": 20260325,
|
||||
"inherit_incumbent_floor": true
|
||||
},
|
||||
"llm": {
|
||||
"system_prompt": "Propose a single engine config patch that increases the maximum feasible sampling_u under the SLO target. Favor launch-safe changes grounded in the incumbent result and only propose knobs that plausibly improve throughput above the incumbent request rate.",
|
||||
"max_history_trials": 8,
|
||||
"endpoint": {
|
||||
"provider": "codex",
|
||||
"model": "gpt-5.4",
|
||||
"stream": true,
|
||||
"api_key_env": "OPENAI_API_KEY",
|
||||
"timeout_s": 180
|
||||
}
|
||||
}
|
||||
}
|
||||
176
configs/examples/dash0_qwen27b_tp_ab.json
Normal file
176
configs/examples/dash0_qwen27b_tp_ab.json
Normal file
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"study_id": "dash0-qwen27b-tp-ab-chat-0-8k",
|
||||
"hardware": {
|
||||
"gpu_count": 8,
|
||||
"gpu_model": "H20",
|
||||
"host_candidates": [
|
||||
"dash0"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"model_id": "qwen3.5-27b-256k-0223-internal",
|
||||
"served_model_name": "qwen35-27b-aituner"
|
||||
},
|
||||
"engine": {
|
||||
"engine_name": "vllm",
|
||||
"engine_version": "latest-release-on-dash0",
|
||||
"exec_path": "/usr/local/bin/vllm",
|
||||
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
|
||||
"host": "127.0.0.1",
|
||||
"port": 18082,
|
||||
"healthcheck_path": "/v1/models",
|
||||
"ready_timeout_s": 900,
|
||||
"request_timeout_s": 180,
|
||||
"launch_args": [
|
||||
"serve",
|
||||
"/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal"
|
||||
],
|
||||
"base_envs": {
|
||||
"VLLM_DISABLE_COMPILE_CACHE": "1",
|
||||
"DS_LLM_IGNORE_WARMUP": "1",
|
||||
"DS_LLM_IGNORE_CHECK_WARMUP": "1",
|
||||
"VLLM_ENABLE_MODEL_RUNNER_WARMUP": "1",
|
||||
"VLLM_GDN_USE_FUSED_QKVZBA_KERNEL": "0",
|
||||
"PARAM_TOTAL_MAX": "262144",
|
||||
"PARAM_IN_LENGTH_MAX": "262144",
|
||||
"PARAM_MAX_LENGTH_MAX": "131072",
|
||||
"DS_LLM_MAX_THINK_TOKENS": "81920",
|
||||
"DS_LLM_GRACEFUL_SHUTDOWN_WAIT_SECONDS": "600",
|
||||
"VLLM_FP8_USE_BLADNN": "1",
|
||||
"VLLM_MOE_USE_BLADNN": "1",
|
||||
"VLLM_GDN_USE_BLADNN": "0",
|
||||
"VLLM_USE_V1": "1",
|
||||
"VLLM_IS_HYBRID_MODEL": "1",
|
||||
"VLLM_ENABLE_TORCH_COMPILE": "1",
|
||||
"VLLM_ATTENTION_BACKEND": "FLASH_ATTN",
|
||||
"VLLM_QUANTIZE_ROUTED_EXPERTS_ONLY": "1",
|
||||
"VLLM_USE_FLASHINFER_SAMPLER": "0",
|
||||
"VLLM_DP_MASTER_PORT": "9528",
|
||||
"VLLM_RESPONSE_TIMEOUT": "300",
|
||||
"VLLM_LOG_REQ_KV_LENS": "1",
|
||||
"DS_LLM_GRACEFUL_SHUTDOWN_KEEP_SECONDS": "600",
|
||||
"CUDA_VISIBLE_DEVICES": "2,3,4,5,6,7"
|
||||
},
|
||||
"base_flags": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 18082,
|
||||
"served-model-name": "qwen35-27b-aituner",
|
||||
"trust-remote-code": true,
|
||||
"dtype": "bfloat16",
|
||||
"gpu-memory-utilization": 0.9,
|
||||
"enable-prefix-caching": true,
|
||||
"mamba-cache-mode": "light",
|
||||
"distributed-executor-backend": "mp",
|
||||
"block-size": 64,
|
||||
"enable-chunked-prefill": true,
|
||||
"max-num-batched-tokens": 8192,
|
||||
"disable-cascade-attn": true,
|
||||
"max-model-len": 262144,
|
||||
"speculative-config": "{\"method\":\"qwen3_next_vl_mtp\",\"num_speculative_tokens\":3}",
|
||||
"mm-processor-cache-gb": 0,
|
||||
"limit-mm-per-prompt": "{\"image\":256,\"video\":64}",
|
||||
"compilation-config": "{\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"use_inductor\":false,\"pass_config\":{\"fuse_norm_quant\":false,\"fuse_act_quant\":false,\"fuse_attn_quant\":false}}",
|
||||
"mamba-cache-dtype": "float32",
|
||||
"skip-mm-profiling": true,
|
||||
"quantization": "fp8",
|
||||
"tensor-parallel-size": 1,
|
||||
"disable-log-requests": true
|
||||
},
|
||||
"tunable_envs": [
|
||||
"VLLM_ENABLE_TORCH_COMPILE"
|
||||
],
|
||||
"tunable_flags": [
|
||||
"tensor-parallel-size",
|
||||
"data-parallel-size",
|
||||
"expert-parallel-size",
|
||||
"gpu-memory-utilization",
|
||||
"block-size",
|
||||
"max-num-batched-tokens",
|
||||
"max-num-seqs",
|
||||
"enable-prefix-caching",
|
||||
"enable-chunked-prefill"
|
||||
],
|
||||
"topology_constraints": {
|
||||
"require_tp_dp_product_equals_gpu_count": false,
|
||||
"require_ep_size_leq_tp_dp_product": true,
|
||||
"require_ep_size_divides_tp_dp_product": true,
|
||||
"require_enable_expert_parallel_when_ep_gt_one": true,
|
||||
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
|
||||
"allowed_tp_dp_products": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_tensor_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_data_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_expert_parallel_sizes": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"python_executable": "python3"
|
||||
},
|
||||
"trace": {
|
||||
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
|
||||
"window_id": "chat_w20260311_1000",
|
||||
"u_field": "sampling_u",
|
||||
"timestamp_field": "timestamp",
|
||||
"max_concurrency": 32,
|
||||
"input_length_filter": {
|
||||
"min_input_tokens": 0,
|
||||
"max_input_tokens": 8192
|
||||
},
|
||||
"replay_time_scale": 1.0,
|
||||
"early_stop_max_lag_s": 120.0,
|
||||
"early_stop_max_elapsed_s": 900.0,
|
||||
"adaptive_stop": {
|
||||
"enabled": true,
|
||||
"tau": 0.9,
|
||||
"tau_c": 0.9,
|
||||
"stable_checks": 3,
|
||||
"max_checks": 20,
|
||||
"min_fraction": 0.1,
|
||||
"boundary_delta": 0.02
|
||||
}
|
||||
},
|
||||
"slo": {
|
||||
"target_pass_rate": 0.95,
|
||||
"ttft_rule": {
|
||||
"kind": "linear_ms",
|
||||
"intercept_ms": 4000,
|
||||
"per_token_ms": 0.125
|
||||
},
|
||||
"tpot_rule": {
|
||||
"kind": "fixed_ms",
|
||||
"threshold_ms": 50
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"low": 0.0,
|
||||
"high": 0.125,
|
||||
"tolerance": 0.001,
|
||||
"max_probes": 6,
|
||||
"sample_seed": 20260325
|
||||
},
|
||||
"llm": {
|
||||
"system_prompt": "Propose a single engine config patch that increases the maximum feasible sampling_u under the SLO target. Favor launch-safe changes grounded in the incumbent result and only propose knobs that plausibly improve throughput above the incumbent request rate.",
|
||||
"max_history_trials": 8,
|
||||
"endpoint": {
|
||||
"provider": "codex",
|
||||
"model": "gpt-5.4",
|
||||
"stream": true,
|
||||
"api_key_env": "OPENAI_API_KEY",
|
||||
"timeout_s": 180
|
||||
}
|
||||
}
|
||||
}
|
||||
138
configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json
Normal file
138
configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"study_id": "dash0-qwen30b-a3b-stopA-fulldata-chat-0-8k",
|
||||
"hardware": {
|
||||
"gpu_count": 8,
|
||||
"gpu_model": "H20",
|
||||
"host_candidates": [
|
||||
"dash0"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"model_id": "Qwen/Qwen3-30B-A3B",
|
||||
"served_model_name": "qwen3-30b-a3b-community"
|
||||
},
|
||||
"engine": {
|
||||
"engine_name": "vllm",
|
||||
"engine_version": "0.20.0",
|
||||
"exec_path": "/usr/local/bin/vllm",
|
||||
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
|
||||
"host": "127.0.0.1",
|
||||
"port": 18230,
|
||||
"healthcheck_path": "/v1/models",
|
||||
"ready_timeout_s": 900,
|
||||
"request_timeout_s": 900,
|
||||
"launch_args": [
|
||||
"serve",
|
||||
"/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B"
|
||||
],
|
||||
"base_envs": {
|
||||
"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7",
|
||||
"HOME": "/tmp/wjh",
|
||||
"XDG_CACHE_HOME": "/tmp/wjh/.cache"
|
||||
},
|
||||
"base_flags": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 18230,
|
||||
"served-model-name": "qwen3-30b-a3b-community",
|
||||
"gpu-memory-utilization": 0.9,
|
||||
"max-model-len": 16384,
|
||||
"trust-remote-code": true,
|
||||
"enable-prefix-caching": true
|
||||
},
|
||||
"tunable_envs": [],
|
||||
"tunable_flags": [
|
||||
"tensor-parallel-size",
|
||||
"data-parallel-size",
|
||||
"enable-expert-parallel",
|
||||
"expert-parallel-size",
|
||||
"gpu-memory-utilization",
|
||||
"max-num-batched-tokens",
|
||||
"max-num-seqs",
|
||||
"block-size",
|
||||
"enable-prefix-caching",
|
||||
"enable-chunked-prefill"
|
||||
],
|
||||
"topology_constraints": {
|
||||
"require_tp_dp_product_equals_gpu_count": false,
|
||||
"require_ep_size_leq_tp_dp_product": true,
|
||||
"require_ep_size_divides_tp_dp_product": true,
|
||||
"require_enable_expert_parallel_when_ep_gt_one": true,
|
||||
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
|
||||
"allowed_tp_dp_products": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_tensor_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_data_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_expert_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
]
|
||||
},
|
||||
"python_executable": "/tmp/wjh/venvs/vllm-0.20.0-cu129/bin/python"
|
||||
},
|
||||
"trace": {
|
||||
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
|
||||
"window_id": "chat_w20260311_1000",
|
||||
"completion_tokens_override": 128,
|
||||
"u_field": "sampling_u",
|
||||
"timestamp_field": "timestamp",
|
||||
"max_concurrency": 64,
|
||||
"input_length_filter": {
|
||||
"min_input_tokens": 0,
|
||||
"max_input_tokens": 8192
|
||||
},
|
||||
"replay_time_scale": 1.0,
|
||||
"early_stop_max_lag_s": 120.0,
|
||||
"early_stop_max_elapsed_s": 900.0
|
||||
},
|
||||
"slo": {
|
||||
"target_pass_rate": 0.95,
|
||||
"ttft_rule": {
|
||||
"kind": "step_ms",
|
||||
"buckets": [
|
||||
{
|
||||
"max_input_tokens": 4096,
|
||||
"threshold_ms": 2000
|
||||
},
|
||||
{
|
||||
"max_input_tokens": 32768,
|
||||
"threshold_ms": 4000
|
||||
},
|
||||
{
|
||||
"threshold_ms": 6000
|
||||
}
|
||||
]
|
||||
},
|
||||
"tpot_rule": {
|
||||
"kind": "fixed_ms",
|
||||
"threshold_ms": 50
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"low": 0.0,
|
||||
"high": 0.125,
|
||||
"tolerance": 0.001,
|
||||
"max_probes": 4,
|
||||
"sample_seed": 20260325
|
||||
},
|
||||
"llm": {
|
||||
"system_prompt": "Tune community vLLM 0.20.0 serving for Qwen3-30B-A3B. Start from the default vLLM engine configuration, use only launch-safe patches, and optimize request_rate_per_gpu under the configured SLO.",
|
||||
"max_history_trials": 8,
|
||||
"use_harness": false
|
||||
}
|
||||
}
|
||||
147
configs/examples/dash0_qwen30b_a3b_stopA_on.json
Normal file
147
configs/examples/dash0_qwen30b_a3b_stopA_on.json
Normal file
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"study_id": "dash0-qwen30b-a3b-stopA-on-chat-0-8k",
|
||||
"hardware": {
|
||||
"gpu_count": 8,
|
||||
"gpu_model": "H20",
|
||||
"host_candidates": [
|
||||
"dash0"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"model_id": "Qwen/Qwen3-30B-A3B",
|
||||
"served_model_name": "qwen3-30b-a3b-community"
|
||||
},
|
||||
"engine": {
|
||||
"engine_name": "vllm",
|
||||
"engine_version": "0.20.0",
|
||||
"exec_path": "/usr/local/bin/vllm",
|
||||
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
|
||||
"host": "127.0.0.1",
|
||||
"port": 18230,
|
||||
"healthcheck_path": "/v1/models",
|
||||
"ready_timeout_s": 900,
|
||||
"request_timeout_s": 900,
|
||||
"launch_args": [
|
||||
"serve",
|
||||
"/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B"
|
||||
],
|
||||
"base_envs": {
|
||||
"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7",
|
||||
"HOME": "/tmp/wjh",
|
||||
"XDG_CACHE_HOME": "/tmp/wjh/.cache"
|
||||
},
|
||||
"base_flags": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 18230,
|
||||
"served-model-name": "qwen3-30b-a3b-community",
|
||||
"gpu-memory-utilization": 0.9,
|
||||
"max-model-len": 16384,
|
||||
"trust-remote-code": true,
|
||||
"enable-prefix-caching": true
|
||||
},
|
||||
"tunable_envs": [],
|
||||
"tunable_flags": [
|
||||
"tensor-parallel-size",
|
||||
"data-parallel-size",
|
||||
"enable-expert-parallel",
|
||||
"expert-parallel-size",
|
||||
"gpu-memory-utilization",
|
||||
"max-num-batched-tokens",
|
||||
"max-num-seqs",
|
||||
"block-size",
|
||||
"enable-prefix-caching",
|
||||
"enable-chunked-prefill"
|
||||
],
|
||||
"topology_constraints": {
|
||||
"require_tp_dp_product_equals_gpu_count": false,
|
||||
"require_ep_size_leq_tp_dp_product": true,
|
||||
"require_ep_size_divides_tp_dp_product": true,
|
||||
"require_enable_expert_parallel_when_ep_gt_one": true,
|
||||
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
|
||||
"allowed_tp_dp_products": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_tensor_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_data_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_expert_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
]
|
||||
},
|
||||
"python_executable": "/tmp/wjh/venvs/vllm-0.20.0-cu129/bin/python"
|
||||
},
|
||||
"trace": {
|
||||
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
|
||||
"window_id": "chat_w20260311_1000",
|
||||
"completion_tokens_override": 128,
|
||||
"u_field": "sampling_u",
|
||||
"timestamp_field": "timestamp",
|
||||
"max_concurrency": 64,
|
||||
"input_length_filter": {
|
||||
"min_input_tokens": 0,
|
||||
"max_input_tokens": 8192
|
||||
},
|
||||
"replay_time_scale": 1.0,
|
||||
"early_stop_max_lag_s": 120.0,
|
||||
"early_stop_max_elapsed_s": 900.0,
|
||||
"adaptive_stop": {
|
||||
"enabled": true,
|
||||
"tau": 0.9,
|
||||
"tau_c": 0.9,
|
||||
"stable_checks": 3,
|
||||
"max_checks": 20,
|
||||
"min_fraction": 0.1,
|
||||
"boundary_delta": 0.02
|
||||
}
|
||||
},
|
||||
"slo": {
|
||||
"target_pass_rate": 0.95,
|
||||
"ttft_rule": {
|
||||
"kind": "step_ms",
|
||||
"buckets": [
|
||||
{
|
||||
"max_input_tokens": 4096,
|
||||
"threshold_ms": 2000
|
||||
},
|
||||
{
|
||||
"max_input_tokens": 32768,
|
||||
"threshold_ms": 4000
|
||||
},
|
||||
{
|
||||
"threshold_ms": 6000
|
||||
}
|
||||
]
|
||||
},
|
||||
"tpot_rule": {
|
||||
"kind": "fixed_ms",
|
||||
"threshold_ms": 50
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"low": 0.0,
|
||||
"high": 0.125,
|
||||
"tolerance": 0.001,
|
||||
"max_probes": 4,
|
||||
"sample_seed": 20260325
|
||||
},
|
||||
"llm": {
|
||||
"system_prompt": "Tune community vLLM 0.20.0 serving for Qwen3-30B-A3B. Start from the default vLLM engine configuration, use only launch-safe patches, and optimize request_rate_per_gpu under the configured SLO.",
|
||||
"max_history_trials": 8,
|
||||
"use_harness": false
|
||||
}
|
||||
}
|
||||
155
configs/examples/dash0_qwen30b_a3b_stopB_e2e.json
Normal file
155
configs/examples/dash0_qwen30b_a3b_stopB_e2e.json
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"study_id": "dash0-qwen30b-a3b-stopB-e2e-hi-chat-0-8k",
|
||||
"hardware": {
|
||||
"gpu_count": 8,
|
||||
"gpu_model": "H20",
|
||||
"host_candidates": [
|
||||
"dash0"
|
||||
]
|
||||
},
|
||||
"model": {
|
||||
"model_id": "Qwen/Qwen3-30B-A3B",
|
||||
"served_model_name": "qwen3-30b-a3b-community"
|
||||
},
|
||||
"engine": {
|
||||
"engine_name": "vllm",
|
||||
"engine_version": "0.20.0",
|
||||
"exec_path": "/usr/local/bin/vllm",
|
||||
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
|
||||
"host": "127.0.0.1",
|
||||
"port": 18230,
|
||||
"healthcheck_path": "/v1/models",
|
||||
"ready_timeout_s": 900,
|
||||
"request_timeout_s": 900,
|
||||
"launch_args": [
|
||||
"serve",
|
||||
"/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B"
|
||||
],
|
||||
"base_envs": {
|
||||
"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7",
|
||||
"HOME": "/tmp/wjh",
|
||||
"XDG_CACHE_HOME": "/tmp/wjh/.cache"
|
||||
},
|
||||
"base_flags": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 18230,
|
||||
"served-model-name": "qwen3-30b-a3b-community",
|
||||
"gpu-memory-utilization": 0.9,
|
||||
"max-model-len": 16384,
|
||||
"trust-remote-code": true,
|
||||
"enable-prefix-caching": true
|
||||
},
|
||||
"tunable_envs": [],
|
||||
"tunable_flags": [
|
||||
"tensor-parallel-size",
|
||||
"data-parallel-size",
|
||||
"enable-expert-parallel",
|
||||
"expert-parallel-size",
|
||||
"gpu-memory-utilization",
|
||||
"max-num-batched-tokens",
|
||||
"max-num-seqs",
|
||||
"block-size",
|
||||
"enable-prefix-caching",
|
||||
"enable-chunked-prefill"
|
||||
],
|
||||
"topology_constraints": {
|
||||
"require_tp_dp_product_equals_gpu_count": false,
|
||||
"require_ep_size_leq_tp_dp_product": true,
|
||||
"require_ep_size_divides_tp_dp_product": true,
|
||||
"require_enable_expert_parallel_when_ep_gt_one": true,
|
||||
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
|
||||
"allowed_tp_dp_products": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_tensor_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_data_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
],
|
||||
"allowed_expert_parallel_sizes": [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8
|
||||
]
|
||||
},
|
||||
"python_executable": "/tmp/wjh/venvs/vllm-0.20.0-cu129/bin/python"
|
||||
},
|
||||
"trace": {
|
||||
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
|
||||
"window_id": "chat_w20260311_1000",
|
||||
"completion_tokens_override": 128,
|
||||
"u_field": "sampling_u",
|
||||
"timestamp_field": "timestamp",
|
||||
"max_concurrency": 64,
|
||||
"input_length_filter": {
|
||||
"min_input_tokens": 0,
|
||||
"max_input_tokens": 8192
|
||||
},
|
||||
"max_requests_per_probe": 512,
|
||||
"replay_time_scale": 0.1,
|
||||
"early_stop_max_lag_s": 120.0,
|
||||
"early_stop_max_elapsed_s": 900.0,
|
||||
"adaptive_stop": {
|
||||
"enabled": true,
|
||||
"tau": 0.9,
|
||||
"tau_c": 0.9,
|
||||
"stable_checks": 3,
|
||||
"max_checks": 20,
|
||||
"min_fraction": 0.1,
|
||||
"boundary_delta": 0.02
|
||||
}
|
||||
},
|
||||
"slo": {
|
||||
"target_pass_rate": 0.95,
|
||||
"ttft_rule": {
|
||||
"kind": "step_ms",
|
||||
"buckets": [
|
||||
{
|
||||
"max_input_tokens": 4096,
|
||||
"threshold_ms": 2000
|
||||
},
|
||||
{
|
||||
"max_input_tokens": 32768,
|
||||
"threshold_ms": 4000
|
||||
},
|
||||
{
|
||||
"threshold_ms": 6000
|
||||
}
|
||||
]
|
||||
},
|
||||
"tpot_rule": {
|
||||
"kind": "fixed_ms",
|
||||
"threshold_ms": 50
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"low": 0.0,
|
||||
"high": 1.0,
|
||||
"tolerance": 0.001,
|
||||
"max_probes": 6,
|
||||
"sample_seed": 20260325
|
||||
},
|
||||
"llm": {
|
||||
"system_prompt": "Tune community vLLM 0.20.0 serving for Qwen3-30B-A3B. Start from the default vLLM engine configuration, use only launch-safe patches, and optimize request_rate_per_gpu under the configured SLO.",
|
||||
"max_history_trials": 8,
|
||||
"use_harness": true,
|
||||
"endpoint": {
|
||||
"provider": "codex",
|
||||
"model": "gpt-5.4",
|
||||
"stream": true,
|
||||
"api_key_env": "OPENAI_API_KEY",
|
||||
"timeout_s": 240
|
||||
}
|
||||
}
|
||||
}
|
||||
13
configs/examples/stopb_27b_ab/p1_tp1.json
Normal file
13
configs/examples/stopb_27b_ab/p1_tp1.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"observation": "baseline TP1 (deployed flags)",
|
||||
"diagnosis": "deterministic TP A/B point",
|
||||
"config_patch": {
|
||||
"env_patch": {},
|
||||
"flag_patch": {}
|
||||
},
|
||||
"expected_effects": [
|
||||
"measure peak request_rate_per_gpu at this topology"
|
||||
],
|
||||
"why_not_previous_failures": "n/a",
|
||||
"should_stop": false
|
||||
}
|
||||
15
configs/examples/stopb_27b_ab/p2_tp2.json
Normal file
15
configs/examples/stopb_27b_ab/p2_tp2.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"observation": "TP2",
|
||||
"diagnosis": "deterministic TP A/B point",
|
||||
"config_patch": {
|
||||
"env_patch": {},
|
||||
"flag_patch": {
|
||||
"tensor-parallel-size": 2
|
||||
}
|
||||
},
|
||||
"expected_effects": [
|
||||
"measure peak request_rate_per_gpu at this topology"
|
||||
],
|
||||
"why_not_previous_failures": "n/a",
|
||||
"should_stop": false
|
||||
}
|
||||
15
configs/examples/stopb_27b_ab/p3_tp4.json
Normal file
15
configs/examples/stopb_27b_ab/p3_tp4.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"observation": "TP4",
|
||||
"diagnosis": "deterministic TP A/B point",
|
||||
"config_patch": {
|
||||
"env_patch": {},
|
||||
"flag_patch": {
|
||||
"tensor-parallel-size": 4
|
||||
}
|
||||
},
|
||||
"expected_effects": [
|
||||
"measure peak request_rate_per_gpu at this topology"
|
||||
],
|
||||
"why_not_previous_failures": "n/a",
|
||||
"should_stop": false
|
||||
}
|
||||
@@ -141,8 +141,8 @@ New comparable studies:
|
||||
|
||||
| Variant | Study ID | Status |
|
||||
| --- | --- | --- |
|
||||
| no-harness baseline | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` | running first |
|
||||
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` | queued to run after baseline |
|
||||
| no-harness baseline | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` | completed |
|
||||
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` | completed |
|
||||
|
||||
Both specs set:
|
||||
|
||||
@@ -152,4 +152,4 @@ Both specs set:
|
||||
- SLO: TTFT p95 <= 4000ms, TPOT p95 <= 25ms, target pass rate 0.95
|
||||
- search: full range, `inherit_incumbent_floor=false`
|
||||
|
||||
The no-harness baseline is running in tmux session `qwen27b-gpu8-noharness-20260513`. The harness run should only be started after the no-harness baseline finishes or reaches a sufficient early comparison point, because both need the full GPU host and should not run concurrently.
|
||||
Results: harness best `0.2696 req/s/GPU` (TP=4, MBT=7680) vs no-harness best `0.1233 req/s/GPU` (prefix-caching=false), a **+118.6%** improvement. Full analysis in `qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-20260513.md`.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# qwen235b Thinking Prefill Harness Ablation, 2026-05-10
|
||||
|
||||
**Superseded** by `qwen235b-thinking-prefill-ttft-3s6s9s-20260514.md` (updated SLO thresholds, 8-GPU setup). This document is retained for reference only.
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# qwen235b Thinking Prefill Harness Ablation (TTFT 3s/6s/9s)
|
||||
|
||||
Date: 2026-05-14 / 2026-05-15
|
||||
|
||||
Supersedes: `qwen235b-thinking-prefill-ttft-20260510.md` (different SLO thresholds).
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`
|
||||
- Engine: internal vLLM at `/usr/local/bin/vllm`
|
||||
- Model: `/home/admin/resource/model/464482ce.qwen3-235b-a22b/256k-0717`
|
||||
- Trace window: `thinking_w20260327_1000`
|
||||
- Request mode: chat, with `completion_tokens_override=1` for prefill-only measurement
|
||||
- SLO: TTFT-only stepped p95 pass target, target pass rate `0.95`
|
||||
- input tokens `<=4096`: `3000 ms`
|
||||
- input tokens `<=32768`: `6000 ms`
|
||||
- otherwise: `9000 ms`
|
||||
- GPU env: `CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7` (8x H20)
|
||||
- Baseline topology: `TP=4`
|
||||
- LLM: `gpt-5.4`
|
||||
- Code: profile-driven harness planner, post GPU-visibility fix (`5c2958e`+)
|
||||
|
||||
## Studies
|
||||
|
||||
| Variant | Study ID | search.high |
|
||||
| --- | --- | ---: |
|
||||
| no-harness | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-12iter-noharness-minprompt-gpt54-20260514` | 0.125 |
|
||||
| harness | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-12iter-harness-profileplanner-gpt54-20260514` | 0.125 |
|
||||
| harness (high=0.25) | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-high025-12iter-harness-profileplanner-gpt54-20260515` | 0.25 |
|
||||
|
||||
The `harness (high=0.25)` run was added to test whether raising `search.high` lets the harness find a better runtime config after reaching the search ceiling at `0.125`.
|
||||
|
||||
## Result
|
||||
|
||||
Raw per-iteration performance for Fig18-style plot. Metric: `best_request_rate_per_gpu`. `NA` means the proposed config did not produce a feasible point. `fail` means engine launch failure. `stop` means harness stopped before launching another trial.
|
||||
|
||||
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness raw `perf[i]` | 0.1804 | fail | 0.1892 | fail | 0.1892 | 0.1804 | 0.2217 | 0.2029 | 0.2029 | 0.2029 | 0.1892 | 0.1804 |
|
||||
| harness raw `perf[i]` | 0.2029 | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop | stop |
|
||||
| harness (high=0.25) raw `perf[i]` | 0.2029 | 0.3921 | 0.3442 | 0.3921 | 0.3821 | 0.3821 | 0.3821 | 0.3688 | 0.3821 | 0.3821 | 0.3821 | 0.3821 |
|
||||
|
||||
| Variant | GPU trials | Best iter | Best req/s | Best req/s/GPU | Best config summary |
|
||||
| --- | ---: | ---: | ---: | ---: | --- |
|
||||
| no-harness | 12 | 7 | 0.8867 | 0.2217 | TP=4, MNS=112, MBT=7168 |
|
||||
| harness | 2 (stop) | 2 | 3.0900 | 0.3863 | TP=8 |
|
||||
| harness (high=0.25) | 12 | 2 | 3.1367 | **0.3921** | TP=8 |
|
||||
|
||||
Harness reached **+74.2%** over no-harness at iter 2. With `search.high=0.25`, the harness found `0.3921 req/s/GPU` (+76.8%).
|
||||
|
||||
## Incumbent Curve
|
||||
|
||||
Best-so-far request rate per GPU after each iteration.
|
||||
|
||||
| Variant | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness | 0.1804 | 0.1804 | 0.1892 | 0.1892 | 0.1892 | 0.1892 | 0.2217 | 0.2217 | 0.2217 | 0.2217 | 0.2217 | 0.2217 |
|
||||
| harness | 0.2029 | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop | stop |
|
||||
| harness (high=0.25) | 0.2029 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 |
|
||||
|
||||
## Trial Details
|
||||
|
||||
No-harness:
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.1804 | 0.1804 | completed | baseline (TP=4) |
|
||||
| 2 | - | 0.1804 | launch fail | TP=4, EP=4, MNS=128 |
|
||||
| 3 | 0.1892 | 0.1892 | completed | MNS=96 |
|
||||
| 4 | - | 0.1892 | launch fail | TP=4, DP=2, EP off, MNS=96 |
|
||||
| 5 | 0.1892 | 0.1892 | completed | MNS=112 |
|
||||
| 6 | 0.1804 | 0.1892 | completed | MNS=112, MBT=9216 |
|
||||
| 7 | 0.2217 | 0.2217 | completed | MNS=112, MBT=7168 |
|
||||
| 8 | 0.2029 | 0.2217 | completed | MNS=112, MBT=6144 |
|
||||
| 9 | 0.2029 | 0.2217 | completed | MNS=120, MBT=7168 |
|
||||
| 10 | 0.2029 | 0.2217 | completed | TP=4, DP=1, EP off, MNS=108, MBT=7168 |
|
||||
| 11 | 0.1892 | 0.2217 | completed | MNS=112, MBT=7680 |
|
||||
| 12 | 0.1804 | 0.2217 | completed | MNS=112, MBT=6912 |
|
||||
|
||||
Harness (`search.high=0.125`):
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.2029 | 0.2029 | completed | baseline (TP=4) |
|
||||
| 2 | 0.3863 | 0.3863 | completed | TP=8 |
|
||||
| 3 | - | - | harness stop | search-high saturation (`sampling_u=0.123` vs `search.high=0.125`) |
|
||||
|
||||
Harness (`search.high=0.25`):
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.2029 | 0.2029 | completed | baseline (TP=4) |
|
||||
| 2 | 0.3921 | 0.3921 | completed | TP=8 |
|
||||
| 3 | 0.3442 | 0.3921 | completed | TP=8, chunked-prefill, MBT=32768 |
|
||||
| 4 | 0.3921 | 0.3921 | completed | TP=8, MBT=12288 |
|
||||
| 5 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=16384 |
|
||||
| 6 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=14336 |
|
||||
| 7 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=10240 |
|
||||
| 8 | 0.3688 | 0.3921 | completed | TP=8, EP off, MBT=11776 |
|
||||
| 9 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=13312 |
|
||||
| 10 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=7168 |
|
||||
| 11 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=12032 |
|
||||
| 12 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=12800 |
|
||||
|
||||
## Interpretation
|
||||
|
||||
No-harness never attempted TP=8. It stayed on the TP=4 baseline, encountered two launch failures (EP=4 and DP=2), and spent all remaining trials on runtime knob tuning within the TP=4 family. Its best finding was `MNS=112, MBT=7168` at iter 7 (`0.2217 req/s/GPU`).
|
||||
|
||||
Harness identified `ttft_prefill` as the dominant bottleneck from the baseline trial and immediately proposed TP=8 as the first topology move. This is the correct direction for a prefill-only workload with heavy-tail prompts (p95 ~19.7k tokens, p99 ~30k tokens).
|
||||
|
||||
With `search.high=0.125`, the harness stopped at iter 2 because the incumbent's best feasible `sampling_u=0.123` was within one search resolution of `search.high`. With `search.high=0.25`, the harness continued for 12 trials but the best remained iter 2 (`TP=8, default MBT`). The additional 10 trials explored MBT variations on TP=8 but none improved per-GPU throughput. This confirms the 2-trial harness result was already at or near the local optimum.
|
||||
|
||||
The gap between harness and no-harness (`+76.8%`) comes entirely from topology: TP=8 doubles the per-GPU prefill compute bandwidth compared to TP=4, which directly reduces TTFT and allows higher admitted request rates under the stepped TTFT SLO.
|
||||
|
||||
## Comparison with Previous Run (2026-05-10)
|
||||
|
||||
The 2026-05-10 run used different SLO thresholds and is documented in `qwen235b-thinking-prefill-ttft-20260510.md`. The core finding is consistent: harness finds TP=8 at iter 2-3 while no-harness gets stuck on TP=4 runtime tuning.
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Date: 2026-05-10
|
||||
|
||||
**Superseded** by `qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-20260513.md` (corrected 8-GPU setup). This document used `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7` (7 GPUs) and is retained for reference only.
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0` (`172.27.114.84`)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Qwen27B Chat 0-8k Harness Ablation (8-GPU)
|
||||
|
||||
Date: 2026-05-13
|
||||
|
||||
Supersedes: `qwen27b-chat-0-8k-ttft4s-tpot25-20260510.md` (7-GPU / gpu3skip setup).
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`
|
||||
- Model: `/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`
|
||||
- Workload: `chat_w20260311_1000`, chat, 0-8k input window
|
||||
- SLO: TTFT <= 4000ms and TPOT <= 25ms, target pass rate = 0.95
|
||||
- Trial budget: 12 total tuning iterations per study
|
||||
- Search: `sampling_u` in `[0, 0.0625]`, tolerance `0.001`, max probes `6`
|
||||
- Execution: `python3 -m aituner.cli study tune ... --max-trials 12`
|
||||
- GPU env: `CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7` (8x H20)
|
||||
- Baseline topology: `TP=1`
|
||||
- LLM: `gpt-5.4`
|
||||
- Code: profile-driven harness planner, post GPU-visibility fix (`5c2958e`+)
|
||||
|
||||
## Studies
|
||||
|
||||
| Variant | Study ID |
|
||||
| --- | --- |
|
||||
| no-harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` |
|
||||
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` |
|
||||
|
||||
## Result
|
||||
|
||||
Raw per-iteration performance for Fig18-style plot. Metric: `best_request_rate_per_gpu` from that trial's own `result.json`. `NA` means the proposed config did not produce a feasible point. `fail` means engine launch failure.
|
||||
|
||||
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness raw `perf[i]` | 0.0650 | fail | fail | 0.0617 | 0.0650 | 0.1233 | 0.1050 | 0.1233 | 0.0650 | 0.0650 | 0.0617 | 0.1233 |
|
||||
| harness raw `perf[i]` | 0.0650 | 0.1992 | 0.2621 | 0.2056 | 0.1544 | 0.2696 | 0.2621 | 0.2621 | 0.2696 | 0.2621 | 0.2621 | 0.2621 |
|
||||
|
||||
| Variant | Best iter | Best request rate | Best request rate / GPU | Best config summary |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| no-harness | 6 | 0.1233 | 0.1233 | `enable-prefix-caching=false` |
|
||||
| harness | 6 | 1.0783 | **0.2696** | `tensor-parallel-size=4`, `max-num-batched-tokens=7680` |
|
||||
|
||||
Harness final best is **+118.6%** over no-harness.
|
||||
|
||||
## Incumbent Curve
|
||||
|
||||
Best-so-far request rate per GPU after each iteration.
|
||||
|
||||
| Variant | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 |
|
||||
| harness | 0.0650 | 0.1992 | 0.2621 | 0.2621 | 0.2621 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 |
|
||||
|
||||
## Trial Details
|
||||
|
||||
No-harness:
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.0650 | 0.0650 | completed | baseline |
|
||||
| 2 | - | 0.0650 | launch fail | `gpu-memory-utilization=0.94`, `max-num-batched-tokens=16384` |
|
||||
| 3 | - | 0.0650 | launch fail | `enable-chunked-prefill=false` |
|
||||
| 4 | 0.0617 | 0.0650 | completed | `data-parallel-size=2` |
|
||||
| 5 | 0.0650 | 0.0650 | completed | `block-size=32` |
|
||||
| 6 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false` |
|
||||
| 7 | 0.1050 | 0.1233 | completed | `enable-prefix-caching=false`, `block-size=32` |
|
||||
| 8 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-seqs=32` |
|
||||
| 9 | 0.0650 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-batched-tokens=4096` |
|
||||
| 10 | 0.0650 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-seqs=16` |
|
||||
| 11 | 0.0617 | 0.1233 | completed | `data-parallel-size=2`, `enable-prefix-caching=false` |
|
||||
| 12 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false` (+ torch compile off) |
|
||||
|
||||
Harness:
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.0650 | 0.0650 | completed | baseline |
|
||||
| 2 | 0.1992 | 0.1992 | completed | `tensor-parallel-size=2` |
|
||||
| 3 | 0.2621 | 0.2621 | completed | `tensor-parallel-size=4` |
|
||||
| 4 | 0.2056 | 0.2621 | completed | `tensor-parallel-size=8` |
|
||||
| 5 | 0.1544 | 0.2621 | completed | `tensor-parallel-size=4`, `data-parallel-size=2` |
|
||||
| 6 | 0.2696 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680` |
|
||||
| 7 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `enable-chunked-prefill=true`, `max-num-batched-tokens=12288` |
|
||||
| 8 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7424` |
|
||||
| 9 | 0.2696 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=64` |
|
||||
| 10 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=56` |
|
||||
| 11 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=60` |
|
||||
| 12 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=63` |
|
||||
|
||||
## Interpretation
|
||||
|
||||
No-harness never tested any TP change in 12 trials. It started from TP=1, encountered two early launch failures, then spent all remaining budget on runtime knobs (`enable-prefix-caching`, `block-size`, `max-num-seqs`, `max-num-batched-tokens`). Its best discovery was disabling prefix caching at iter 6, reaching only `0.1233 req/s/GPU`.
|
||||
|
||||
Harness systematically explored the TP frontier: iter 2 tested TP=2, iter 3 tested TP=4, iter 4 tested TP=8. The profile-driven planner identified `ttft_prefill` as the ranked bottleneck and proposed increasing TP as the primary relief action. After TP=4 proved best per-GPU, the harness tested TP=4/DP=2 (worse) then shifted to runtime refinement within the TP=4 family, settling on `max-num-batched-tokens=7680` as the marginal improvement.
|
||||
|
||||
The result demonstrates that topology exploration is critical for this workload: the no-harness LLM failed to discover TP>1 configurations entirely, while the harness reached the optimal TP=4 topology by iter 3 and refined it by iter 6.
|
||||
|
||||
## Comparison with Previous 7-GPU Run
|
||||
|
||||
The 7-GPU (`gpu3skip`) run from 2026-05-10 used `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7` and is not directly comparable. The harness result on 7-GPU was `0.2742 req/s/GPU` (TP=4, chunked-prefill, MBT=16384). On 8-GPU, the harness found a similar TP=4 optimum at `0.2696 req/s/GPU` with slightly different runtime tuning. The core finding is consistent: harness accelerates topology discovery and significantly outperforms no-harness.
|
||||
51
docs/harness-ablation/qwen27b-tp-sweep-20260616.md
Normal file
51
docs/harness-ablation/qwen27b-tp-sweep-20260616.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Qwen3.5-27B TP sweep under length-aware TTFT SLO — 2026-06-16
|
||||
|
||||
Branch `feat/two-stop`. Deterministic ground-truth A/B (proposal files, no LLM):
|
||||
TP1 vs TP2 vs TP4 on the dense Qwen3.5-27B (internal 256k, fp8, spec-decode) at
|
||||
0–8k chat, vLLM 0.11.1, H20, `replay_time_scale=1.0` (no smoke), Stop-A enabled,
|
||||
pinned to GPUs 2–7.
|
||||
|
||||
**SLO**: TTFT ≤ `4000 + 0.125·L_in` ms (= 4s + L_in/8k), TPOT ≤ 50 ms, pass ≥ 95%.
|
||||
|
||||
## Result
|
||||
|
||||
| config | best_u | raw req/s | req/s/GPU | pass | saturated |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| TP1 | 0.00195 | 0.065 | **0.065** | 1.00 | no |
|
||||
| TP2 | 0.0195 | 0.585 | **0.2925** | 0.96 | no |
|
||||
| TP4 | 0.123 | 3.63 | **≥0.908** | 0.98 | **yes (best_u≈high=0.125)** |
|
||||
|
||||
- **Per-GPU throughput rises sharply with TP for the dense 27B**: TP2 = 4.5× TP1,
|
||||
TP4 ≥ 14× TP1. Opposite of the MoE Qwen3-30B-A3B (TP1 best per-GPU) — confirms the
|
||||
dense-vs-MoE distinction.
|
||||
- **Mechanism**: TP1 is TPOT-bound — one H20 cannot decode a 27B under 50 ms/token
|
||||
once the batch grows, so it saturates at ~0.065 req/s/GPU. Loosening TTFT (2s→4-5s)
|
||||
did *not* change TP1 (still 0.065), confirming TPOT — not TTFT — is TP1's binding
|
||||
constraint. Each TP doubling speeds decode+prefill enough to more than recover the
|
||||
added GPUs.
|
||||
- **TP4 saturated** the offered-load ceiling (`best_u=0.123 ≈ 0.125`): still feasible
|
||||
after ~the whole trace, so 0.908 is a lower bound. True peak (and TP8) need a
|
||||
raised `search.high` to measure.
|
||||
|
||||
## Process findings (fed back into the harness)
|
||||
|
||||
- **Bug fixed**: a request exceeding `request_timeout_s` raised a raw `TimeoutError`
|
||||
mid-stream that escaped `_run_one_request` and crashed the whole trial; now wrapped
|
||||
as `HttpClientError` (failed request, not failed trial). Commit `2fcaf80`.
|
||||
- **Open gap**: killing a `study tune` run orphans the `VLLM::EngineCore` workers
|
||||
(SIGTERM/SIGKILL of the loop doesn't tear down the engine), which twice left leaked
|
||||
GPU memory on GPUs 0/1 (dead PIDs still pinning KV, only clearable via root
|
||||
`nvidia-smi --gpu-reset`). Fix: SIGTERM handler in the CLI loop + make
|
||||
`_terminate_process_tree` match `EngineCore` workers, not just `vllm serve`.
|
||||
- Experiment hygiene: scale=1.0 makes each probe take real arrival time; `search.high`
|
||||
must bracket the config's boundary (too wide wastes probes on a low-capacity config;
|
||||
too low saturates a high-capacity one), and `request_timeout_s` must be modest so
|
||||
overloaded probes drain fast.
|
||||
|
||||
## Next
|
||||
|
||||
- Re-measure TP4 (and TP8) with `search.high` raised (e.g. 0.5) to find the true peak
|
||||
per-GPU and the TP knee.
|
||||
- Run the Stop-B agentic loop on this 27B stack: unlike the 30B (baseline already
|
||||
optimal), here the loop should climb TP1→TP2→TP4 and stop — a real improving
|
||||
trajectory (the original Phase-5 "A" goal).
|
||||
121
docs/harness-ablation/stop-a-validation-20260615.md
Normal file
121
docs/harness-ablation/stop-a-validation-20260615.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Stop-A validation (Phase 3) — 2026-06-15
|
||||
|
||||
Branch `feat/two-stop`. Stop-A = truncate each probe's replay once the offered
|
||||
L-C-A of the replayed prefix converges to the full set (pure L-C-A criterion +
|
||||
C-gate). This note records the CPU calibration and the GPU fidelity check.
|
||||
|
||||
## 1. Calibration (CPU, no serving)
|
||||
|
||||
`scripts/stop_a_calibration.py` on the dash0 0321 10:00–10:10 windows:
|
||||
|
||||
| dim | chat (19239 req, hit≈7%) | coder (2451 req, structured reuse) |
|
||||
| --- | --- | --- |
|
||||
| A | ≥0.95 by frac 0.10 | fast |
|
||||
| L | ≥0.96 from frac 0.05 | 0.05=0.75 (heavy tail) → ≥0.94 by 0.20 |
|
||||
| **C (slowest)** | noisy, dips (0.50→0.885, 0.55→0.835), stable ≥0.92 only ~0.85 | smooth, stable ≥0.92 by ~0.70 |
|
||||
|
||||
Stop fraction (τ_L=τ_A=0.90, W=3):
|
||||
|
||||
| τ_c | chat | coder |
|
||||
| --- | --- | --- |
|
||||
| 0.85 | 0.45 (273s) | 0.45 (255s) |
|
||||
| 0.90 | 0.70 (423s) | 0.55 (318s) |
|
||||
| 0.92 | 0.85 (513s) | 0.70 (411s) |
|
||||
|
||||
Findings:
|
||||
- **C is the slowest dimension in both workloads** — reproduces paper §5.2 / Fig 9.
|
||||
- **What makes C hard to call converged is signal *noise*, not reuse magnitude.**
|
||||
Low-reuse chat has a sparse/spiky ideal-hit-length series, so its C similarity
|
||||
oscillates and is *harder* to stabilize than the structured, higher-reuse coder.
|
||||
Consequence: a strict τ_c (0.92) gives chat only ~15% saving. A more robust C
|
||||
feature for the low-reuse regime is future work.
|
||||
|
||||
## 2. GPU fidelity check (Qwen3-30B-A3B, vLLM 0.11.1, H20)
|
||||
|
||||
One full-window run (`adaptive_stop` disabled, `replay_time_scale=1.0`, window
|
||||
`chat_w20260311_1000`, 0–8k, out=128), then `scripts/stop_a_validate.py`
|
||||
recomputes each probe's convergence prefix and compares the truncated verdict to
|
||||
the full verdict — so a single GPU run validates truncation fidelity (no second run).
|
||||
|
||||
Trial result: best feasible `sampling_u=0.078125`, request_rate **2.30 req/s**,
|
||||
pass_rate 0.973.
|
||||
|
||||
Per-probe verdict (τ=0.9):
|
||||
|
||||
| τ_c | verdict matches | mean replay saved |
|
||||
| --- | --- | --- |
|
||||
| 0.85 | 3/4 | 54% |
|
||||
| 0.90 | 3/4 | 52% |
|
||||
| 0.92 | 3/4 | 38% |
|
||||
|
||||
The mismatch is the same probe at every τ_c — the feasibility knee `0.08594`:
|
||||
|
||||
```
|
||||
thresh full_pass prefix_pass full_feas prefix_feas
|
||||
0.08594 0.946 0.956–0.961 False True <- mismatch
|
||||
0.07812 0.973 0.987–0.990 True True
|
||||
0.06250 0.986 1.000 True True
|
||||
0.09375 0.268 0.49–0.54 False False
|
||||
```
|
||||
|
||||
## 3. Interpretation
|
||||
|
||||
- **Stop-A works and saves ~50% of replay** (vs the full 600 s window) while
|
||||
preserving 3/4 probe verdicts. (The paper's ~70% is vs a 30-min fixed baseline;
|
||||
our baseline is the 600 s window, so the percentages are not directly comparable.)
|
||||
- **The one failure is a boundary false-positive at the feasibility knee.** At
|
||||
`0.08594` the full window is 0.946 (just below the 0.95 SLO) but the prefix is
|
||||
0.956–0.961 (just above): the *second half* of the window degraded — engine-state
|
||||
drift (KV fill / fragmentation / later-arriving harder requests) that the
|
||||
*offered* L-C-A cannot see. The C-gate did not help because offered-C had
|
||||
converged; the divergence is in the measured pass-rate, not in C.
|
||||
- If Stop-A were enabled, the binary search would accept `0.08594`, overestimating
|
||||
the peak sustainable rate by one binary step (~10%).
|
||||
|
||||
**This is the boundary jitter we accepted when choosing the pure-L-C-A criterion.**
|
||||
The data now argues for revisiting the previously-declined **SLO-boundary guard**:
|
||||
keep replaying while the measured pass-rate is within ±δ of the target, even after
|
||||
L-C-A converges. It targets exactly this knee case at low extra cost (it only
|
||||
extends replay on probes sitting on the feasibility boundary). Recommend adding it
|
||||
as a small Stop-A enhancement before enabling Stop-A in production studies.
|
||||
|
||||
## 4. SLO-boundary guard (implemented + validated)
|
||||
|
||||
Added `trace.adaptive_stop.boundary_delta` (default 0.02): when a truncated probe's
|
||||
measured pass-rate lands within ±δ of the SLO target, re-measure on the full window
|
||||
and use that verdict. Re-ran the same config with `adaptive_stop` enabled
|
||||
(τ=0.9, τ_c=0.90, δ=0.02):
|
||||
|
||||
| threshold | feasible | pass | selected | replayed | boundary_extended |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 0.06250 | True | 1.000 | 1086 | 487 (45%) | — |
|
||||
| 0.09375 | False | 0.444 | 1656 | 822 (50%) | — |
|
||||
| 0.07812 | True | 0.994 | 1378 | 682 (49%) | — |
|
||||
| 0.08594 | **False** | 0.947 | 1523 | **1523 (100%)** | **True** |
|
||||
|
||||
Result: best feasible `sampling_u=0.078125` (rate 2.30 req/s) — **identical to the
|
||||
full-replay baseline**. The guard fired on exactly the one knee probe and
|
||||
re-measured it to the correct infeasible verdict; the other three probes truncated
|
||||
to ~45–50%. Net replayed 3514/5643 requests ≈ **38% replay saved on this trial
|
||||
while recovering the correct peak rate** (no one-step overestimate).
|
||||
|
||||
**Conclusion: Stop-A with the boundary guard is correct (verdict matches full
|
||||
replay) and still saves replay time. Safe to enable.** Configs:
|
||||
`dash0_qwen30b_a3b_stopA_fulldata.json` (OFF baseline) and
|
||||
`dash0_qwen30b_a3b_stopA_on.json` (ON).
|
||||
|
||||
## Repro
|
||||
|
||||
```
|
||||
# calibration
|
||||
PYTHONPATH=src python3 scripts/stop_a_calibration.py \
|
||||
--jsonl <DIR>/qwen_chat_blksz_64_032109-032111.jsonl --block-size 64 \
|
||||
--window-start 3600 --window-end 4200 --gpu-count 8 --label chat
|
||||
# GPU run + fidelity
|
||||
PYTHONPATH=src python3 -m aituner.cli study tune \
|
||||
--spec configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json \
|
||||
--store-root .aituner/stopA-fulldata --max-trials 1
|
||||
PYTHONPATH=src python3 scripts/stop_a_validate.py \
|
||||
--spec configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json \
|
||||
--store-root .aituner/stopA-fulldata --tau 0.9 --tau-c 0.90
|
||||
```
|
||||
86
docs/harness-ablation/stop-b-e2e-20260615.md
Normal file
86
docs/harness-ablation/stop-b-e2e-20260615.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Stop-B end-to-end validation (Phase 5) — 2026-06-15
|
||||
|
||||
Branch `feat/two-stop`. Real agentic loop on dash0: Qwen3-30B-A3B / vLLM 0.11.1 /
|
||||
8×H20, `gpt-5.4` (via codex/prism) proposing configs, Stop-A enabled to accelerate
|
||||
each evaluation, `use_harness=True` so the Stop-B deterministic validator + LLM-stop
|
||||
veto are active. Config `dash0_qwen30b_a3b_stopB_e2e.json`, `search.high=1.0`,
|
||||
`max_probes=6`, `--max-trials 8`.
|
||||
|
||||
## Two stop paths exercised
|
||||
|
||||
**Run A (`search.high=0.125`)** — the default config already saturates the offered-load
|
||||
search range, so Stop-B fired immediately via the **search-high-saturation** path:
|
||||
`stop_authorized_by: validator`, reason *"the incumbent's highest measured probe is
|
||||
feasible and within the binary-search resolution of search.high."* Correct
|
||||
measurement-ceiling stop (no point proposing configs when the load range, not the
|
||||
config, is the bound).
|
||||
|
||||
**Run B (`search.high=1.0`)** — forces a real multi-iteration search:
|
||||
|
||||
| trial | TP | DP | EP | feasible | raw req/s | **req/s/GPU** | source |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 0001 | 1 | 1 | 1 | ✓ | 2.90 | **2.900** | baseline |
|
||||
| 0002 | 2 | 1 | 1 | ✓ | 4.42 | 2.208 | harness TP-seed |
|
||||
| 0003 | 2 | 1 | 2 | ✗ launch-fail | — | — | harness (EP) |
|
||||
| 0004 | 1 | 2 | 1 | ✓ | 4.42 | 2.208 | LLM (after veto) |
|
||||
| 0005 | 2 | 2 | 1 | ✓ | 8.37 | 2.092 | harness |
|
||||
| 0006 | 2 | 2 | 2 | ✗ launch-fail | — | — | harness (EP) |
|
||||
| 0007 | 4 | 1 | 1 | ✓ | 8.37 | 2.092 | LLM |
|
||||
| (0008) | — | — | — | **STOP** | — | — | LLM stop, honored after veto budget |
|
||||
|
||||
Incumbent: **trial-0001 (TP1), 2.90 req/s/GPU — never beaten.**
|
||||
|
||||
> **⚠️ The per-GPU trajectory above is NOT a valid benchmark — it validates only
|
||||
> the Stop-B *mechanics*.** Two confounds:
|
||||
> 1. **Trace-ceiling saturation.** TP2·DP2 and TP4 reached `best_sampling_u≈0.98`
|
||||
> (still feasible after consuming ~the whole window), so their *true* peak
|
||||
> per-GPU is higher than the 2.09 shown — we ran out of offered load to push
|
||||
> them to their boundary. Only TP1 (u=0.31), TP2 (u=0.48) and DP2 (u=0.48)
|
||||
> found real boundaries. The `sampling_u` axis maxes at the full trace, so any
|
||||
> config that sustains more than the window's offered rate cannot be measured.
|
||||
> 2. **Smoke regime.** This run inherited `replay_time_scale=0.1` +
|
||||
> `max_requests_per_probe=512` (README: convergence test, *not* a benchmark) —
|
||||
> compressed arrivals distort A and the 512 cap imposes a ~8.4 req/s ceiling.
|
||||
>
|
||||
> The below-ceiling TP1 (2.90) > TP2 (2.21) ordering *may* be real for this model
|
||||
> (Qwen3-30B-A3B is an MoE with ~3B active params → little compute per token → TP
|
||||
> adds all-reduce overhead with little benefit), which differs from the dense
|
||||
> Qwen3.5-27B where TP2 wins. But this run cannot establish it. A valid benchmark
|
||||
> needs `scale=1.0`, no cap, and enough offered-load headroom that strong configs
|
||||
> are not trace-saturated — see the 27B TP A/B follow-up.
|
||||
|
||||
## Phase-5 acceptance
|
||||
|
||||
- **No regression.** The primary metric `request_rate_per_gpu` stayed 2.90 the whole
|
||||
run. Scaling TP/DP raised *raw* throughput (4.42, 8.37) but lowered per-GPU
|
||||
efficiency (2.21, 2.09); the loop correctly kept the TP1 baseline as incumbent and
|
||||
never adopted a worse-per-GPU config. (Matches the paper: long-prompt, low-reuse
|
||||
chat prefers small TP for per-GPU efficiency.)
|
||||
- **Stop-B authority validated live.** At trial 4 the LLM tried to stop
|
||||
(`should_stop=true`); the deterministic validator **vetoed** it
|
||||
(`validator_did_not_authorize_stop`, `continue_harness_guided_search`), forcing one
|
||||
more confirmation (DP2, which also failed to beat baseline). After the budget, the
|
||||
LLM's later, well-justified stop was honored (`stop_authorized_by:
|
||||
llm_after_veto_budget`). The bounded veto behaved exactly as designed.
|
||||
- **Auditable reason chain.** Every stop/veto carries a diagnosis grounded in the
|
||||
measured evidence (e.g. *"increasing TP 1→2 lowers per-GPU efficiency even though
|
||||
token latency improves … EP is explicitly blocked by launch-failure evidence"*).
|
||||
- **Launch-failure robustness.** Two EP configs (trial-0003, 0006) failed to launch
|
||||
under vLLM 0.11.1; the harness recorded them as hard-negative evidence and the LLM
|
||||
explicitly stopped proposing EP.
|
||||
|
||||
## Notes / limitations
|
||||
|
||||
- For this workload the baseline (TP1) is already per-GPU optimal, so iterations-to-
|
||||
*best* = 1; the remaining trials are the loop *confirming* no config beats baseline
|
||||
before stopping. A workload with an under-tuned default would show an improving
|
||||
trajectory; this run validates the stop/no-regression machinery, not a tuning win.
|
||||
- The final stop came via `llm_after_veto_budget` (validator vetoed once, then
|
||||
deferred), not a pure deterministic validator stop — because the deterministic
|
||||
conditions (3-within-2%, saturation, validation-exhausted) did not cleanly fire
|
||||
when every trial was a distinct config with a distinct per-GPU rate. The validator
|
||||
acted as the *guard* (preventing premature stop), which is its designed role.
|
||||
- 7 trials > the paper's 3–6 average, inflated by the wider search range, 2 EP
|
||||
launch-failures, and the veto. Acceptable for a validation run.
|
||||
- LLM token: the non-interactive shell lacks `OPENAI_API_KEY`; export it from the
|
||||
codex `auth.json` (`~/.codex/auth.json`) before the run.
|
||||
64
docs/harness-ablation/stop-b-e2e-27b-20260616.md
Normal file
64
docs/harness-ablation/stop-b-e2e-27b-20260616.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Stop-B end-to-end on dense Qwen3.5-27B (the improving trajectory) — 2026-06-16
|
||||
|
||||
Branch `feat/two-stop`. Real `gpt-5.4` agentic loop (codex/prism), Stop-A enabled,
|
||||
length-aware TTFT SLO (4s + L_in/8k, TPOT ≤ 50 ms), vLLM 0.11.1, H20, GPUs 2–7,
|
||||
`replay_time_scale=1.0`, `search.high=0.25`, `inherit_incumbent_floor=true`.
|
||||
Config `dash0_qwen27b_stopB_loop.json`. Companion to the 30B run
|
||||
(`stop-b-e2e-20260616.md`); together they cover all Stop-B behaviors.
|
||||
|
||||
## Trajectory (incumbent = TP4 @ 1.00 req/s/GPU)
|
||||
|
||||
| iter | proposed by | config | per_gpu | adopted? |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | baseline | TP1 | 0.123 | incumbent |
|
||||
| 2 | gpt-5.4 | TP2 | 0.2925 (2.4×) | ✅ new incumbent |
|
||||
| 3 | gpt-5.4 | TP4 | **1.0012 (8.1×)** | ✅ new incumbent |
|
||||
| 4 | gpt-5.4 | TP4 + chunked-prefill + mbt=16384 | 0.942 | ❌ **worse → rejected** |
|
||||
| 5 | gpt-5.4 | TP2 + DP2 | (loop stopped before completing) | — |
|
||||
|
||||
(Run stopped manually after iter-4 — see "Why stopped" below. Incumbent preserved
|
||||
at TP4.)
|
||||
|
||||
## What this demonstrates (the piece the 30B run could not)
|
||||
|
||||
- **A genuine improving climb.** `gpt-5.4` + the harness raised per-GPU throughput
|
||||
TP1 → TP2 → TP4 (0.123 → 0.29 → 1.00, 8.1×), each step a correctly-diagnosed real
|
||||
gain: TP1 is TPOT-bound, so the agent scaled tensor-parallelism, then — once
|
||||
topology was won — pivoted to **runtime tuning on the winning family** (chunked
|
||||
prefill + larger batched tokens).
|
||||
- **No regression.** The runtime tweak (iter-4) measured *below* plain TP4
|
||||
(0.942 < 1.00), and the harness correctly **kept TP4 as the incumbent** rather than
|
||||
adopting the worse config — the core Stop-B guarantee, shown live.
|
||||
- Combined with the 30B run (search-high-saturation `validator`-authorized stop +
|
||||
premature-LLM-stop veto), every Stop-B behavior is now validated end-to-end:
|
||||
improving climb, correct bottleneck-driven proposals, no regression, deterministic
|
||||
stop authority, and the LLM-stop veto.
|
||||
|
||||
## Process wins / findings
|
||||
|
||||
- **SIGTERM teardown fix validated in practice.** This loop was stopped with a plain
|
||||
SIGTERM and the engine + EngineCore workers torn down cleanly — GPUs 2–7 freed, no
|
||||
orphan, no leaked memory (contrast: the pre-fix runs twice leaked GPU0/1). Commit
|
||||
`b17b213`.
|
||||
- **Timeout-as-failed-request fix** (`2fcaf80`) held — no trial crashed on
|
||||
request timeouts this run.
|
||||
|
||||
## Why stopped (efficiency finding — feeds next round)
|
||||
|
||||
The loop was stopped after iter-4 rather than run to an explicit Stop-B firing,
|
||||
because each TP4-family trial took ~3 h: at `scale=1.0`, infeasible high-θ probes
|
||||
each run to the **`early_stop_max_elapsed_s=900` cap** (`probe_elapsed_s>900`), and
|
||||
the primary+fallback binary search doubles the probe count. Stop-A truncates a
|
||||
*converged* replay but does not shortcut an *overloaded* probe that simply runs out
|
||||
the clock. **For a practical agentic loop at scale=1.0, lower `early_stop_max_elapsed_s`
|
||||
(≈300 s)** so overloaded probes die fast; consider also having an infeasible-and-
|
||||
overloaded probe early-stop on a fast lag/throughput signal rather than the elapsed
|
||||
cap. The convergence itself was already evident (iter-4's runtime tweak and the
|
||||
queued TP2+DP2 were not beating TP4).
|
||||
|
||||
## Next
|
||||
|
||||
- Lower the elapsed cap and (optionally) re-run to capture the explicit Stop-B stop
|
||||
on this 27B stack.
|
||||
- Land the deferred items: more robust C feature for the low-reuse regime; Stop-C
|
||||
cross-day retune trigger; §7 baselines (SCOOT/naive/community).
|
||||
106
docs/qwen27b-chat-0-8k-tpot25-16iter-20260506.md
Normal file
106
docs/qwen27b-chat-0-8k-tpot25-16iter-20260506.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# qwen27b-chat-0-8k TPOT25 16-Iter Harness Compare
|
||||
|
||||
## Goal
|
||||
|
||||
Rerun the internal vLLM Qwen3.5-27B chat 0-8k tuning comparison under a stricter
|
||||
TPOT SLO:
|
||||
|
||||
- no-harness: 16 tuning iterations;
|
||||
- harness: 16 tuning iterations, with permission to stop early if the harness
|
||||
convergence guard decides no further GPU trial is needed.
|
||||
|
||||
Both variants must be launched directly through AITuner. No state seeding,
|
||||
manual replay, or historical-result injection is allowed.
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`.
|
||||
- Hardware: 8 NVIDIA H20 GPUs.
|
||||
- Engine: internal vLLM at `/usr/local/bin/vllm`.
|
||||
- Model:
|
||||
`/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`.
|
||||
- Served model name: `qwen35-27b-aituner`.
|
||||
- Workload window: `chat_w20260311_1000`.
|
||||
- Trace path source: `/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json`.
|
||||
- Request mode: `chat`.
|
||||
- Input bucket: `0 <= input_length <= 8192`.
|
||||
- Replay scale: `1.0`.
|
||||
- Max concurrency: `32`.
|
||||
- Max requests per probe: unset, so each probe uses the full selected trace
|
||||
subset for its `sampling_u` threshold.
|
||||
- Restart engine after early stop: `true` for both variants. This is needed
|
||||
under TPOT25 because very slow infeasible probes can leave live HTTP requests
|
||||
in the engine after the SLO is already unrecoverable. Restarting keeps the
|
||||
next binary-search probe from being contaminated by previous in-flight work.
|
||||
- Search field: `sampling_u`.
|
||||
- Search range: `low=0.0`, `high=0.0625`.
|
||||
- Search probes: `max_probes=6`, `tolerance=0.001`.
|
||||
- Sampling seed: `20260325`.
|
||||
|
||||
## SLO
|
||||
|
||||
- Target pass rate: `0.95`.
|
||||
- TTFT rule:
|
||||
|
||||
| Input tokens | TTFT threshold |
|
||||
| ---: | ---: |
|
||||
| `<=4096` | `2000 ms` |
|
||||
| `<=32768` | `4000 ms` |
|
||||
| otherwise | `6000 ms` |
|
||||
|
||||
- TPOT rule: fixed `<=25 ms`.
|
||||
|
||||
## Specs
|
||||
|
||||
Remote generated specs:
|
||||
|
||||
- no-harness:
|
||||
`.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-noharness.json`
|
||||
- harness:
|
||||
`.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-harness.json`
|
||||
|
||||
The two specs were generated from
|
||||
`configs/examples/dash0_qwen27b_tight_slo_run4_0_8k.json`. After normalizing
|
||||
`study_id` and `llm.use_harness`, the JSON payloads compare equal. Therefore the
|
||||
only tuning-behavior difference between the formal comparison runs is whether
|
||||
the harness is enabled.
|
||||
|
||||
## Commands
|
||||
|
||||
No-harness:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src python3 -m aituner.cli study tune \
|
||||
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-noharness.json \
|
||||
--store-root .aituner-tight \
|
||||
--max-trials 16
|
||||
```
|
||||
|
||||
Harness:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src python3 -m aituner.cli study tune \
|
||||
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-harness.json \
|
||||
--store-root .aituner-tight \
|
||||
--max-trials 16
|
||||
```
|
||||
|
||||
## Run Log
|
||||
|
||||
- 2026-05-06 12:37 CST: generated both remote specs and verified that the only
|
||||
normalized difference is `llm.use_harness`.
|
||||
- 2026-05-06 12:37 CST: started no-harness in tmux session
|
||||
`qwen27b_tpot25_noharness_16iter_20260506`.
|
||||
- 2026-05-06 21:06 CST: stopped the initial no-harness pre-run before using it
|
||||
for comparison. It used `restart_engine_after_early_stop=false`; the first
|
||||
TP1 baseline probe already recorded `slo_pass_rate_unrecoverable`, but
|
||||
unfinished requests remained live in vLLM and would contaminate the next probe.
|
||||
- 2026-05-06 21:07 CST: generated the formal clean specs with
|
||||
`restart_engine_after_early_stop=true` for both variants and verified the
|
||||
normalized diff is still only `llm.use_harness`.
|
||||
- 2026-05-06 21:09 CST: started formal no-harness run in tmux session
|
||||
`qwen27b_tpot25_restart_noharness_16iter_20260506`.
|
||||
|
||||
## Results
|
||||
|
||||
Pending.
|
||||
@@ -92,17 +92,39 @@ def parse_args() -> argparse.Namespace:
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def stable_uniform(*, seed: int, window_id: str, index: int, row: dict[str, Any]) -> float:
|
||||
def resolve_session_root(row: dict[str, Any], root_of: dict[Any, Any]) -> Any:
|
||||
"""Resolve the session root chat_id for a trace row.
|
||||
|
||||
Sessions are multi-turn chains linked via parent_chat_id (turn>1 points to the
|
||||
parent turn's chat_id, the root turn has parent_chat_id=-1). Because parent
|
||||
turns precede their children in time, a single streaming pass that records
|
||||
chat_id -> root resolves the full chain. Rows whose parent is not yet known
|
||||
(e.g. it fell outside the materialized span) fall back to the parent id so
|
||||
siblings still group together.
|
||||
"""
|
||||
chat_id = row.get("chat_id")
|
||||
parent = row.get("parent_chat_id")
|
||||
parent_is_root = (
|
||||
parent is None
|
||||
or (isinstance(parent, (int, float)) and not isinstance(parent, bool) and int(parent) < 0)
|
||||
)
|
||||
root = chat_id if parent_is_root else root_of.get(parent, parent)
|
||||
if chat_id is not None:
|
||||
root_of[chat_id] = root
|
||||
return root
|
||||
|
||||
|
||||
def session_uniform(*, seed: int, window_id: str, session_root: Any) -> float:
|
||||
"""Deterministic per-session uniform score in [0, 1).
|
||||
|
||||
All turns of a session share one score, so thresholding sampling_u keeps or
|
||||
drops whole sessions and preserves intra-session prefix (KV-cache) reuse.
|
||||
"""
|
||||
payload = json.dumps(
|
||||
{
|
||||
"seed": seed,
|
||||
"window_id": window_id,
|
||||
"index": index,
|
||||
"timestamp": row.get("timestamp"),
|
||||
"input_length": row.get("input_length"),
|
||||
"output_length": row.get("output_length"),
|
||||
"chat_id": row.get("chat_id"),
|
||||
"turn": row.get("turn"),
|
||||
"session_root": session_root,
|
||||
},
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
@@ -241,12 +263,16 @@ def materialize_windows(
|
||||
bucket = grouped[(trace_path, prompt_path)]
|
||||
bucket.sort(key=lambda item: (float(item["window_start"]), str(item["window_id"])))
|
||||
matched_rows = 0
|
||||
root_of: dict[Any, Any] = {}
|
||||
with trace_path.open() as trace_handle, prompt_path.open() as prompt_handle:
|
||||
for trace_raw, prompt_raw in zip(trace_handle, prompt_handle):
|
||||
trace_raw = trace_raw.strip()
|
||||
if not trace_raw:
|
||||
continue
|
||||
trace_row = json.loads(trace_raw)
|
||||
# Resolve session linkage for every row (even unmatched ones)
|
||||
# so multi-turn chains crossing the window edge still group.
|
||||
session_root = resolve_session_root(trace_row, root_of)
|
||||
timestamp = float(trace_row.get("timestamp") or 0.0)
|
||||
matched_window: dict[str, Any] | None = None
|
||||
for window in bucket:
|
||||
@@ -267,11 +293,11 @@ def materialize_windows(
|
||||
start = float(matched_window["window_start"])
|
||||
out["source_timestamp"] = timestamp
|
||||
out["timestamp"] = timestamp - start
|
||||
out["sampling_u"] = stable_uniform(
|
||||
out["session_root"] = session_root
|
||||
out["sampling_u"] = session_uniform(
|
||||
seed=sample_seed,
|
||||
window_id=window_id,
|
||||
index=stats_by_window[window_id].num_requests,
|
||||
row=merged,
|
||||
session_root=session_root,
|
||||
)
|
||||
handles[window_id].write(json.dumps(out, ensure_ascii=False) + "\n")
|
||||
stats_by_window[window_id].record(out)
|
||||
@@ -311,7 +337,7 @@ def build_output_window(
|
||||
output["num_excluded_too_long"] = 0
|
||||
output["sampling_u_field"] = "sampling_u"
|
||||
output["sampling_seed"] = int(sample_seed)
|
||||
output["sampling_strategy"] = "fixed_uniform_score"
|
||||
output["sampling_strategy"] = "session_coherent_uniform_score"
|
||||
output["first_request_ts"] = stats.first_request_ts
|
||||
output["last_request_ts"] = stats.last_request_ts
|
||||
output["first_request_index"] = stats.first_request_index
|
||||
|
||||
@@ -10,6 +10,7 @@ from aituner.llm import (
|
||||
load_capability_profile,
|
||||
parse_proposal_text,
|
||||
)
|
||||
from aituner.lca import build_study_workload_profile
|
||||
from aituner.spec import load_study_spec
|
||||
from aituner.store import StudyStore
|
||||
from aituner.trace import load_trace_requests, summarize_window
|
||||
@@ -89,6 +90,7 @@ def main() -> int:
|
||||
window_summary=summarize_window(requests, window),
|
||||
state=state,
|
||||
capability_profile=capability_profile,
|
||||
workload_profile=build_study_workload_profile(study, requests, window),
|
||||
)
|
||||
prompt_name = f"prompt-{state.next_trial_index:04d}"
|
||||
store.write_prompt(study.study_id, prompt_name, prompt)
|
||||
|
||||
128
scripts/stop_a_calibration.py
Normal file
128
scripts/stop_a_calibration.py
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stop-A calibration: print the offered-L-C-A convergence curve for a raw trace window.
|
||||
|
||||
The convergence of prefix-vs-full L-C-A is a deterministic property of the trace
|
||||
metadata (lengths, hash_ids, arrivals), so this runs on CPU without serving the
|
||||
model. Use it to pick tau / tau_c / stable_checks and to compare how late the C
|
||||
dimension converges across workloads (e.g. low-reuse chat vs high-reuse coder).
|
||||
|
||||
Example:
|
||||
PYTHONPATH=src python3 scripts/stop_a_calibration.py \
|
||||
--jsonl /dashscope/.../qwen_chat_blksz_64_032109-032111.jsonl \
|
||||
--block-size 64 --window-start 3600 --window-end 4200 --gpu-count 8 --label chat
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from aituner.lca import find_convergence_prefix, resolve_length_mode
|
||||
from aituner.trace import TraceRequest, WindowRecord
|
||||
|
||||
|
||||
def _session_root(row: dict, root_of: dict) -> object:
|
||||
chat_id = row.get("chat_id")
|
||||
parent = row.get("parent_chat_id")
|
||||
parent_is_root = parent is None or (
|
||||
isinstance(parent, (int, float)) and not isinstance(parent, bool) and int(parent) < 0
|
||||
)
|
||||
root = chat_id if parent_is_root else root_of.get(parent, parent)
|
||||
if chat_id is not None:
|
||||
root_of[chat_id] = root
|
||||
return root
|
||||
|
||||
|
||||
def load_window(jsonl: Path, *, window_start: float, window_end: float) -> list[TraceRequest]:
|
||||
root_of: dict = {}
|
||||
requests: list[TraceRequest] = []
|
||||
with jsonl.open(encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
row = json.loads(line)
|
||||
_session_root(row, root_of) # keep chain complete even outside the window
|
||||
ts = float(row.get("timestamp") or 0.0)
|
||||
if not (window_start <= ts < window_end):
|
||||
continue
|
||||
hash_ids = row.get("hash_ids")
|
||||
requests.append(
|
||||
TraceRequest(
|
||||
row_id=str(row.get("chat_id")),
|
||||
arrival_s=ts - window_start,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=int(row.get("input_length") or 0),
|
||||
completion_tokens_hint=int(row.get("output_length") or 0),
|
||||
metadata={"hash_ids": hash_ids if isinstance(hash_ids, list) else None},
|
||||
)
|
||||
)
|
||||
requests.sort(key=lambda item: item.arrival_s)
|
||||
return requests
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--jsonl", type=Path, required=True)
|
||||
ap.add_argument("--block-size", type=int, required=True)
|
||||
ap.add_argument("--window-start", type=float, default=3600.0)
|
||||
ap.add_argument("--window-end", type=float, default=4200.0)
|
||||
ap.add_argument("--gpu-count", type=int, default=8)
|
||||
ap.add_argument("--length-mode", default="total")
|
||||
ap.add_argument("--label", default="")
|
||||
ap.add_argument("--tau", type=float, default=0.9)
|
||||
ap.add_argument("--max-checks", type=int, default=20)
|
||||
args = ap.parse_args()
|
||||
|
||||
requests = load_window(
|
||||
args.jsonl, window_start=args.window_start, window_end=args.window_end
|
||||
)
|
||||
window = WindowRecord(
|
||||
window_id=args.label or args.jsonl.stem,
|
||||
trace_path=args.jsonl,
|
||||
trace_type=args.label or "chat",
|
||||
window_start=0.0,
|
||||
window_end=float(args.window_end - args.window_start),
|
||||
source_payload={"block_size": args.block_size},
|
||||
)
|
||||
mode = resolve_length_mode(length_mode=args.length_mode)
|
||||
rows_with_hash = sum(1 for r in requests if r.metadata.get("hash_ids"))
|
||||
print(
|
||||
f"[{args.label}] requests={len(requests)} rows_with_hash_ids={rows_with_hash} "
|
||||
f"window={args.window_start:.0f}-{args.window_end:.0f}s block_size={args.block_size}"
|
||||
)
|
||||
|
||||
# Full curve (tau_c high so it never short-circuits; we read the curve directly).
|
||||
point = find_convergence_prefix(
|
||||
requests, window, gpu_count=args.gpu_count, length_mode=mode,
|
||||
tau=args.tau, tau_c=1.01, stable_checks=10_000, max_checks=args.max_checks,
|
||||
min_fraction=0.05,
|
||||
)
|
||||
print(" frac time_s L C A")
|
||||
for c in point.checks:
|
||||
s = c["family_similarity"]
|
||||
print(
|
||||
f" {c['fraction']:.2f} {c['time_s']:7.1f} "
|
||||
f"{s['L']:.3f} {s['C']:.3f} {s['A']:.3f}"
|
||||
)
|
||||
|
||||
# Stop fraction at candidate tau_c values (L,A >= tau, C >= tau_c, stable for W=3).
|
||||
print(" -- stop fraction (tau_L=tau_A=%.2f, W=3) --" % args.tau)
|
||||
for tau_c in (0.85, 0.90, 0.92, 0.95):
|
||||
p = find_convergence_prefix(
|
||||
requests, window, gpu_count=args.gpu_count, length_mode=mode,
|
||||
tau=args.tau, tau_c=tau_c, stable_checks=3, max_checks=args.max_checks,
|
||||
min_fraction=0.05,
|
||||
)
|
||||
verdict = (
|
||||
f"stop@frac={p.fraction:.2f} t={p.stop_time_s:.0f}s"
|
||||
if p.converged
|
||||
else "NEVER (replays full window)"
|
||||
)
|
||||
print(f" tau_c={tau_c:.2f}: {verdict}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
101
scripts/stop_a_validate.py
Normal file
101
scripts/stop_a_validate.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate Stop-A truncation fidelity from a full-replay trial's probe_details.
|
||||
|
||||
Given a completed trial that replayed the full window (adaptive_stop disabled), for
|
||||
each probe recompute the L-C-A convergence prefix and compare the feasibility
|
||||
verdict / pass-rate of the truncated prefix against the full probe. This answers:
|
||||
"would Stop-A have changed the measured peak-sustainable-rate?" using only the one
|
||||
full run (no second GPU run needed).
|
||||
|
||||
Example:
|
||||
PYTHONPATH=src python3 scripts/stop_a_validate.py \
|
||||
--spec configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json \
|
||||
--store-root .aituner/stopA-fulldata --tau 0.9 --tau-c 0.90
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from aituner.lca import find_convergence_prefix, resolve_length_mode
|
||||
from aituner.spec import load_study_spec
|
||||
from aituner.trace import load_trace_requests, select_requests_for_threshold
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--spec", type=Path, required=True)
|
||||
ap.add_argument("--store-root", type=Path, required=True)
|
||||
ap.add_argument("--tau", type=float, default=0.9)
|
||||
ap.add_argument("--tau-c", type=float, default=0.90)
|
||||
ap.add_argument("--stable-checks", type=int, default=3)
|
||||
ap.add_argument("--target-pass-rate", type=float, default=0.95)
|
||||
args = ap.parse_args()
|
||||
|
||||
study = load_study_spec(args.spec)
|
||||
window, requests = load_trace_requests(study, study_spec_path=args.spec)
|
||||
mode = resolve_length_mode(request_mode=study.trace.request_mode)
|
||||
gpu_count = study.hardware.gpu_count
|
||||
|
||||
detail_files = sorted(args.store_root.glob("*/trials/*/probe_details.jsonl"))
|
||||
if not detail_files:
|
||||
print(f"no probe_details.jsonl under {args.store_root}")
|
||||
return 1
|
||||
|
||||
print(f"target_pass_rate={args.target_pass_rate} tau={args.tau} tau_c={args.tau_c}")
|
||||
print(
|
||||
"thresh n_full stop_idx frac full_pass prefix_pass "
|
||||
"full_feas prefix_feas verdict_match"
|
||||
)
|
||||
mismatches = 0
|
||||
total = 0
|
||||
saved_fractions = []
|
||||
for detail_file in detail_files:
|
||||
with detail_file.open(encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
probe = json.loads(line)
|
||||
threshold = float(probe["threshold"])
|
||||
outcomes = probe.get("outcomes") or []
|
||||
# arrival-ordered outcomes that carry an arrival_s and verdict
|
||||
ordered = sorted(
|
||||
(o for o in outcomes if o.get("arrival_s") is not None),
|
||||
key=lambda o: float(o["arrival_s"]),
|
||||
)
|
||||
n = len(ordered)
|
||||
if n == 0:
|
||||
continue
|
||||
selected = select_requests_for_threshold(requests, threshold=threshold)
|
||||
cp = find_convergence_prefix(
|
||||
selected, window, gpu_count=gpu_count, length_mode=mode,
|
||||
tau=args.tau, tau_c=args.tau_c, stable_checks=args.stable_checks,
|
||||
)
|
||||
# Map the convergence prefix fraction onto the replayed outcomes.
|
||||
stop_n = max(1, min(n, round(cp.fraction * n)))
|
||||
full_pass = sum(1 for o in ordered if o.get("evaluation")) / n
|
||||
prefix_pass = sum(1 for o in ordered[:stop_n] if o.get("evaluation")) / stop_n
|
||||
full_feas = full_pass >= args.target_pass_rate
|
||||
prefix_feas = prefix_pass >= args.target_pass_rate
|
||||
match = full_feas == prefix_feas
|
||||
total += 1
|
||||
mismatches += 0 if match else 1
|
||||
saved_fractions.append(1.0 - cp.fraction)
|
||||
print(
|
||||
f"{threshold:.5f} {n:6d} {stop_n:7d} {cp.fraction:.2f} "
|
||||
f"{full_pass:.3f} {prefix_pass:.3f} "
|
||||
f"{str(full_feas):5s} {str(prefix_feas):5s} {match}"
|
||||
)
|
||||
if total:
|
||||
avg_saved = sum(saved_fractions) / len(saved_fractions)
|
||||
print(
|
||||
f"\nverdict matches: {total - mismatches}/{total} "
|
||||
f"mean replay saved: {avg_saved*100:.0f}%"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from .compare import run_compare
|
||||
@@ -12,8 +13,21 @@ from .harness import (
|
||||
build_harness_stop_proposal,
|
||||
)
|
||||
from .job import append_job, build_trial_job
|
||||
from .lca import (
|
||||
build_study_workload_profile,
|
||||
build_workload_profile,
|
||||
resolve_length_mode,
|
||||
similarity_report,
|
||||
)
|
||||
from .llm import build_prompt, call_llm_for_proposal, load_capability_profile, parse_proposal_text
|
||||
from .spec import Proposal, SpecError, load_study_spec, to_jsonable
|
||||
from .spec import (
|
||||
Proposal,
|
||||
SpecError,
|
||||
StudySpec,
|
||||
load_structured_file,
|
||||
load_study_spec,
|
||||
to_jsonable,
|
||||
)
|
||||
from .store import StudyStore
|
||||
from .trace import load_trace_requests, summarize_window
|
||||
from .worker import run_trial
|
||||
@@ -127,6 +141,7 @@ def cmd_study_prompt(args: argparse.Namespace) -> int:
|
||||
window_summary=summarize_window(requests, window),
|
||||
state=state,
|
||||
capability_profile=capability_profile,
|
||||
workload_profile=build_study_workload_profile(study, requests, window),
|
||||
)
|
||||
prompt_name = args.prompt_name or f"prompt-{state.next_trial_index:04d}"
|
||||
path = store.write_prompt(study.study_id, prompt_name, prompt)
|
||||
@@ -147,6 +162,7 @@ def cmd_study_llm_propose(args: argparse.Namespace) -> int:
|
||||
window_summary=summarize_window(requests, window),
|
||||
state=state,
|
||||
capability_profile=capability_profile,
|
||||
workload_profile=build_study_workload_profile(study, requests, window),
|
||||
)
|
||||
proposal_text = call_llm_for_proposal(
|
||||
policy=study.llm,
|
||||
@@ -210,6 +226,8 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
|
||||
if proposal_files and max_trials > len(proposal_files):
|
||||
max_trials = len(proposal_files)
|
||||
executed: list[dict[str, object]] = []
|
||||
stop_vetoes = 0
|
||||
max_llm_stop_vetoes = 1
|
||||
for idx in range(max_trials):
|
||||
state = store.load_state(study.study_id)
|
||||
if state.tuning_stop_reason:
|
||||
@@ -229,11 +247,13 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
|
||||
break
|
||||
window, requests = load_trace_requests(study, study_spec_path=spec_path)
|
||||
window_summary = summarize_window(requests, window)
|
||||
workload_profile = build_study_workload_profile(study, requests, window)
|
||||
harness_context = (
|
||||
build_harness_context(
|
||||
study=study,
|
||||
window_summary=window_summary,
|
||||
state=state,
|
||||
workload_profile=workload_profile,
|
||||
)
|
||||
if study.llm.use_harness
|
||||
else None
|
||||
@@ -243,6 +263,7 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
|
||||
window_summary=window_summary,
|
||||
state=state,
|
||||
capability_profile=capability_profile,
|
||||
workload_profile=workload_profile,
|
||||
)
|
||||
prompt_name = f"prompt-{state.next_trial_index:04d}"
|
||||
store.write_prompt(study.study_id, prompt_name, prompt)
|
||||
@@ -315,7 +336,34 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
|
||||
proposal = parse_proposal_text(proposal_text, study)
|
||||
store.write_proposal(study.study_id, proposal_name, proposal)
|
||||
if proposal.should_stop:
|
||||
if proposal_name.startswith("harness-stop-"):
|
||||
is_harness_stop = proposal_name.startswith("harness-stop-")
|
||||
is_llm_stop = not is_harness_stop and proposal_source is None
|
||||
stop_authority = (
|
||||
harness_context.get("stop_authority")
|
||||
if isinstance(harness_context, dict)
|
||||
else None
|
||||
)
|
||||
authorized = stop_authority is None or bool(stop_authority.get("authorized"))
|
||||
# Stop-B authority: the deterministic validator overrides an
|
||||
# LLM-originated stop. Veto an unauthorized stop (bounded) so the
|
||||
# loop does not converge prematurely on the agent's say-so alone.
|
||||
if is_llm_stop and not authorized and stop_vetoes < max_llm_stop_vetoes:
|
||||
stop_vetoes += 1
|
||||
executed.append(
|
||||
{
|
||||
"trial_id": None,
|
||||
"proposal_name": proposal_name,
|
||||
"proposal_source": "llm",
|
||||
"stop_vetoed": True,
|
||||
"reason": "validator_did_not_authorize_stop",
|
||||
"validator_reason": (
|
||||
stop_authority.get("reason") if stop_authority else None
|
||||
),
|
||||
"diagnosis": proposal.diagnosis,
|
||||
}
|
||||
)
|
||||
continue
|
||||
if is_harness_stop:
|
||||
proposal_source_label = "harness"
|
||||
else:
|
||||
proposal_source_label = str(proposal_source) if proposal_source else "llm"
|
||||
@@ -325,6 +373,13 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
|
||||
"proposal_name": proposal_name,
|
||||
"proposal_source": proposal_source_label,
|
||||
"stopped": True,
|
||||
"stop_authorized_by": (
|
||||
"validator"
|
||||
if (is_harness_stop or authorized)
|
||||
else "file_proposal"
|
||||
if proposal_source is not None
|
||||
else "llm_after_veto_budget"
|
||||
),
|
||||
"diagnosis": proposal.diagnosis,
|
||||
"state_best_trial_id": state.best_trial_id,
|
||||
"state_best_request_rate": state.best_request_rate,
|
||||
@@ -422,6 +477,159 @@ def cmd_compare_run(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _resolve_profile_gpu_count(args: argparse.Namespace, study: StudySpec) -> int:
|
||||
gpu_count = args.gpu_count
|
||||
if gpu_count is None:
|
||||
gpu_count = study.hardware.gpu_count
|
||||
if gpu_count <= 0:
|
||||
raise SpecError("--gpu-count must be > 0.")
|
||||
return int(gpu_count)
|
||||
|
||||
|
||||
def _load_profile_study_spec(spec_path: Path) -> StudySpec:
|
||||
payload = dict(load_structured_file(spec_path))
|
||||
llm_payload = dict(payload.get("llm") or {})
|
||||
llm_payload.pop("endpoint", None)
|
||||
payload["llm"] = llm_payload
|
||||
return StudySpec.from_dict(payload)
|
||||
|
||||
|
||||
def _profile_current_study_window(args: argparse.Namespace) -> dict[str, object]:
|
||||
spec_path = Path(args.spec).resolve()
|
||||
study = _load_profile_study_spec(spec_path)
|
||||
mode = resolve_length_mode(
|
||||
request_mode=study.trace.request_mode,
|
||||
length_mode=args.length_mode,
|
||||
)
|
||||
window, requests = load_trace_requests(study, study_spec_path=spec_path)
|
||||
profile = build_workload_profile(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=_resolve_profile_gpu_count(args, study),
|
||||
length_mode=mode,
|
||||
)
|
||||
return {
|
||||
"profile": profile.to_dict(),
|
||||
"source": {
|
||||
"study_spec_path": str(spec_path),
|
||||
"window_id": study.trace.window_id,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _resolve_windows_path_for_profile(study: StudySpec, *, study_spec_path: Path) -> Path:
|
||||
path = Path(study.trace.windows_path)
|
||||
if not path.is_absolute():
|
||||
path = (study_spec_path.parent / path).resolve()
|
||||
return path
|
||||
|
||||
|
||||
def _load_profile_windows(
|
||||
study: StudySpec,
|
||||
*,
|
||||
study_spec_path: Path,
|
||||
) -> list[dict[str, object]]:
|
||||
windows_path = _resolve_windows_path_for_profile(study, study_spec_path=study_spec_path)
|
||||
payload = json.loads(windows_path.read_text(encoding="utf-8"))
|
||||
raw_windows = payload.get("windows") if isinstance(payload, dict) else payload
|
||||
if not isinstance(raw_windows, list):
|
||||
raise SpecError(f"windows payload must contain a list: {windows_path}")
|
||||
return [
|
||||
{str(key): value for key, value in item.items()}
|
||||
for item in raw_windows
|
||||
if isinstance(item, dict)
|
||||
]
|
||||
|
||||
|
||||
def _selected_profile_windows(
|
||||
args: argparse.Namespace,
|
||||
study: StudySpec,
|
||||
*,
|
||||
study_spec_path: Path,
|
||||
) -> list[dict[str, object]]:
|
||||
windows = _load_profile_windows(study, study_spec_path=study_spec_path)
|
||||
window_ids = set(args.window_id or [])
|
||||
selected: list[dict[str, object]] = []
|
||||
for item in windows:
|
||||
window_id = str(item.get("window_id") or "").strip()
|
||||
if not window_id:
|
||||
continue
|
||||
if window_ids and window_id not in window_ids:
|
||||
continue
|
||||
if not window_ids and not args.all:
|
||||
if window_id != study.trace.window_id:
|
||||
continue
|
||||
trace_type = str(item.get("trace_type") or "").strip()
|
||||
if args.trace_type and trace_type != args.trace_type:
|
||||
continue
|
||||
date_value = str(item.get("date") or "").strip()
|
||||
if args.date_from and date_value and date_value < args.date_from:
|
||||
continue
|
||||
if args.date_to and date_value and date_value > args.date_to:
|
||||
continue
|
||||
if args.slot_token and str(item.get("slot_token") or "").strip() != args.slot_token:
|
||||
continue
|
||||
selected.append(item)
|
||||
selected.sort(
|
||||
key=lambda item: (
|
||||
str(item.get("date") or ""),
|
||||
str(item.get("slot_token") or ""),
|
||||
str(item.get("window_id") or ""),
|
||||
)
|
||||
)
|
||||
if args.limit is not None:
|
||||
selected = selected[: args.limit]
|
||||
if not selected:
|
||||
raise SpecError("No trace windows selected for profile similarity.")
|
||||
return selected
|
||||
|
||||
|
||||
def cmd_profile_window(args: argparse.Namespace) -> int:
|
||||
print(json.dumps(_profile_current_study_window(args), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_profile_similarity(args: argparse.Namespace) -> int:
|
||||
spec_path = Path(args.spec).resolve()
|
||||
study = _load_profile_study_spec(spec_path)
|
||||
mode = resolve_length_mode(
|
||||
request_mode=study.trace.request_mode,
|
||||
length_mode=args.length_mode,
|
||||
)
|
||||
gpu_count = _resolve_profile_gpu_count(args, study)
|
||||
profiles = []
|
||||
selected = _selected_profile_windows(args, study, study_spec_path=spec_path)
|
||||
for item in selected:
|
||||
window_id = str(item["window_id"])
|
||||
window_study = replace(study, trace=replace(study.trace, window_id=window_id))
|
||||
window, requests = load_trace_requests(window_study, study_spec_path=spec_path)
|
||||
profiles.append(
|
||||
build_workload_profile(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=gpu_count,
|
||||
length_mode=mode,
|
||||
)
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"source": {
|
||||
"study_spec_path": str(spec_path),
|
||||
"selected_window_count": len(profiles),
|
||||
"length_mode": mode,
|
||||
"gpu_count": gpu_count,
|
||||
},
|
||||
"profiles": [profile.to_dict() for profile in profiles],
|
||||
"similarity": similarity_report(profiles),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="AITuner CLI")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
@@ -490,6 +698,50 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
compare_run.add_argument("--output-root")
|
||||
compare_run.set_defaults(func=cmd_compare_run)
|
||||
|
||||
profile = subparsers.add_parser("profile")
|
||||
profile_sub = profile.add_subparsers(dest="profile_command", required=True)
|
||||
|
||||
profile_window = profile_sub.add_parser("window")
|
||||
profile_window.add_argument("--spec", required=True)
|
||||
profile_window.add_argument(
|
||||
"--length-mode",
|
||||
default="auto",
|
||||
choices=["auto", "total", "input", "output"],
|
||||
help="Token length basis for the L vector. auto uses output for decode_only and total otherwise.",
|
||||
)
|
||||
profile_window.add_argument(
|
||||
"--gpu-count",
|
||||
type=int,
|
||||
help="GPU denominator for per-GPU arrival rate. Defaults to hardware.gpu_count.",
|
||||
)
|
||||
profile_window.set_defaults(func=cmd_profile_window)
|
||||
|
||||
profile_similarity = profile_sub.add_parser("similarity")
|
||||
profile_similarity.add_argument("--spec", required=True)
|
||||
profile_similarity.add_argument("--window-id", action="append")
|
||||
profile_similarity.add_argument("--trace-type")
|
||||
profile_similarity.add_argument("--date-from")
|
||||
profile_similarity.add_argument("--date-to")
|
||||
profile_similarity.add_argument("--slot-token")
|
||||
profile_similarity.add_argument("--limit", type=int)
|
||||
profile_similarity.add_argument(
|
||||
"--all",
|
||||
action="store_true",
|
||||
help="Profile all windows selected by filters. Without this or --window-id, only the study window is used.",
|
||||
)
|
||||
profile_similarity.add_argument(
|
||||
"--length-mode",
|
||||
default="auto",
|
||||
choices=["auto", "total", "input", "output"],
|
||||
help="Token length basis for the L vector. auto uses output for decode_only and total otherwise.",
|
||||
)
|
||||
profile_similarity.add_argument(
|
||||
"--gpu-count",
|
||||
type=int,
|
||||
help="GPU denominator for per-GPU arrival rate. Defaults to hardware.gpu_count.",
|
||||
)
|
||||
profile_similarity.set_defaults(func=cmd_profile_similarity)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .lca import EPSILON, WorkloadProfile
|
||||
from .spec import ConfigPatch, Proposal, StudySpec, StudyState, TrialSummary
|
||||
|
||||
|
||||
@@ -30,6 +31,7 @@ def build_harness_context(
|
||||
study: StudySpec,
|
||||
window_summary: dict[str, Any],
|
||||
state: StudyState,
|
||||
workload_profile: WorkloadProfile | None = None,
|
||||
) -> dict[str, Any]:
|
||||
recent_diagnostics = _recent_trial_diagnostics(state)
|
||||
trial_profiles = _trial_profiles(study, recent_diagnostics)
|
||||
@@ -46,24 +48,32 @@ def build_harness_context(
|
||||
trial_profiles,
|
||||
bottleneck_hypotheses,
|
||||
)
|
||||
harness_stop = _harness_stop_decision(
|
||||
study,
|
||||
state,
|
||||
recent_diagnostics,
|
||||
experiment_plan=experiment_plan,
|
||||
)
|
||||
return {
|
||||
"paper_alignment": {
|
||||
"goal": "Use workload-feature-to-knob harnesses to reduce wasted trials and avoid regressing after a good configuration is found.",
|
||||
"feature_model": "L-C-A: request lengths, inter-request KV-cache reuse, and arrival dynamics.",
|
||||
"trial_policy": "Profile measured trials, rank bottleneck hypotheses, score generic candidate actions, and stop only when no useful measured hypothesis remains.",
|
||||
},
|
||||
"workload_lca_profile": _workload_lca_profile(window_summary),
|
||||
"workload_lca_profile": _workload_lca_profile(window_summary, workload_profile),
|
||||
"recent_trial_diagnostics": recent_diagnostics,
|
||||
"trial_profiles": trial_profiles,
|
||||
"bottleneck_hypotheses": bottleneck_hypotheses,
|
||||
"candidate_actions": experiment_plan["candidate_actions"],
|
||||
"experiment_plan": experiment_plan,
|
||||
"convergence_guard": _convergence_guard(state, recent_diagnostics),
|
||||
"harness_stop": _harness_stop_decision(
|
||||
"harness_stop": harness_stop,
|
||||
"stop_authority": _stop_authority(
|
||||
study,
|
||||
state,
|
||||
recent_diagnostics,
|
||||
experiment_plan=experiment_plan,
|
||||
experiment_plan,
|
||||
harness_stop,
|
||||
),
|
||||
"harness_proposal": _harness_proposal_decision(
|
||||
study,
|
||||
@@ -141,7 +151,12 @@ def render_harness_context(context: dict[str, Any]) -> str:
|
||||
return json.dumps(context, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def _workload_lca_profile(window_summary: dict[str, Any]) -> dict[str, Any]:
|
||||
def _workload_lca_profile(
|
||||
window_summary: dict[str, Any],
|
||||
workload_profile: WorkloadProfile | None = None,
|
||||
) -> dict[str, Any]:
|
||||
if workload_profile is not None:
|
||||
return _canonical_lca_profile(workload_profile)
|
||||
prefix_cache = window_summary.get("prefix_cache")
|
||||
if not isinstance(prefix_cache, dict):
|
||||
prefix_cache = {}
|
||||
@@ -178,6 +193,54 @@ def _workload_lca_profile(window_summary: dict[str, Any]) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _canonical_lca_profile(profile: WorkloadProfile) -> dict[str, Any]:
|
||||
"""Authoritative L-C-A block: the paper's 10-dim RobustScaler vector.
|
||||
|
||||
Sourced from lca.WorkloadProfile so the prompt's L-C-A is the same metric
|
||||
used for the workload-similarity computations, not an ad-hoc re-derivation.
|
||||
The regime labels reuse the heuristic classifiers but are fed from the
|
||||
canonical stats.
|
||||
"""
|
||||
stats = profile.stats if isinstance(profile.stats, dict) else {}
|
||||
length = stats.get("length") if isinstance(stats.get("length"), dict) else {}
|
||||
cache = stats.get("cache") if isinstance(stats.get("cache"), dict) else {}
|
||||
arrival = stats.get("arrival") if isinstance(stats.get("arrival"), dict) else {}
|
||||
length_p95 = _as_float(length.get("p95"))
|
||||
length_p50 = _as_float(length.get("p50"))
|
||||
tail_ratio = float(length_p95 / max(length_p50, EPSILON)) if length_p95 else 0.0
|
||||
repeated_token_ratio = _as_float(cache.get("input_hit_rate"))
|
||||
fano_1s = _as_float(arrival.get("fano_1s"))
|
||||
interarrival_cv = _as_float(arrival.get("interarrival_cv"))
|
||||
return {
|
||||
"metric": "paper L-C-A (10-dim, RobustScaler-normalized) from lca.WorkloadProfile",
|
||||
"length_mode": profile.length_mode,
|
||||
"feature_names": list(profile.feature_names),
|
||||
"vector": list(profile.vector),
|
||||
"L_request_lengths": {
|
||||
"mean": _as_float(length.get("mean")),
|
||||
"p50": length_p50,
|
||||
"p95": length_p95,
|
||||
"cv": _as_float(length.get("cv")),
|
||||
"tail_ratio_p95_p50": tail_ratio,
|
||||
"regime": _length_regime(length_p95, tail_ratio),
|
||||
},
|
||||
"C_prefix_cache": {
|
||||
"hit_rate": _as_float(cache.get("hit_rate")),
|
||||
"input_hit_rate": repeated_token_ratio,
|
||||
"repeated_block_ratio": _as_float(cache.get("repeated_block_ratio")),
|
||||
"rows_with_hash_ids": int(cache.get("rows_with_hash_ids") or 0),
|
||||
"regime": _cache_regime(repeated_token_ratio),
|
||||
},
|
||||
"A_arrivals": {
|
||||
"request_rate": _as_float(arrival.get("request_rate")),
|
||||
"request_rate_per_gpu": _as_float(arrival.get("request_rate_per_gpu")),
|
||||
"interarrival_cv": interarrival_cv,
|
||||
"fano_1s": fano_1s,
|
||||
"regime": _arrival_regime(fano_1s, interarrival_cv),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _knob_harnesses(
|
||||
study: StudySpec,
|
||||
window_summary: dict[str, Any],
|
||||
@@ -753,6 +816,43 @@ def _harness_stop_decision(
|
||||
}
|
||||
|
||||
|
||||
def _stop_authority(
|
||||
study: StudySpec,
|
||||
state: StudyState,
|
||||
recent_diagnostics: list[dict[str, Any]],
|
||||
experiment_plan: dict[str, Any] | None,
|
||||
harness_stop: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Stop-B authority: the deterministic validator decides if stopping is justified.
|
||||
|
||||
``authorized`` mirrors the deterministic harness stop decision. The LLM's
|
||||
should_stop is only a corroborating signal: the tuning loop honors an
|
||||
LLM-originated stop only when this validator authorizes it (or when the
|
||||
harness is disabled). ``opportunity_remains`` flags that a concrete adjacent
|
||||
probe (open topology frontier or a high-value planned candidate) still exists,
|
||||
so an early stop would leave measured headroom on the table.
|
||||
"""
|
||||
frontier = _topology_frontier_status(study, state, recent_diagnostics)
|
||||
next_action = (
|
||||
experiment_plan.get("next_action") if isinstance(experiment_plan, dict) else None
|
||||
)
|
||||
has_candidate = (
|
||||
isinstance(next_action, dict) and _as_float(next_action.get("score")) >= 0.35
|
||||
)
|
||||
opportunity_remains = bool(frontier.get("frontier_open")) or has_candidate
|
||||
authorized = bool(harness_stop.get("should_stop"))
|
||||
return {
|
||||
"authorized": authorized,
|
||||
"reason": harness_stop.get("reason"),
|
||||
"opportunity_remains": opportunity_remains,
|
||||
"summary": (
|
||||
"Deterministic validator authorizes stop; no adjacent bottleneck probe remains."
|
||||
if authorized
|
||||
else "Validator does not authorize stop; LLM should_stop is advisory only."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _harness_proposal_decision(
|
||||
study: StudySpec,
|
||||
window_summary: dict[str, Any],
|
||||
|
||||
@@ -179,6 +179,9 @@ def chat_completion(
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
raise HttpClientError(f"llm_completion failed: {exc.code} {detail}") from exc
|
||||
except OSError as exc:
|
||||
# TimeoutError (socket.timeout), URLError, ConnectionError all subclass OSError.
|
||||
raise HttpClientError(f"llm_completion failed: {exc}") from exc
|
||||
|
||||
|
||||
def stream_text_completion(
|
||||
@@ -232,6 +235,8 @@ def stream_text_completion(
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
raise HttpClientError(f"stream_text_completion failed: {exc.code} {detail}") from exc
|
||||
except OSError as exc:
|
||||
raise HttpClientError(f"stream_text_completion failed: {exc}") from exc
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
@@ -293,6 +298,10 @@ def stream_chat_completion(
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
raise HttpClientError(f"stream_chat_completion failed: {exc.code} {detail}") from exc
|
||||
except OSError as exc:
|
||||
# A request that exceeds request_timeout_s raises TimeoutError mid-stream;
|
||||
# treat it as a failed request (SLO miss), not a crashed trial.
|
||||
raise HttpClientError(f"stream_chat_completion failed: {exc}") from exc
|
||||
ttft_ms = None if first_token_at is None else (first_token_at - start) * 1000.0
|
||||
if completion_tokens is None and chunk_token_count > 0:
|
||||
completion_tokens = chunk_token_count
|
||||
|
||||
577
src/aituner/lca.py
Normal file
577
src/aituner/lca.py
Normal file
@@ -0,0 +1,577 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import statistics
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Sequence
|
||||
|
||||
from .trace import TraceRequest, WindowRecord
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .spec import StudySpec
|
||||
|
||||
|
||||
EPSILON = 1e-9
|
||||
|
||||
FEATURE_NAMES = [
|
||||
"L.log_mean_length",
|
||||
"L.log_p95_over_mean_length",
|
||||
"L.cv_length",
|
||||
"C.log_mean_hit_length",
|
||||
"C.log_p95_over_mean_hit_length",
|
||||
"C.cv_hit_length",
|
||||
"C.hit_rate",
|
||||
"A.log_request_rate_per_gpu",
|
||||
"A.cv_interarrival",
|
||||
"A.log_fano_1s",
|
||||
]
|
||||
|
||||
FAMILY_SLICES = {
|
||||
"L": slice(0, 3),
|
||||
"C": slice(3, 7),
|
||||
"A": slice(7, 10),
|
||||
}
|
||||
|
||||
LENGTH_MODES = {"total", "input", "output"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WorkloadProfile:
|
||||
window_id: str
|
||||
trace_type: str
|
||||
request_count: int
|
||||
duration_s: float
|
||||
gpu_count: int
|
||||
length_mode: str
|
||||
feature_names: list[str]
|
||||
vector: list[float]
|
||||
stats: dict[str, Any]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"window_id": self.window_id,
|
||||
"trace_type": self.trace_type,
|
||||
"request_count": self.request_count,
|
||||
"duration_s": self.duration_s,
|
||||
"gpu_count": self.gpu_count,
|
||||
"length_mode": self.length_mode,
|
||||
"feature_names": self.feature_names,
|
||||
"vector": self.vector,
|
||||
"stats": self.stats,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RobustScale:
|
||||
feature_names: list[str]
|
||||
center: list[float]
|
||||
scale: list[float]
|
||||
|
||||
def transform(self, vector: Sequence[float]) -> list[float]:
|
||||
return [
|
||||
(float(value) - self.center[idx]) / self.scale[idx]
|
||||
for idx, value in enumerate(vector)
|
||||
]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"feature_names": self.feature_names,
|
||||
"center": self.center,
|
||||
"scale": self.scale,
|
||||
}
|
||||
|
||||
|
||||
def resolve_length_mode(*, request_mode: str | None = None, length_mode: str = "auto") -> str:
|
||||
normalized = str(length_mode or "auto").strip().lower()
|
||||
if normalized == "auto":
|
||||
return (
|
||||
"output"
|
||||
if str(request_mode or "").strip().lower() == "decode_only"
|
||||
else "total"
|
||||
)
|
||||
if normalized not in LENGTH_MODES:
|
||||
raise ValueError(
|
||||
"length_mode must be one of: auto, total, input, output."
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def build_workload_profile(
|
||||
requests: list[TraceRequest],
|
||||
window: WindowRecord,
|
||||
*,
|
||||
gpu_count: int,
|
||||
length_mode: str = "total",
|
||||
) -> WorkloadProfile:
|
||||
if gpu_count <= 0:
|
||||
raise ValueError("gpu_count must be > 0.")
|
||||
if length_mode not in LENGTH_MODES:
|
||||
raise ValueError(f"Unsupported length_mode: {length_mode}")
|
||||
|
||||
duration_s = _duration_s(requests, window)
|
||||
input_lengths = [float(item.prompt_tokens_hint or 0) for item in requests]
|
||||
output_lengths = [float(item.completion_tokens_hint or 0) for item in requests]
|
||||
profile_lengths = [
|
||||
_profile_length(input_len, output_len, length_mode=length_mode)
|
||||
for input_len, output_len in zip(input_lengths, output_lengths)
|
||||
]
|
||||
hit_lengths, cache_stats = _ideal_cache_hit_lengths(
|
||||
requests,
|
||||
input_lengths=input_lengths,
|
||||
block_size=_block_size(window),
|
||||
)
|
||||
arrival_stats = _arrival_stats(requests, duration_s=duration_s, gpu_count=gpu_count)
|
||||
|
||||
length_stats = _series_stats(profile_lengths)
|
||||
hit_stats = _series_stats(hit_lengths)
|
||||
total_profile_length = sum(profile_lengths)
|
||||
total_input_length = sum(input_lengths)
|
||||
total_hit_length = sum(hit_lengths)
|
||||
feature_hit_rate = (
|
||||
float(total_hit_length / max(total_profile_length, EPSILON))
|
||||
if total_profile_length > 0
|
||||
else 0.0
|
||||
)
|
||||
input_hit_rate = (
|
||||
float(total_hit_length / max(total_input_length, EPSILON))
|
||||
if total_input_length > 0
|
||||
else 0.0
|
||||
)
|
||||
|
||||
vector = [
|
||||
math.log1p(length_stats["mean"]),
|
||||
math.log1p(length_stats["p95"] / max(length_stats["mean"], EPSILON)),
|
||||
length_stats["cv"],
|
||||
math.log1p(hit_stats["mean"]),
|
||||
math.log1p(hit_stats["p95"] / max(hit_stats["mean"], EPSILON)),
|
||||
hit_stats["cv"],
|
||||
feature_hit_rate,
|
||||
math.log1p(arrival_stats["request_rate_per_gpu"]),
|
||||
arrival_stats["interarrival_cv"],
|
||||
math.log1p(arrival_stats["fano_1s"]),
|
||||
]
|
||||
|
||||
return WorkloadProfile(
|
||||
window_id=window.window_id,
|
||||
trace_type=window.trace_type,
|
||||
request_count=len(requests),
|
||||
duration_s=duration_s,
|
||||
gpu_count=int(gpu_count),
|
||||
length_mode=length_mode,
|
||||
feature_names=list(FEATURE_NAMES),
|
||||
vector=[float(item) for item in vector],
|
||||
stats={
|
||||
"length": {
|
||||
**length_stats,
|
||||
"mode": length_mode,
|
||||
"total": total_profile_length,
|
||||
"input_total": total_input_length,
|
||||
"output_total": sum(output_lengths),
|
||||
},
|
||||
"cache": {
|
||||
**hit_stats,
|
||||
**cache_stats,
|
||||
"total_hit_length": total_hit_length,
|
||||
"hit_rate": feature_hit_rate,
|
||||
"input_hit_rate": input_hit_rate,
|
||||
},
|
||||
"arrival": arrival_stats,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def build_study_workload_profile(
|
||||
study: "StudySpec",
|
||||
requests: list[TraceRequest],
|
||||
window: WindowRecord,
|
||||
) -> WorkloadProfile:
|
||||
"""Canonical L-C-A profile for a study's loaded window.
|
||||
|
||||
This is the single source of truth for the paper's 10-dimensional L-C-A
|
||||
feature vector used by the harness prompt and (later) by Stop-A.
|
||||
"""
|
||||
mode = resolve_length_mode(
|
||||
request_mode=study.trace.request_mode,
|
||||
length_mode="auto",
|
||||
)
|
||||
return build_workload_profile(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=study.hardware.gpu_count,
|
||||
length_mode=mode,
|
||||
)
|
||||
|
||||
|
||||
def fit_robust_scale(profiles: Sequence[WorkloadProfile]) -> RobustScale:
|
||||
if not profiles:
|
||||
raise ValueError("At least one profile is required to fit a robust scale.")
|
||||
centers: list[float] = []
|
||||
scales: list[float] = []
|
||||
for idx in range(len(FEATURE_NAMES)):
|
||||
values = [float(profile.vector[idx]) for profile in profiles]
|
||||
median = _percentile(values, 50.0)
|
||||
iqr = _percentile(values, 75.0) - _percentile(values, 25.0)
|
||||
centers.append(float(median))
|
||||
scales.append(float(iqr if abs(iqr) > EPSILON else 1.0))
|
||||
return RobustScale(feature_names=list(FEATURE_NAMES), center=centers, scale=scales)
|
||||
|
||||
|
||||
def profile_similarity(
|
||||
left: WorkloadProfile,
|
||||
right: WorkloadProfile,
|
||||
*,
|
||||
scale: RobustScale | None = None,
|
||||
) -> float:
|
||||
scaler = scale or fit_robust_scale([left, right])
|
||||
z_left = scaler.transform(left.vector)
|
||||
z_right = scaler.transform(right.vector)
|
||||
return _similarity_from_z(z_left, z_right)
|
||||
|
||||
|
||||
def similarity_report(profiles: Sequence[WorkloadProfile]) -> dict[str, Any]:
|
||||
if not profiles:
|
||||
raise ValueError("At least one profile is required.")
|
||||
scale = fit_robust_scale(profiles)
|
||||
transformed = [scale.transform(profile.vector) for profile in profiles]
|
||||
rows: list[dict[str, Any]] = []
|
||||
matrix: list[list[float]] = []
|
||||
for i, left in enumerate(profiles):
|
||||
row_values: list[float] = []
|
||||
for j, right in enumerate(profiles):
|
||||
sim = _similarity_from_z(transformed[i], transformed[j])
|
||||
row_values.append(sim)
|
||||
rows.append(
|
||||
{
|
||||
"left": left.window_id,
|
||||
"right": right.window_id,
|
||||
"similarity": sim,
|
||||
"family_similarity": _family_similarity(transformed[i], transformed[j]),
|
||||
}
|
||||
)
|
||||
matrix.append(row_values)
|
||||
return {
|
||||
"feature_names": list(FEATURE_NAMES),
|
||||
"scaler": scale.to_dict(),
|
||||
"windows": [profile.window_id for profile in profiles],
|
||||
"matrix": matrix,
|
||||
"pairs": rows,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConvergencePoint:
|
||||
converged: bool
|
||||
stop_index: int
|
||||
stop_time_s: float
|
||||
fraction: float
|
||||
family_similarity: dict[str, float]
|
||||
checks: list[dict[str, Any]]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"converged": self.converged,
|
||||
"stop_index": self.stop_index,
|
||||
"stop_time_s": self.stop_time_s,
|
||||
"fraction": self.fraction,
|
||||
"family_similarity": self.family_similarity,
|
||||
"checks": self.checks,
|
||||
}
|
||||
|
||||
|
||||
def find_convergence_prefix(
|
||||
requests: list[TraceRequest],
|
||||
window: WindowRecord,
|
||||
*,
|
||||
gpu_count: int,
|
||||
length_mode: str = "total",
|
||||
tau: float = 0.9,
|
||||
tau_c: float = 0.92,
|
||||
stable_checks: int = 3,
|
||||
max_checks: int = 20,
|
||||
min_fraction: float = 0.1,
|
||||
) -> ConvergencePoint:
|
||||
"""Earliest arrival-ordered prefix whose offered L-C-A converges to the full set.
|
||||
|
||||
The L-C-A vector is a deterministic function of the trace metadata, so the
|
||||
convergence of prefix-vs-full is itself deterministic (the paper's Fig. 9
|
||||
curve). Stop-A replays only up to this prefix. A prefix counts as converged
|
||||
when the L and A family similarities reach ``tau`` and the (slowest) C family
|
||||
similarity reaches the stricter ``tau_c`` for ``stable_checks`` consecutive
|
||||
checkpoints. If that never happens within the window the point reports the
|
||||
full set (converged=False), which keeps the C-gate honest: an unconverged C
|
||||
means the probe must replay the whole window rather than stop early.
|
||||
"""
|
||||
total = len(requests)
|
||||
if total == 0:
|
||||
return ConvergencePoint(
|
||||
converged=False,
|
||||
stop_index=0,
|
||||
stop_time_s=0.0,
|
||||
fraction=1.0,
|
||||
family_similarity={"L": 1.0, "C": 1.0, "A": 1.0},
|
||||
checks=[],
|
||||
)
|
||||
# Compare each arrival-ordered prefix to the whole set, both measured over
|
||||
# their own elapsed span so the A (rate) dimension is comparable rather than
|
||||
# diluted by the fixed window length.
|
||||
target = _prefix_profile(
|
||||
requests, total, window, gpu_count=gpu_count, length_mode=length_mode
|
||||
)
|
||||
indices = _checkpoint_indices(
|
||||
total, max_checks=max_checks, min_fraction=min_fraction
|
||||
)
|
||||
checks: list[dict[str, Any]] = []
|
||||
consecutive = 0
|
||||
converged_index: int | None = None
|
||||
converged_sims: dict[str, float] | None = None
|
||||
for index in indices:
|
||||
prefix = _prefix_profile(
|
||||
requests, index, window, gpu_count=gpu_count, length_mode=length_mode
|
||||
)
|
||||
sims = _family_similarity(target.vector, prefix.vector)
|
||||
checks.append(
|
||||
{
|
||||
"index": index,
|
||||
"fraction": float(index / total),
|
||||
"time_s": float(requests[index - 1].arrival_s),
|
||||
"family_similarity": sims,
|
||||
}
|
||||
)
|
||||
passed = sims["L"] >= tau and sims["A"] >= tau and sims["C"] >= tau_c
|
||||
consecutive = consecutive + 1 if passed else 0
|
||||
if consecutive >= stable_checks and converged_index is None:
|
||||
converged_index = index
|
||||
converged_sims = sims
|
||||
break
|
||||
if converged_index is None:
|
||||
last_sims = checks[-1]["family_similarity"] if checks else {"L": 1.0, "C": 1.0, "A": 1.0}
|
||||
return ConvergencePoint(
|
||||
converged=False,
|
||||
stop_index=total,
|
||||
stop_time_s=float(requests[-1].arrival_s),
|
||||
fraction=1.0,
|
||||
family_similarity=last_sims,
|
||||
checks=checks,
|
||||
)
|
||||
return ConvergencePoint(
|
||||
converged=True,
|
||||
stop_index=converged_index,
|
||||
stop_time_s=float(requests[converged_index - 1].arrival_s),
|
||||
fraction=float(converged_index / total),
|
||||
family_similarity=converged_sims or {},
|
||||
checks=checks,
|
||||
)
|
||||
|
||||
|
||||
def _prefix_profile(
|
||||
requests: list[TraceRequest],
|
||||
index: int,
|
||||
window: WindowRecord,
|
||||
*,
|
||||
gpu_count: int,
|
||||
length_mode: str,
|
||||
) -> WorkloadProfile:
|
||||
prefix = requests[:index]
|
||||
start = float(prefix[0].arrival_s) if prefix else float(window.window_start)
|
||||
end = float(prefix[-1].arrival_s) if prefix else float(window.window_start)
|
||||
prefix_window = WindowRecord(
|
||||
window_id=window.window_id,
|
||||
trace_path=window.trace_path,
|
||||
trace_type=window.trace_type,
|
||||
window_start=start,
|
||||
window_end=end,
|
||||
source_payload=window.source_payload,
|
||||
)
|
||||
return build_workload_profile(
|
||||
prefix, prefix_window, gpu_count=gpu_count, length_mode=length_mode
|
||||
)
|
||||
|
||||
|
||||
def _checkpoint_indices(total: int, *, max_checks: int, min_fraction: float) -> list[int]:
|
||||
start = max(1, int(math.ceil(min_fraction * total)))
|
||||
if total <= max_checks:
|
||||
candidates = range(start, total + 1)
|
||||
else:
|
||||
step = max(1, total // max_checks)
|
||||
candidates = list(range(start, total + 1, step))
|
||||
if candidates and candidates[-1] != total:
|
||||
candidates.append(total)
|
||||
seen: list[int] = []
|
||||
for value in candidates:
|
||||
clamped = min(total, max(1, int(value)))
|
||||
if not seen or seen[-1] != clamped:
|
||||
seen.append(clamped)
|
||||
return seen
|
||||
|
||||
|
||||
def dumps_profile(profile: WorkloadProfile) -> str:
|
||||
return json.dumps(profile.to_dict(), ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
|
||||
def _duration_s(requests: list[TraceRequest], window: WindowRecord) -> float:
|
||||
duration = max(float(window.window_end) - float(window.window_start), 0.0)
|
||||
if duration > 0:
|
||||
return duration
|
||||
if len(requests) >= 2:
|
||||
return max(0.0, float(requests[-1].arrival_s) - float(requests[0].arrival_s))
|
||||
return 0.0
|
||||
|
||||
|
||||
def _profile_length(input_length: float, output_length: float, *, length_mode: str) -> float:
|
||||
if length_mode == "input":
|
||||
return max(input_length, 0.0)
|
||||
if length_mode == "output":
|
||||
return max(output_length, 0.0)
|
||||
return max(input_length, 0.0) + max(output_length, 0.0)
|
||||
|
||||
|
||||
def _block_size(window: WindowRecord) -> int:
|
||||
value = window.source_payload.get("block_size")
|
||||
if isinstance(value, bool):
|
||||
return 1
|
||||
if isinstance(value, (int, float)) and value > 0:
|
||||
return int(value)
|
||||
if isinstance(value, str) and value.strip():
|
||||
try:
|
||||
parsed = int(value)
|
||||
except ValueError:
|
||||
return 1
|
||||
return parsed if parsed > 0 else 1
|
||||
return 1
|
||||
|
||||
|
||||
def _ideal_cache_hit_lengths(
|
||||
requests: list[TraceRequest],
|
||||
*,
|
||||
input_lengths: list[float],
|
||||
block_size: int,
|
||||
) -> tuple[list[float], dict[str, Any]]:
|
||||
seen_hashes: set[Any] = set()
|
||||
hit_lengths: list[float] = []
|
||||
total_blocks = 0
|
||||
repeated_blocks = 0
|
||||
rows_with_hash_ids = 0
|
||||
for request, input_length in zip(requests, input_lengths):
|
||||
hash_ids = request.metadata.get("hash_ids")
|
||||
if not isinstance(hash_ids, list):
|
||||
hit_lengths.append(0.0)
|
||||
continue
|
||||
rows_with_hash_ids += 1
|
||||
repeated_for_request = 0
|
||||
for hash_id in hash_ids:
|
||||
total_blocks += 1
|
||||
if hash_id in seen_hashes:
|
||||
repeated_blocks += 1
|
||||
repeated_for_request += 1
|
||||
else:
|
||||
seen_hashes.add(hash_id)
|
||||
hit_lengths.append(float(min(max(input_length, 0.0), repeated_for_request * block_size)))
|
||||
return hit_lengths, {
|
||||
"block_size": block_size,
|
||||
"rows_with_hash_ids": rows_with_hash_ids,
|
||||
"total_blocks": total_blocks,
|
||||
"repeated_blocks": repeated_blocks,
|
||||
"repeated_block_ratio": (
|
||||
float(repeated_blocks / total_blocks) if total_blocks else 0.0
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _arrival_stats(
|
||||
requests: list[TraceRequest],
|
||||
*,
|
||||
duration_s: float,
|
||||
gpu_count: int,
|
||||
) -> dict[str, Any]:
|
||||
arrivals = [float(item.arrival_s) for item in requests]
|
||||
interarrivals = [
|
||||
max(0.0, arrivals[idx] - arrivals[idx - 1])
|
||||
for idx in range(1, len(arrivals))
|
||||
]
|
||||
per_second_counts = _per_second_counts(arrivals, duration_s=duration_s)
|
||||
qps = float(len(requests) / duration_s) if duration_s > 0 else 0.0
|
||||
return {
|
||||
"request_rate": qps,
|
||||
"request_rate_per_gpu": float(qps / gpu_count) if gpu_count > 0 else 0.0,
|
||||
"interarrival_cv": _cv(interarrivals),
|
||||
"fano_1s": _fano(per_second_counts),
|
||||
"one_second_count_mean": statistics.fmean(per_second_counts)
|
||||
if per_second_counts
|
||||
else 0.0,
|
||||
"one_second_count_variance": statistics.pvariance(per_second_counts)
|
||||
if len(per_second_counts) >= 2
|
||||
else 0.0,
|
||||
"one_second_bin_count": len(per_second_counts),
|
||||
}
|
||||
|
||||
|
||||
def _per_second_counts(arrivals: list[float], *, duration_s: float) -> list[float]:
|
||||
if duration_s <= 0:
|
||||
return [float(len(arrivals))] if arrivals else []
|
||||
bin_count = max(1, int(math.ceil(duration_s)))
|
||||
counts = [0.0 for _ in range(bin_count)]
|
||||
for arrival in arrivals:
|
||||
if arrival < 0:
|
||||
continue
|
||||
idx = int(math.floor(arrival))
|
||||
if 0 <= idx < bin_count:
|
||||
counts[idx] += 1.0
|
||||
return counts
|
||||
|
||||
|
||||
def _series_stats(values: list[float]) -> dict[str, float]:
|
||||
return {
|
||||
"count": float(len(values)),
|
||||
"mean": statistics.fmean(values) if values else 0.0,
|
||||
"p50": _percentile(values, 50.0),
|
||||
"p95": _percentile(values, 95.0),
|
||||
"cv": _cv(values),
|
||||
}
|
||||
|
||||
|
||||
def _cv(values: list[float]) -> float:
|
||||
if not values:
|
||||
return 0.0
|
||||
mean = statistics.fmean(values)
|
||||
if abs(mean) <= EPSILON:
|
||||
return 0.0
|
||||
return float(statistics.pstdev(values) / mean) if len(values) >= 2 else 0.0
|
||||
|
||||
|
||||
def _fano(values: list[float]) -> float:
|
||||
if not values:
|
||||
return 0.0
|
||||
mean = statistics.fmean(values)
|
||||
if abs(mean) <= EPSILON:
|
||||
return 0.0
|
||||
return float(statistics.pvariance(values) / mean) if len(values) >= 2 else 0.0
|
||||
|
||||
|
||||
def _percentile(values: Sequence[float], p: float) -> float:
|
||||
if not values:
|
||||
return 0.0
|
||||
ordered = sorted(float(item) for item in values)
|
||||
if len(ordered) == 1:
|
||||
return ordered[0]
|
||||
rank = (p / 100.0) * (len(ordered) - 1)
|
||||
lower = int(math.floor(rank))
|
||||
upper = int(math.ceil(rank))
|
||||
if lower == upper:
|
||||
return ordered[lower]
|
||||
weight = rank - lower
|
||||
return float(ordered[lower] * (1.0 - weight) + ordered[upper] * weight)
|
||||
|
||||
|
||||
def _similarity_from_z(left: Sequence[float], right: Sequence[float]) -> float:
|
||||
distance = math.sqrt(
|
||||
sum((float(lval) - float(rval)) ** 2 for lval, rval in zip(left, right))
|
||||
)
|
||||
return float(math.exp(-distance))
|
||||
|
||||
|
||||
def _family_similarity(left: Sequence[float], right: Sequence[float]) -> dict[str, float]:
|
||||
result: dict[str, float] = {}
|
||||
for family, family_slice in FAMILY_SLICES.items():
|
||||
result[family] = _similarity_from_z(left[family_slice], right[family_slice])
|
||||
return result
|
||||
@@ -3,12 +3,15 @@ from __future__ import annotations
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .harness import build_harness_context, render_harness_context
|
||||
from .http_client import chat_completion, stream_text_completion
|
||||
from .spec import LLMPolicySpec, Proposal, SpecError, StudySpec, StudyState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lca import WorkloadProfile
|
||||
|
||||
|
||||
def _parse_bool_like(value: Any, *, context: str) -> bool:
|
||||
if isinstance(value, bool):
|
||||
@@ -178,6 +181,7 @@ def build_prompt(
|
||||
window_summary: dict[str, Any],
|
||||
state: StudyState,
|
||||
capability_profile: dict[str, Any] | None,
|
||||
workload_profile: "WorkloadProfile | None" = None,
|
||||
) -> str:
|
||||
objective_notes: list[str] = []
|
||||
if study.trace.request_mode == "decode_only":
|
||||
@@ -409,6 +413,7 @@ def build_prompt(
|
||||
study=study,
|
||||
window_summary=window_summary,
|
||||
state=state,
|
||||
workload_profile=workload_profile,
|
||||
)
|
||||
),
|
||||
"",
|
||||
|
||||
@@ -29,6 +29,9 @@ def _rule_threshold_ms(rule: ThresholdRule, prompt_tokens: int | None) -> float:
|
||||
if rule.kind == "fixed_ms":
|
||||
assert rule.threshold_ms is not None
|
||||
return rule.threshold_ms
|
||||
if rule.kind == "linear_ms":
|
||||
assert rule.intercept_ms is not None and rule.per_token_ms is not None
|
||||
return float(rule.intercept_ms) + float(rule.per_token_ms) * float(prompt_tokens or 0)
|
||||
if rule.kind != "step_ms":
|
||||
raise ValueError(f"Unsupported threshold rule: {rule.kind}")
|
||||
prompt = float(prompt_tokens or 0)
|
||||
|
||||
@@ -321,6 +321,71 @@ class InputLengthFilterSpec:
|
||||
return spec
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AdaptiveStopSpec:
|
||||
"""Stop-A: truncate per-probe replay once the offered L-C-A converges.
|
||||
|
||||
Disabled by default; the thresholds are calibrated per workload (Phase 3)
|
||||
before being switched on, so existing studies are unaffected.
|
||||
"""
|
||||
|
||||
enabled: bool = False
|
||||
tau: float = 0.9
|
||||
tau_c: float = 0.92
|
||||
stable_checks: int = 3
|
||||
max_checks: int = 20
|
||||
min_fraction: float = 0.1
|
||||
boundary_delta: float = 0.02
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Any) -> "AdaptiveStopSpec":
|
||||
if data is None:
|
||||
return cls()
|
||||
m = _require_mapping(data, context="trace.adaptive_stop")
|
||||
enabled = (
|
||||
_require_bool(m.get("enabled"), context="trace.adaptive_stop.enabled")
|
||||
if m.get("enabled") is not None
|
||||
else False
|
||||
)
|
||||
tau = _require_float(m.get("tau", 0.9), context="trace.adaptive_stop.tau")
|
||||
tau_c = _require_float(m.get("tau_c", 0.92), context="trace.adaptive_stop.tau_c")
|
||||
stable_checks = _require_int(
|
||||
m.get("stable_checks", 3), context="trace.adaptive_stop.stable_checks"
|
||||
)
|
||||
max_checks = _require_int(
|
||||
m.get("max_checks", 20), context="trace.adaptive_stop.max_checks"
|
||||
)
|
||||
min_fraction = _require_float(
|
||||
m.get("min_fraction", 0.1), context="trace.adaptive_stop.min_fraction"
|
||||
)
|
||||
boundary_delta = _require_float(
|
||||
m.get("boundary_delta", 0.02), context="trace.adaptive_stop.boundary_delta"
|
||||
)
|
||||
for name, value in (("tau", tau), ("tau_c", tau_c), ("min_fraction", min_fraction)):
|
||||
if not 0.0 < value <= 1.0:
|
||||
raise SpecError(f"trace.adaptive_stop.{name} must be in (0, 1].")
|
||||
if not 0.0 <= boundary_delta < 1.0:
|
||||
raise SpecError("trace.adaptive_stop.boundary_delta must be in [0, 1).")
|
||||
if stable_checks <= 0 or max_checks <= 0:
|
||||
raise SpecError(
|
||||
"trace.adaptive_stop.stable_checks and max_checks must be > 0."
|
||||
)
|
||||
if stable_checks > max_checks:
|
||||
raise SpecError(
|
||||
"trace.adaptive_stop.stable_checks must be <= max_checks, "
|
||||
"otherwise convergence can never be detected."
|
||||
)
|
||||
return cls(
|
||||
enabled=enabled,
|
||||
tau=tau,
|
||||
tau_c=tau_c,
|
||||
stable_checks=stable_checks,
|
||||
max_checks=max_checks,
|
||||
min_fraction=min_fraction,
|
||||
boundary_delta=boundary_delta,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TraceSpec:
|
||||
windows_path: str
|
||||
@@ -338,6 +403,7 @@ class TraceSpec:
|
||||
early_stop_max_lag_s: float | None = None
|
||||
early_stop_max_elapsed_s: float | None = None
|
||||
restart_engine_after_early_stop: bool = False
|
||||
adaptive_stop: AdaptiveStopSpec = AdaptiveStopSpec()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Mapping[str, Any]) -> "TraceSpec":
|
||||
@@ -429,6 +495,7 @@ class TraceSpec:
|
||||
if data.get("restart_engine_after_early_stop") is not None
|
||||
else request_mode == "decode_only"
|
||||
),
|
||||
adaptive_stop=AdaptiveStopSpec.from_dict(data.get("adaptive_stop")),
|
||||
)
|
||||
|
||||
|
||||
@@ -437,6 +504,8 @@ class ThresholdRule:
|
||||
kind: str
|
||||
threshold_ms: float | None = None
|
||||
buckets: list[dict[str, float]] = field(default_factory=list)
|
||||
intercept_ms: float | None = None
|
||||
per_token_ms: float | None = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Mapping[str, Any], *, context: str) -> "ThresholdRule":
|
||||
@@ -448,6 +517,18 @@ class ThresholdRule:
|
||||
data.get("threshold_ms"), context=f"{context}.threshold_ms"
|
||||
),
|
||||
)
|
||||
if kind == "linear_ms":
|
||||
# threshold = intercept_ms + per_token_ms * input_tokens
|
||||
# e.g. "4s + L_in/8k" -> intercept_ms=4000, per_token_ms=0.125
|
||||
intercept_ms = _require_float(
|
||||
data.get("intercept_ms"), context=f"{context}.intercept_ms"
|
||||
)
|
||||
per_token_ms = _require_float(
|
||||
data.get("per_token_ms"), context=f"{context}.per_token_ms"
|
||||
)
|
||||
if intercept_ms < 0 or per_token_ms < 0:
|
||||
raise SpecError(f"{context}.intercept_ms/per_token_ms must be >= 0.")
|
||||
return cls(kind=kind, intercept_ms=intercept_ms, per_token_ms=per_token_ms)
|
||||
if kind == "step_ms":
|
||||
raw = data.get("buckets")
|
||||
if not isinstance(raw, list) or not raw:
|
||||
|
||||
@@ -16,6 +16,7 @@ from typing import Any, Callable
|
||||
|
||||
from .engine import build_launch_recipe
|
||||
from .http_client import HttpClientError, stream_chat_completion, wait_for_server
|
||||
from .lca import find_convergence_prefix, resolve_length_mode
|
||||
from .search import ThresholdProbe, binary_search_max_feasible
|
||||
from .slo import RequestOutcome, evaluate_request, summarize_evaluations
|
||||
from .spec import ConfigPatch, SamplingSearchSpec, TrialSpec, load_study_spec, to_jsonable
|
||||
@@ -209,6 +210,112 @@ def _probe_outcome_details(
|
||||
}
|
||||
|
||||
|
||||
_SIGTERM_NOT_INSTALLED = object()
|
||||
|
||||
|
||||
def _install_sigterm_as_keyboardinterrupt() -> Any:
|
||||
"""Make SIGTERM raise KeyboardInterrupt so the engine-teardown finally runs.
|
||||
|
||||
When `study tune` is killed, a default SIGTERM skips the finally blocks and
|
||||
orphans the vLLM engine (and its EngineCore workers) on the GPUs. Converting
|
||||
SIGTERM to KeyboardInterrupt lets _terminate_process_tree run. Only installable
|
||||
from the main thread; returns the previous handler (or a sentinel).
|
||||
"""
|
||||
if threading.current_thread() is not threading.main_thread():
|
||||
return _SIGTERM_NOT_INSTALLED
|
||||
|
||||
def _handler(signum: int, frame: Any) -> None:
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
try:
|
||||
return signal.signal(signal.SIGTERM, _handler)
|
||||
except (ValueError, OSError):
|
||||
return _SIGTERM_NOT_INSTALLED
|
||||
|
||||
|
||||
def _restore_sigterm(previous: Any) -> None:
|
||||
if previous is _SIGTERM_NOT_INSTALLED:
|
||||
return
|
||||
if threading.current_thread() is not threading.main_thread():
|
||||
return
|
||||
try:
|
||||
signal.signal(signal.SIGTERM, previous)
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
|
||||
|
||||
def _ignore_sigterm_if_main() -> None:
|
||||
"""Ignore SIGTERM during teardown so a second signal cannot orphan the engine."""
|
||||
if threading.current_thread() is not threading.main_thread():
|
||||
return
|
||||
try:
|
||||
signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
|
||||
|
||||
def _adaptive_replay_set(
|
||||
selected: list[TraceRequest],
|
||||
*,
|
||||
study: Any,
|
||||
window: Any,
|
||||
) -> tuple[list[TraceRequest], dict[str, Any] | None]:
|
||||
"""Stop-A: truncate the replay to the offered-L-C-A convergence prefix.
|
||||
|
||||
Returns the (possibly shortened) request list to replay and a certificate of
|
||||
the convergence decision. When Stop-A is disabled, or C never converges, the
|
||||
full selected set is replayed (the C-gate: no early stop on a cold cache).
|
||||
"""
|
||||
spec = study.trace.adaptive_stop
|
||||
if not getattr(spec, "enabled", False) or not selected:
|
||||
return selected, None
|
||||
point = find_convergence_prefix(
|
||||
selected,
|
||||
window,
|
||||
gpu_count=study.hardware.gpu_count,
|
||||
length_mode=resolve_length_mode(request_mode=study.trace.request_mode),
|
||||
tau=spec.tau,
|
||||
tau_c=spec.tau_c,
|
||||
stable_checks=spec.stable_checks,
|
||||
max_checks=spec.max_checks,
|
||||
min_fraction=spec.min_fraction,
|
||||
)
|
||||
replay = selected[: point.stop_index] if point.stop_index > 0 else selected
|
||||
certificate = {
|
||||
"enabled": True,
|
||||
"converged": point.converged,
|
||||
"stop_index": point.stop_index,
|
||||
"total_selected": len(selected),
|
||||
"fraction": point.fraction,
|
||||
"stop_time_s": point.stop_time_s,
|
||||
"family_similarity": point.family_similarity,
|
||||
}
|
||||
return replay, certificate
|
||||
|
||||
|
||||
def _should_extend_on_boundary(
|
||||
*,
|
||||
pass_rate: float,
|
||||
target_pass_rate: float,
|
||||
certificate: dict[str, Any] | None,
|
||||
truncated: bool,
|
||||
boundary_delta: float,
|
||||
) -> bool:
|
||||
"""SLO-boundary guard: re-measure on the full window when a truncated probe
|
||||
lands within +/- boundary_delta of the SLO target.
|
||||
|
||||
Offered-L-C-A convergence cannot see engine-state drift in the window's tail,
|
||||
so a near-boundary truncated verdict is untrustworthy. This fires only on
|
||||
probes sitting on the feasibility knee, so non-boundary probes keep the Stop-A
|
||||
time saving.
|
||||
"""
|
||||
if certificate is None or not certificate.get("converged"):
|
||||
return False
|
||||
if not truncated or boundary_delta <= 0:
|
||||
return False
|
||||
return abs(float(pass_rate) - float(target_pass_rate)) <= float(boundary_delta)
|
||||
|
||||
|
||||
def _best_feasible_probe_record(probe_history: list[dict[str, Any]]) -> dict[str, Any] | None:
|
||||
feasible = [
|
||||
item
|
||||
@@ -505,6 +612,7 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
|
||||
)
|
||||
|
||||
process = launch_process()
|
||||
previous_sigterm = _install_sigterm_as_keyboardinterrupt()
|
||||
probe_history: list[dict[str, Any]] = []
|
||||
failure_stage = "engine_launch"
|
||||
try:
|
||||
@@ -519,27 +627,49 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
|
||||
def evaluator(threshold: float) -> ThresholdProbe[ProbePayload]:
|
||||
nonlocal process
|
||||
selected = select_requests_for_threshold(requests, threshold=threshold)
|
||||
restart_after_early_stop = study.trace.restart_engine_after_early_stop
|
||||
outcomes, early_stopped, early_stop_reason = _replay_requests(
|
||||
selected,
|
||||
base_url=recipe.base_url,
|
||||
timeout_s=recipe.request_timeout_s,
|
||||
max_concurrency=study.trace.max_concurrency,
|
||||
target_pass_rate=study.slo.target_pass_rate,
|
||||
max_lag_s=study.trace.early_stop_max_lag_s,
|
||||
max_elapsed_s=study.trace.early_stop_max_elapsed_s,
|
||||
evaluate_outcome=lambda outcome: evaluate_request(outcome, study.slo),
|
||||
drain_inflight_on_early_stop=not restart_after_early_stop,
|
||||
replay_set, adaptive_stop_certificate = _adaptive_replay_set(
|
||||
selected, study=study, window=window
|
||||
)
|
||||
restart_after_early_stop = study.trace.restart_engine_after_early_stop
|
||||
|
||||
def _run(reqs: list[TraceRequest]) -> tuple[list[RequestOutcome], bool, str]:
|
||||
return _replay_requests(
|
||||
reqs,
|
||||
base_url=recipe.base_url,
|
||||
timeout_s=recipe.request_timeout_s,
|
||||
max_concurrency=study.trace.max_concurrency,
|
||||
target_pass_rate=study.slo.target_pass_rate,
|
||||
max_lag_s=study.trace.early_stop_max_lag_s,
|
||||
max_elapsed_s=study.trace.early_stop_max_elapsed_s,
|
||||
evaluate_outcome=lambda outcome: evaluate_request(outcome, study.slo),
|
||||
drain_inflight_on_early_stop=not restart_after_early_stop,
|
||||
)
|
||||
|
||||
outcomes, early_stopped, early_stop_reason = _run(replay_set)
|
||||
evaluations, summary = summarize_evaluations(outcomes, study.slo)
|
||||
if _should_extend_on_boundary(
|
||||
pass_rate=summary["slo_pass_rate"],
|
||||
target_pass_rate=study.slo.target_pass_rate,
|
||||
certificate=adaptive_stop_certificate,
|
||||
truncated=len(replay_set) < len(selected),
|
||||
boundary_delta=study.trace.adaptive_stop.boundary_delta,
|
||||
):
|
||||
# On the feasibility knee the truncated verdict is untrustworthy;
|
||||
# re-measure the full window and use that result.
|
||||
replay_set = selected
|
||||
outcomes, early_stopped, early_stop_reason = _run(selected)
|
||||
evaluations, summary = summarize_evaluations(outcomes, study.slo)
|
||||
if adaptive_stop_certificate is not None:
|
||||
adaptive_stop_certificate["boundary_extended"] = True
|
||||
probe_details = _probe_outcome_details(
|
||||
threshold=threshold,
|
||||
selected=selected,
|
||||
selected=replay_set,
|
||||
outcomes=outcomes,
|
||||
evaluations=evaluations,
|
||||
early_stopped=early_stopped,
|
||||
early_stop_reason=early_stop_reason,
|
||||
)
|
||||
probe_details["adaptive_stop"] = adaptive_stop_certificate
|
||||
with probe_details_path.open("a", encoding="utf-8") as details_handle:
|
||||
details_handle.write(
|
||||
json.dumps(probe_details, ensure_ascii=False) + "\n"
|
||||
@@ -580,12 +710,14 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
|
||||
probe_record = {
|
||||
"threshold": threshold,
|
||||
"request_count": payload.request_count,
|
||||
"replayed_request_count": len(replay_set),
|
||||
"pass_rate": payload.pass_rate,
|
||||
"request_rate": payload.request_rate,
|
||||
"feasible": payload.feasible,
|
||||
"early_stopped": payload.early_stopped,
|
||||
"early_stop_reason": payload.early_stop_reason,
|
||||
"latency_summary": payload.latency_summary,
|
||||
"adaptive_stop": adaptive_stop_certificate,
|
||||
}
|
||||
probe_history.append(probe_record)
|
||||
StudyStore.write_json(Path(trial.probe_log_path), probe_history)
|
||||
@@ -739,4 +871,6 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
|
||||
StudyStore.write_json(Path(trial.result_path), result)
|
||||
return result
|
||||
finally:
|
||||
_ignore_sigterm_if_main()
|
||||
_terminate_process_tree(process, timeout_s=30.0, marker_env=trial_marker_env)
|
||||
_restore_sigterm(previous_sigterm)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import contextlib
|
||||
import io
|
||||
import math
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
@@ -13,6 +16,7 @@ from aituner.cli import main as cli_main
|
||||
from aituner.compare import _aggregate_summary, load_compare_spec, run_compare
|
||||
from aituner.engine import build_launch_recipe
|
||||
from aituner.http_client import (
|
||||
HttpClientError,
|
||||
StreamMetrics,
|
||||
_auth_headers,
|
||||
_openai_url,
|
||||
@@ -25,13 +29,23 @@ from aituner.harness import (
|
||||
build_harness_guided_proposal,
|
||||
build_harness_stop_proposal,
|
||||
)
|
||||
from aituner.lca import (
|
||||
build_study_workload_profile,
|
||||
build_workload_profile,
|
||||
find_convergence_prefix,
|
||||
profile_similarity,
|
||||
resolve_length_mode,
|
||||
similarity_report,
|
||||
)
|
||||
from aituner.llm import _extract_response_text, build_prompt, parse_proposal_text, validate_proposal
|
||||
from aituner.search import ThresholdProbe, binary_search_max_feasible
|
||||
from aituner.slo import RequestOutcome, evaluate_request, summarize_evaluations
|
||||
from aituner.spec import (
|
||||
AdaptiveStopSpec,
|
||||
ConfigPatch,
|
||||
LLMEndpointSpec,
|
||||
Proposal,
|
||||
SloSpec,
|
||||
SpecError,
|
||||
StudyState,
|
||||
TrialSummary,
|
||||
@@ -40,6 +54,10 @@ from aituner.spec import (
|
||||
from aituner.store import StudyStore
|
||||
from aituner.trace import load_trace_requests, summarize_window
|
||||
from aituner.worker import (
|
||||
_adaptive_replay_set,
|
||||
_install_sigterm_as_keyboardinterrupt,
|
||||
_restore_sigterm,
|
||||
_should_extend_on_boundary,
|
||||
_best_feasible_probe_record,
|
||||
_latency_summary,
|
||||
_run_one_request,
|
||||
@@ -48,7 +66,7 @@ from aituner.worker import (
|
||||
_wait_for_server_or_exit,
|
||||
run_trial,
|
||||
)
|
||||
from aituner.trace import TraceRequest
|
||||
from aituner.trace import TraceRequest, WindowRecord
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -241,6 +259,445 @@ class CoreFlowTests(unittest.TestCase):
|
||||
self.assertIn("knob_harnesses", prompt)
|
||||
self.assertTrue(study_root.exists())
|
||||
|
||||
def test_lca_workload_profile_uses_standard_10d_features(self) -> None:
|
||||
window = WindowRecord(
|
||||
window_id="w1",
|
||||
trace_path=Path("trace.jsonl"),
|
||||
trace_type="chat",
|
||||
window_start=0.0,
|
||||
window_end=4.0,
|
||||
source_payload={"block_size": 64},
|
||||
)
|
||||
requests = [
|
||||
TraceRequest(
|
||||
row_id="r1",
|
||||
arrival_s=0.0,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=100,
|
||||
completion_tokens_hint=10,
|
||||
metadata={"hash_ids": [1, 2]},
|
||||
),
|
||||
TraceRequest(
|
||||
row_id="r2",
|
||||
arrival_s=1.0,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=100,
|
||||
completion_tokens_hint=20,
|
||||
metadata={"hash_ids": [1, 3]},
|
||||
),
|
||||
]
|
||||
|
||||
profile = build_workload_profile(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=2,
|
||||
length_mode="total",
|
||||
)
|
||||
|
||||
self.assertEqual(len(profile.feature_names), 10)
|
||||
self.assertEqual(len(profile.vector), 10)
|
||||
self.assertEqual(profile.feature_names[0], "L.log_mean_length")
|
||||
self.assertAlmostEqual(profile.stats["cache"]["total_hit_length"], 64.0)
|
||||
self.assertAlmostEqual(profile.stats["cache"]["hit_rate"], 64.0 / 230.0)
|
||||
self.assertAlmostEqual(profile.stats["cache"]["input_hit_rate"], 64.0 / 200.0)
|
||||
self.assertAlmostEqual(profile.vector[3], math.log1p(32.0))
|
||||
self.assertAlmostEqual(profile.vector[5], 1.0)
|
||||
self.assertAlmostEqual(profile.stats["arrival"]["request_rate_per_gpu"], 0.25)
|
||||
self.assertAlmostEqual(profile.stats["arrival"]["fano_1s"], 0.5)
|
||||
self.assertEqual(resolve_length_mode(request_mode="decode_only"), "output")
|
||||
|
||||
def test_harness_context_uses_canonical_lca_vector(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
study_path = _write_study_assets(tmp_path)
|
||||
study = load_study_spec(study_path)
|
||||
window, requests = load_trace_requests(study, study_spec_path=study_path)
|
||||
profile = build_study_workload_profile(study, requests, window)
|
||||
state = StudyState(study_id=study.study_id, trials=[])
|
||||
summary = summarize_window(requests, window)
|
||||
context = build_harness_context(
|
||||
study=study,
|
||||
window_summary=summary,
|
||||
state=state,
|
||||
workload_profile=profile,
|
||||
)
|
||||
block = context["workload_lca_profile"]
|
||||
# The labeled L-C-A block is the canonical 10-dim metric, not ad-hoc.
|
||||
self.assertEqual(block["vector"], profile.vector)
|
||||
self.assertEqual(len(block["vector"]), 10)
|
||||
self.assertIn("RobustScaler", block["metric"])
|
||||
# Without a profile it falls back to the legacy ad-hoc rendering.
|
||||
legacy = build_harness_context(
|
||||
study=study,
|
||||
window_summary=summary,
|
||||
state=state,
|
||||
)["workload_lca_profile"]
|
||||
self.assertNotIn("vector", legacy)
|
||||
|
||||
def _steady_requests(self, count: int, *, input_tokens: int = 100) -> list:
|
||||
return [
|
||||
TraceRequest(
|
||||
row_id=f"r{i}",
|
||||
arrival_s=float(i),
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=input_tokens,
|
||||
completion_tokens_hint=16,
|
||||
metadata={"hash_ids": None},
|
||||
)
|
||||
for i in range(count)
|
||||
]
|
||||
|
||||
def _conv_window(self) -> WindowRecord:
|
||||
return WindowRecord(
|
||||
window_id="conv",
|
||||
trace_path=Path("trace.jsonl"),
|
||||
trace_type="chat",
|
||||
window_start=0.0,
|
||||
window_end=0.0,
|
||||
source_payload={"block_size": 64},
|
||||
)
|
||||
|
||||
def test_convergence_prefix_stops_early_on_stationary_trace(self) -> None:
|
||||
requests = self._steady_requests(60)
|
||||
point = find_convergence_prefix(
|
||||
requests,
|
||||
self._conv_window(),
|
||||
gpu_count=1,
|
||||
length_mode="total",
|
||||
tau=0.9,
|
||||
tau_c=0.9,
|
||||
stable_checks=3,
|
||||
max_checks=20,
|
||||
min_fraction=0.1,
|
||||
)
|
||||
self.assertTrue(point.converged)
|
||||
# A stationary workload should be trustworthy well before the full window.
|
||||
self.assertLess(point.stop_index, len(requests))
|
||||
self.assertLess(point.fraction, 1.0)
|
||||
self.assertTrue(point.checks)
|
||||
|
||||
def test_convergence_prefix_waits_when_cache_warms_late(self) -> None:
|
||||
window = self._conv_window()
|
||||
# First half: no prefix reuse. Second half: every request reuses block 1,
|
||||
# so the C dimension only stabilizes once the reuse regime is exercised.
|
||||
requests = []
|
||||
for i in range(30):
|
||||
requests.append(
|
||||
TraceRequest(
|
||||
row_id=f"cold{i}",
|
||||
arrival_s=float(i),
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=640,
|
||||
completion_tokens_hint=16,
|
||||
metadata={"hash_ids": [10_000 + i]},
|
||||
)
|
||||
)
|
||||
for i in range(30):
|
||||
requests.append(
|
||||
TraceRequest(
|
||||
row_id=f"warm{i}",
|
||||
arrival_s=float(30 + i),
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=640,
|
||||
completion_tokens_hint=16,
|
||||
metadata={"hash_ids": [1, 2, 3, 4, 5]},
|
||||
)
|
||||
)
|
||||
point = find_convergence_prefix(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=1,
|
||||
length_mode="total",
|
||||
tau=0.9,
|
||||
tau_c=0.95,
|
||||
stable_checks=2,
|
||||
max_checks=20,
|
||||
min_fraction=0.1,
|
||||
)
|
||||
# The C family similarity must be low while only the cold half is seen.
|
||||
early = [c for c in point.checks if c["fraction"] <= 0.4]
|
||||
self.assertTrue(early)
|
||||
self.assertTrue(any(c["family_similarity"]["C"] < 0.9 for c in early))
|
||||
|
||||
def test_stop_authority_mirrors_validator_and_blocks_fresh_stop(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
study = load_study_spec(_write_study_assets(Path(tmp)))
|
||||
state = StudyState(study_id=study.study_id, trials=[])
|
||||
context = build_harness_context(
|
||||
study=study,
|
||||
window_summary={},
|
||||
state=state,
|
||||
)
|
||||
authority = context["stop_authority"]
|
||||
# The authority is the deterministic validator; with no completed
|
||||
# trials it must not authorize a stop.
|
||||
self.assertEqual(
|
||||
authority["authorized"], context["harness_stop"]["should_stop"]
|
||||
)
|
||||
self.assertFalse(authority["authorized"])
|
||||
|
||||
def test_adaptive_replay_set_truncates_only_when_enabled(self) -> None:
|
||||
from types import SimpleNamespace
|
||||
|
||||
requests = self._steady_requests(60)
|
||||
window = self._conv_window()
|
||||
enabled_study = SimpleNamespace(
|
||||
trace=SimpleNamespace(
|
||||
adaptive_stop=AdaptiveStopSpec(
|
||||
enabled=True,
|
||||
tau=0.9,
|
||||
tau_c=0.9,
|
||||
stable_checks=3,
|
||||
max_checks=20,
|
||||
min_fraction=0.1,
|
||||
),
|
||||
request_mode="chat",
|
||||
),
|
||||
hardware=SimpleNamespace(gpu_count=1),
|
||||
)
|
||||
replay, certificate = _adaptive_replay_set(
|
||||
requests, study=enabled_study, window=window
|
||||
)
|
||||
self.assertIsNotNone(certificate)
|
||||
self.assertTrue(certificate["enabled"])
|
||||
self.assertEqual(len(replay), certificate["stop_index"])
|
||||
self.assertLessEqual(len(replay), len(requests))
|
||||
|
||||
disabled_study = SimpleNamespace(
|
||||
trace=SimpleNamespace(
|
||||
adaptive_stop=AdaptiveStopSpec(enabled=False),
|
||||
request_mode="chat",
|
||||
),
|
||||
hardware=SimpleNamespace(gpu_count=1),
|
||||
)
|
||||
passthrough, no_cert = _adaptive_replay_set(
|
||||
requests, study=disabled_study, window=window
|
||||
)
|
||||
self.assertIsNone(no_cert)
|
||||
self.assertEqual(len(passthrough), len(requests))
|
||||
|
||||
def test_boundary_guard_extends_only_near_the_slo_knee(self) -> None:
|
||||
converged = {"converged": True}
|
||||
# Truncated, converged, pass-rate on the knee -> re-measure full.
|
||||
self.assertTrue(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.961, target_pass_rate=0.95, certificate=converged,
|
||||
truncated=True, boundary_delta=0.02,
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.946, target_pass_rate=0.95, certificate=converged,
|
||||
truncated=True, boundary_delta=0.02,
|
||||
)
|
||||
)
|
||||
# Clearly feasible / clearly infeasible -> trust the truncated verdict.
|
||||
self.assertFalse(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.99, target_pass_rate=0.95, certificate=converged,
|
||||
truncated=True, boundary_delta=0.02,
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.50, target_pass_rate=0.95, certificate=converged,
|
||||
truncated=True, boundary_delta=0.02,
|
||||
)
|
||||
)
|
||||
# Not truncated, not converged, guard disabled, or no certificate -> no extend.
|
||||
self.assertFalse(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.95, target_pass_rate=0.95, certificate=converged,
|
||||
truncated=False, boundary_delta=0.02,
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.95, target_pass_rate=0.95, certificate={"converged": False},
|
||||
truncated=True, boundary_delta=0.02,
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.95, target_pass_rate=0.95, certificate=converged,
|
||||
truncated=True, boundary_delta=0.0,
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
_should_extend_on_boundary(
|
||||
pass_rate=0.95, target_pass_rate=0.95, certificate=None,
|
||||
truncated=True, boundary_delta=0.02,
|
||||
)
|
||||
)
|
||||
|
||||
def test_linear_ms_ttft_rule_scales_with_input_length(self) -> None:
|
||||
slo = SloSpec.from_dict(
|
||||
{
|
||||
"target_pass_rate": 0.95,
|
||||
"ttft_rule": {"kind": "linear_ms", "intercept_ms": 4000, "per_token_ms": 0.125},
|
||||
"tpot_rule": {"kind": "fixed_ms", "threshold_ms": 50},
|
||||
}
|
||||
)
|
||||
|
||||
def ev(prompt_tokens: int, ttft_ms: float):
|
||||
return evaluate_request(
|
||||
RequestOutcome(
|
||||
request_id="r",
|
||||
success=True,
|
||||
ttft_ms=ttft_ms,
|
||||
tpot_ms=10.0,
|
||||
prompt_tokens=prompt_tokens,
|
||||
completion_tokens=8,
|
||||
),
|
||||
slo,
|
||||
)
|
||||
|
||||
# threshold = 4000 + 0.125*L_in : 8k->5000ms, 0->4000ms
|
||||
self.assertTrue(ev(8000, 4900).passed)
|
||||
self.assertFalse(ev(8000, 5100).passed)
|
||||
self.assertTrue(ev(0, 3900).passed)
|
||||
self.assertFalse(ev(0, 4100).passed)
|
||||
|
||||
def test_streaming_socket_timeout_is_a_failed_request_not_a_crash(self) -> None:
|
||||
# A request that exceeds request_timeout_s raises TimeoutError mid-stream;
|
||||
# it must surface as HttpClientError (a failed request), never escape to
|
||||
# crash the trial.
|
||||
with mock.patch(
|
||||
"aituner.http_client._urlopen", side_effect=TimeoutError("timed out")
|
||||
):
|
||||
with self.assertRaises(HttpClientError):
|
||||
stream_chat_completion(
|
||||
base_url="http://127.0.0.1:1/v1",
|
||||
body={"messages": [{"role": "user", "content": "hi"}], "stream": True},
|
||||
timeout_s=0.5,
|
||||
)
|
||||
outcome = _run_one_request(
|
||||
TraceRequest(
|
||||
row_id="r",
|
||||
arrival_s=0.0,
|
||||
sampling_u=1.0,
|
||||
body={"messages": [{"role": "user", "content": "hi"}], "stream": True},
|
||||
prompt_tokens_hint=10,
|
||||
completion_tokens_hint=None,
|
||||
),
|
||||
base_url="http://127.0.0.1:1/v1",
|
||||
timeout_s=0.5,
|
||||
)
|
||||
self.assertFalse(outcome.success)
|
||||
self.assertIn("timed out", outcome.error)
|
||||
|
||||
def test_sigterm_is_converted_to_keyboardinterrupt(self) -> None:
|
||||
# So a killed `study tune` runs the engine-teardown finally instead of
|
||||
# orphaning the vLLM EngineCore workers on the GPUs.
|
||||
import signal as _signal
|
||||
|
||||
previous = _install_sigterm_as_keyboardinterrupt()
|
||||
try:
|
||||
with self.assertRaises(KeyboardInterrupt):
|
||||
_signal.raise_signal(_signal.SIGTERM)
|
||||
finally:
|
||||
_restore_sigterm(previous)
|
||||
|
||||
def test_lca_similarity_matrix_separates_different_profiles(self) -> None:
|
||||
window = WindowRecord(
|
||||
window_id="base",
|
||||
trace_path=Path("trace.jsonl"),
|
||||
trace_type="chat",
|
||||
window_start=0.0,
|
||||
window_end=4.0,
|
||||
source_payload={"block_size": 64},
|
||||
)
|
||||
|
||||
def make_profile(window_id: str, input_tokens: int, *, arrival_gap: float) -> object:
|
||||
reqs = [
|
||||
TraceRequest(
|
||||
row_id=f"{window_id}-1",
|
||||
arrival_s=0.0,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=input_tokens,
|
||||
completion_tokens_hint=16,
|
||||
metadata={"hash_ids": [window_id, 1]},
|
||||
),
|
||||
TraceRequest(
|
||||
row_id=f"{window_id}-2",
|
||||
arrival_s=arrival_gap,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=input_tokens,
|
||||
completion_tokens_hint=16,
|
||||
metadata={"hash_ids": [window_id, 1, 2]},
|
||||
),
|
||||
]
|
||||
return build_workload_profile(
|
||||
reqs,
|
||||
WindowRecord(
|
||||
window_id=window_id,
|
||||
trace_path=window.trace_path,
|
||||
trace_type=window.trace_type,
|
||||
window_start=window.window_start,
|
||||
window_end=window.window_end,
|
||||
source_payload=window.source_payload,
|
||||
),
|
||||
gpu_count=1,
|
||||
length_mode="total",
|
||||
)
|
||||
|
||||
p1 = make_profile("same-a", 100, arrival_gap=1.0)
|
||||
p2 = make_profile("same-b", 100, arrival_gap=1.0)
|
||||
p3 = make_profile("different", 10000, arrival_gap=0.1)
|
||||
|
||||
report = similarity_report([p1, p2, p3])
|
||||
|
||||
self.assertAlmostEqual(profile_similarity(p1, p2), 1.0)
|
||||
self.assertGreater(report["matrix"][0][1], report["matrix"][0][2])
|
||||
self.assertIn("L", report["pairs"][2]["family_similarity"])
|
||||
|
||||
def test_cli_profile_window_outputs_lca_profile(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
study_path = _write_study_assets(tmp_path)
|
||||
stdout = io.StringIO()
|
||||
with mock.patch("sys.stdout", stdout):
|
||||
rc = cli_main(
|
||||
[
|
||||
"profile",
|
||||
"window",
|
||||
"--spec",
|
||||
str(study_path),
|
||||
"--gpu-count",
|
||||
"8",
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
payload = json.loads(stdout.getvalue())
|
||||
self.assertEqual(payload["profile"]["window_id"], "chat_w1")
|
||||
self.assertEqual(len(payload["profile"]["vector"]), 10)
|
||||
self.assertEqual(payload["profile"]["gpu_count"], 8)
|
||||
|
||||
def test_cli_profile_window_does_not_resolve_llm_endpoint(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
study_path = _write_study_assets(tmp_path)
|
||||
payload = json.loads(study_path.read_text(encoding="utf-8"))
|
||||
payload["llm"]["endpoint"] = {
|
||||
"provider": "codex",
|
||||
"model": "gpt-5.4",
|
||||
}
|
||||
study_path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
stdout = io.StringIO()
|
||||
with mock.patch("sys.stdout", stdout):
|
||||
rc = cli_main(["profile", "window", "--spec", str(study_path)])
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(json.loads(stdout.getvalue())["profile"]["window_id"], "chat_w1")
|
||||
|
||||
def test_harness_uses_latency_failures_before_generic_unrecoverable(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
@@ -3644,6 +4101,56 @@ class CoreFlowTests(unittest.TestCase):
|
||||
state = store.load_state("study-1")
|
||||
self.assertEqual(state.next_trial_index, 1)
|
||||
|
||||
def test_cli_tune_vetoes_unauthorized_llm_stop(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
study_path = _write_study_assets(tmp_path)
|
||||
spec = json.loads(study_path.read_text(encoding="utf-8"))
|
||||
spec["llm"]["endpoint"] = {
|
||||
"provider": "custom",
|
||||
"base_url": "http://localhost:9/v1",
|
||||
"model": "test-model",
|
||||
"api_key_env": "AITUNER_TEST_KEY",
|
||||
}
|
||||
study_path.write_text(json.dumps(spec), encoding="utf-8")
|
||||
store_root = tmp_path / "store"
|
||||
stop_payload = json.dumps(
|
||||
{
|
||||
"observation": "looks done",
|
||||
"diagnosis": "agent thinks it converged",
|
||||
"config_patch": {"env_patch": {}, "flag_patch": {}},
|
||||
"expected_effects": ["stop"],
|
||||
"why_not_previous_failures": "n/a",
|
||||
"should_stop": True,
|
||||
}
|
||||
)
|
||||
buffer = io.StringIO()
|
||||
with mock.patch("aituner.cli.run_trial") as run_trial_mock, mock.patch(
|
||||
"aituner.cli.call_llm_for_proposal", return_value=stop_payload
|
||||
), contextlib.redirect_stdout(buffer):
|
||||
exit_code = cli_main(
|
||||
[
|
||||
"study",
|
||||
"tune",
|
||||
"--spec",
|
||||
str(study_path),
|
||||
"--store-root",
|
||||
str(store_root),
|
||||
"--skip-baseline",
|
||||
"--max-trials",
|
||||
"2",
|
||||
]
|
||||
)
|
||||
self.assertEqual(exit_code, 0)
|
||||
run_trial_mock.assert_not_called()
|
||||
executed = json.loads(buffer.getvalue())["executed_trials"]
|
||||
# The first unauthorized LLM stop is vetoed; the second is honored
|
||||
# only after the veto budget is spent.
|
||||
self.assertTrue(any(item.get("stop_vetoed") for item in executed))
|
||||
honored = [item for item in executed if item.get("stopped")]
|
||||
self.assertTrue(honored)
|
||||
self.assertEqual(honored[-1]["stop_authorized_by"], "llm_after_veto_budget")
|
||||
|
||||
def test_cli_tune_uses_harness_stop_before_llm(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
|
||||
100
tests/test_prepare_trace_windows.py
Normal file
100
tests/test_prepare_trace_windows.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
_SPEC = importlib.util.spec_from_file_location(
|
||||
"prepare_trace_windows",
|
||||
REPO_ROOT / "scripts" / "prepare_trace_windows.py",
|
||||
)
|
||||
assert _SPEC and _SPEC.loader
|
||||
ptw = importlib.util.module_from_spec(_SPEC)
|
||||
# Register before exec so dataclasses can resolve the module's annotations.
|
||||
sys.modules[_SPEC.name] = ptw
|
||||
_SPEC.loader.exec_module(ptw)
|
||||
|
||||
|
||||
class SessionCoherentSamplingTests(unittest.TestCase):
|
||||
def test_multi_hop_chain_resolves_to_root(self) -> None:
|
||||
root_of: dict[object, object] = {}
|
||||
# turn1 root, turn2 -> turn1, turn3 -> turn2 (multi-hop), streamed in order.
|
||||
self.assertEqual(
|
||||
ptw.resolve_session_root({"chat_id": 1, "parent_chat_id": -1, "turn": 1}, root_of),
|
||||
1,
|
||||
)
|
||||
self.assertEqual(
|
||||
ptw.resolve_session_root({"chat_id": 2, "parent_chat_id": 1, "turn": 2}, root_of),
|
||||
1,
|
||||
)
|
||||
self.assertEqual(
|
||||
ptw.resolve_session_root({"chat_id": 3, "parent_chat_id": 2, "turn": 3}, root_of),
|
||||
1,
|
||||
)
|
||||
|
||||
def test_unknown_parent_falls_back_to_parent_id(self) -> None:
|
||||
root_of: dict[object, object] = {}
|
||||
# parent never seen (fell outside the span): group siblings under the parent.
|
||||
self.assertEqual(
|
||||
ptw.resolve_session_root({"chat_id": 50, "parent_chat_id": 9, "turn": 2}, root_of),
|
||||
9,
|
||||
)
|
||||
self.assertEqual(
|
||||
ptw.resolve_session_root({"chat_id": 51, "parent_chat_id": 9, "turn": 2}, root_of),
|
||||
9,
|
||||
)
|
||||
|
||||
def test_all_turns_of_a_session_share_one_u(self) -> None:
|
||||
root_of: dict[object, object] = {}
|
||||
rows = [
|
||||
{"chat_id": 1, "parent_chat_id": -1, "turn": 1},
|
||||
{"chat_id": 2, "parent_chat_id": 1, "turn": 2},
|
||||
{"chat_id": 3, "parent_chat_id": 2, "turn": 3},
|
||||
]
|
||||
us = {
|
||||
ptw.session_uniform(
|
||||
seed=7,
|
||||
window_id="w",
|
||||
session_root=ptw.resolve_session_root(row, root_of),
|
||||
)
|
||||
for row in rows
|
||||
}
|
||||
self.assertEqual(len(us), 1)
|
||||
only = next(iter(us))
|
||||
self.assertGreaterEqual(only, 0.0)
|
||||
self.assertLess(only, 1.0)
|
||||
|
||||
def test_thresholding_keeps_or_drops_whole_sessions(self) -> None:
|
||||
# Two distinct sessions get distinct scores; a threshold either keeps a
|
||||
# session's every turn or none of them.
|
||||
seed, window_id = 20260325, "chat_w_x"
|
||||
sessions = {
|
||||
"A": [
|
||||
{"chat_id": 10, "parent_chat_id": -1},
|
||||
{"chat_id": 11, "parent_chat_id": 10},
|
||||
],
|
||||
"B": [
|
||||
{"chat_id": 20, "parent_chat_id": -1},
|
||||
{"chat_id": 21, "parent_chat_id": 20},
|
||||
],
|
||||
}
|
||||
root_of: dict[object, object] = {}
|
||||
scored: list[tuple[str, float]] = []
|
||||
for name, rows in sessions.items():
|
||||
for row in rows:
|
||||
root = ptw.resolve_session_root(row, root_of)
|
||||
u = ptw.session_uniform(seed=seed, window_id=window_id, session_root=root)
|
||||
scored.append((name, u))
|
||||
for name in sessions:
|
||||
us = {u for n, u in scored if n == name}
|
||||
self.assertEqual(len(us), 1, f"session {name} turns must share one u")
|
||||
for threshold in (0.0, 0.25, 0.5, 0.75, 1.0):
|
||||
for name in sessions:
|
||||
kept = {u <= threshold for n, u in scored if n == name}
|
||||
self.assertEqual(len(kept), 1, "a session must be kept/dropped as a whole")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user