Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions metainfer/tasks/dcu_kernel_auto_opt/WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ metainfer/tasks/dcu_kernel_auto_opt/
### 2.1 新建任务(表单字段,见 form.yaml)

关键字段:`operator`(Quantized GEMM)、`kernel_language`(HIP C++)、`target_hardware`(K500SM_AI/gfx928)、
`dtype`(INT8 W8A8)、`agent_framework`(ccb / dsh)、`agent_model`(ccb: Opus/Sonnet;dsh: deepseek-v4-flash)、`execution_mode`(Mock / Real INT8 W8A8 GEMM /
`dtype`(INT8 W8A8)、`agent_framework`(ccb / dsh)、`agent_model`(ccb: Opus/Sonnet;dsh: deepseek-flash-4.1 / deepseek-v4-flash)、`execution_mode`(Mock / Real INT8 W8A8 GEMM /
Generate & optimize / Infra smoke)、`target_repo_path`、`shape_assignment_mode`(AI automatic / Manual by GPU)、
`shape_scope`(All API shapes / Selected shapes only)、`shape_config`、`max_iterations`、`minimum_improvement_percent`、`extra_notes`。

Expand Down Expand Up @@ -177,11 +177,25 @@ TP8 另有 wq_b/wo_b (1024,4096)、gate_up (4096,512)、down (256,4096)、indexe
注意 TP1 目录前端可选、契约仍只收 tp_size 4/8,选 TP1 会同样在 prepare 被拒。
- **模型目录 TP8 大 prefill 边界(2026-08-27)**:Hy3/MiniMax M3/GLM5.2 的 TP8 形状支持
M=4096 优化("Selected shapes only" 手动选择,如 minimax 任务)。契约加
`MODEL_TP8_EXTRA_OPTIMIZATION_M_VALUES=(4096,)`(**不进** `DEFAULT_OPTIMIZATION_SHAPES`,
`MODEL_TP8_EXTRA_OPTIMIZATION_M_VALUES`(**不进** `DEFAULT_OPTIMIZATION_SHAPES`,
默认 42 不变,串行验证 fallback 不受影响);前端三个模型的 TP8 topology 加
`mValues: [2,16,3072,4096]`;基线表 15 条 `(8,4096,…)` 已实测
`mValues`(用 `W8A8_MODEL_TP8_M_VALUES` 常量);基线表 15 条 `(8,4096,…)` 已实测
(`tools/baseline/bench_triton_tp8_m4096.py`,int8_utils.matmul_kernel + CUDA-graph replay,
结果存 `tools/baseline/tp8_m4096_graph.json`)。DeepSeek TP8 不加 M=4096。
- **模型目录 TP8 短 decode 边界(2026-09-20)**:Hy3/MiniMax M3/GLM5.2 的 TP8 形状再加
M=8,即 `MODEL_TP8_EXTRA_OPTIMIZATION_M_VALUES=(8, 4096)`;前端
`W8A8_MODEL_TP8_M_VALUES = [2, 8, 16, 3072, 4096]`(DeepSeek TP1/TP8 仍是
`W8A8_M_VALUES=[2,16,3072]`,TP4 仍是 `W8A8_TP4_M_VALUES=[2,16,3072,4096]`)。
基线表 15 条 `(8,8,…)` 在 worker29/gfx928 实测三次
(`tools/baseline/bench_triton_tp8_m8.py --m 8 --mode graph`,协议同 2026-08-12 目录:
CUDA-graph replay、hot cache、warmups=10/samples=20/launches=5、BM16/BN32/BK256),
公布值取「每次 pass median 再取 median」;原始数据
`tools/baseline/tp8_m8_graph_run{1,2,3}.json`,汇总 `tp8_m8_graph_merged.json`。
12/15 形状三次 pass 极差 ≤1.6%(共享卡上 pass 2 的 minimax shared_down_proj 被同租户
干扰到 33.76 µs,取 median 后为 19.98 µs)。DeepSeek TP8 不加 M=8。
- **面板/契约/基线三层一致性测试**:`tests/test_shape_catalog_panel.py` 用 node 真实求值
`static/dkao-shape-input.js` 的 `MODEL_WORKLOADS`,断言面板给出的每个 TP4/TP8 shape
都能过契约校验且能查到基线(否则任务要到 baseline 阶段才报 ValueError)。
- `MIN_M=1, MAX_M=4096`;`WORKSPACE_BUDGET_BYTES=16MB`。
- **M=4096 时大部分 (N,K) 的 split-K workspace 容量为 0** → 大 M kernel 必须走 2D M-tile 路径,
不能依赖 split-K workspace。
Expand Down Expand Up @@ -257,7 +271,8 @@ TP8 另有 wq_b/wo_b (1024,4096)、gate_up (4096,512)、down (256,4096)、indexe
**规范 skill 以 `metainfer/tasks/dcu_kernel_auto_opt/skills/` 内的副本为种子**(int8-w8a8 家族,
2026-08-27 起随插件一起维护):新机器上 `sync_skill_libraries()`(或 WebUI 同步按钮)会自动
把缺失的 skill 补种进 `~/.dsh/skills/`,再镜像到 `~/.claude/skills/`;已有的库 skill 不被覆盖。
baseline 测量工具在 `tools/baseline/`(`bench_triton_tp4/tp8_m4096.py` + `int8_utils.py`)。
baseline 测量工具在 `tools/baseline/`(`bench_triton_tp4_m4096.py`、
`bench_triton_tp8_m4096.py`、`bench_triton_tp8_m8.py` + `int8_utils.py`)。
kernel-repos 默认在 MetaInfer 同级(`METAINFER_KERNEL_REPOS` 可改),不随插件目录走。
5. 改动任何行为后:更新本文件相关段落 + 跑 tests + 用真实任务验证(优先在 zth_meta 里)。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,19 @@ def _merge_kn_tables(
# the original three M values, so each TP4 operator gets exactly one M=4096
# shape while TP8 defaults are unchanged.
TP4_EXTRA_OPTIMIZATION_M_VALUES: Final[tuple[int, ...]] = (4096,)
# Model-catalog TP8 large-prefill boundary added on 2026-08-27. The DeepSeek
# TP8 default workload keeps the original three M values; Hy3 / MiniMax M3 /
# GLM5.2 TP8 operators are additionally optimizable at M=4096 via
# "Selected shapes only" (their (K,N) pairs are already in the allowed sets
# and their Triton CUDA-graph baselines are measured). This constant is not
# applied by _default_optimization_shapes(), so DEFAULT_OPTIMIZATION_SHAPES
# Model-catalog TP8 extra M values. The DeepSeek TP8 default workload keeps
# the original three M values; Hy3 / MiniMax M3 / GLM5.2 TP8 operators are
# additionally optimizable at these M values via "Selected shapes only"
# (their (K,N) pairs are already in the allowed sets and their Triton
# CUDA-graph baselines are measured). This constant is not applied by
# _default_optimization_shapes(), so DEFAULT_OPTIMIZATION_SHAPES
# (DeepSeek-only) and its serial-validation fallback scope are unchanged.
MODEL_TP8_EXTRA_OPTIMIZATION_M_VALUES: Final[tuple[int, ...]] = (4096,)
# M=4096: model-catalog large-prefill boundary added on 2026-08-27.
# M=8: model-catalog short decode boundary added on 2026-09-20; the
# Triton Graph baselines for the 15 catalog (K, N) pairs were
# measured on worker29/gfx928 with tools/baseline/
# bench_triton_tp8_m8.py (raw passes in tp8_m8_graph_run{1,2,3}.json).
MODEL_TP8_EXTRA_OPTIMIZATION_M_VALUES: Final[tuple[int, ...]] = (8, 4096)


def _default_optimization_shapes() -> tuple[dict[str, int | str], ...]:
Expand Down
161 changes: 161 additions & 0 deletions metainfer/tasks/dcu_kernel_auto_opt/assets/w8a8_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import json
import math
import os
import re
import statistics
import subprocess
import sys
import time
from pathlib import Path

try:
Expand All @@ -18,6 +21,159 @@
torch = None # type: ignore[assignment]


#: Device admission rule for anything that measures: VRAM <= 90% and HCU == 0.
#: This harness is the only thing that runs the candidate kernel, and an agent
#: can invoke it directly from its own shell, so the rule is enforced here too —
#: not only in the orchestrator that happens to launch us. A number taken while
#: another workload shares the card is noise (the same unchanged shape has been
#: seen at 884us -> 3047us), so we refuse to produce one.
GATE_EXIT_CODE = 75
_GATE_DRM_ROOT = Path("/sys/class/drm")
_GATE_SMI_ROW = re.compile(
r"^(?P<index>\d+)\s+[\d.]+C\s+[\d.]+W\s+\S+\s+[\d.]+W\s+"
r"(?P<vram>[\d.]+|N/A|n/a)%\s+(?P<util>[\d.]+|N/A|n/a)%"
)


def _gate_number(value):
try:
return float(value)
except (TypeError, ValueError):
return None


def _gate_hy_smi() -> dict:
"""``{gpu: {vram_percent, util_percent}}`` from hy-smi; empty if unreadable."""
for binary in ("hy-smi", "rocm-smi"):
try:
proc = subprocess.run([binary], capture_output=True, text=True,
timeout=10)
except (OSError, subprocess.SubprocessError):
continue
if proc.returncode != 0 or not proc.stdout.strip():
continue
out = {}
for line in proc.stdout.splitlines():
match = _GATE_SMI_ROW.match(line.strip())
if not match:
continue
out[int(match.group("index"))] = {
"vram_percent": _gate_number(match.group("vram").strip("%")),
"util_percent": _gate_number(match.group("util").strip("%")),
}
if out:
return out
return {}


def _gate_sysfs() -> dict:
"""Driver sysfs fallback: works when the management interface says N/A."""
out = {}
cards = []
try:
entries = sorted(_GATE_DRM_ROOT.iterdir(), key=lambda p: p.name)
except OSError:
return out
for entry in entries:
if not re.fullmatch(r"card\d+", entry.name):
continue
device = entry / "device"
try:
uevent = (device / "uevent").read_text(encoding="utf-8")
except OSError:
continue
if not any(driver in uevent for driver in ("hycu", "amdgpu", "hydcu")):
continue
if not (device / "mem_info_vram_total").is_file():
continue
cards.append(device)
for index, device in enumerate(cards):
row = {}
try:
row["util_percent"] = float(
(device / "gpu_busy_percent").read_text().strip())
except (OSError, ValueError):
pass
try:
used = float((device / "mem_info_vram_used").read_text().strip())
total = float((device / "mem_info_vram_total").read_text().strip())
if total > 0:
row["vram_percent"] = round(100.0 * used / total, 2)
except (OSError, ValueError, ZeroDivisionError):
pass
if row:
out[index] = row
return out


def _gate_violation(device: int) -> str:
"""``""`` when the device passes, else why it does not."""
from_smi = _gate_hy_smi().get(device) or {}
complete = (from_smi.get("vram_percent") is not None
and from_smi.get("util_percent") is not None)
readings = from_smi if complete else dict(_gate_sysfs().get(device) or {})
if not complete:
for key in ("vram_percent", "util_percent"):
if readings.get(key) is None and from_smi.get(key) is not None:
readings[key] = from_smi[key]
vram = readings.get("vram_percent")
util = readings.get("util_percent")
if vram is None or util is None:
return "device state unavailable (no HCU/VRAM reading)"
if vram > 90.0:
return f"VRAM {vram:.0f}% > 90%"
if util > 0.0:
return f"HCU {util:.0f}% > 0%"
return ""


def enforce_measurement_gate(device: int | None = None) -> None:
"""Wait for ``VRAM <= 90% and HCU == 0``; exit if it never happens.

``METAINFER_GPU_PREFLIGHT=0`` is the deliberate opt-out. Waiting is
deliberately bounded (``METAINFER_GATE_WAIT_SECONDS`` x
``METAINFER_GATE_MAX_WAITS``, default 30 min x 48 = 24 h): a shared card
must never silently turn into a measurement, and waiting forever is not an
answer either.
"""
if str(os.environ.get("METAINFER_GPU_PREFLIGHT", "")).strip().lower() in {
"0", "false", "no", "off"}:
return
if device is None:
raw = str(os.environ.get("HIP_VISIBLE_DEVICES")
or os.environ.get("ROCR_VISIBLE_DEVICES") or "0")
device = int(raw.split(",")[0].strip() or 0)
try:
wait_s = int(os.environ.get("METAINFER_GATE_WAIT_SECONDS", "") or 1800)
except ValueError:
wait_s = 1800
try:
max_waits = int(os.environ.get("METAINFER_GATE_MAX_WAITS", "") or 48)
except ValueError:
max_waits = 48
for attempt in range(1, max_waits + 1):
reason = _gate_violation(int(device))
if not reason:
return
if attempt > max_waits:
break
print(json.dumps({
"measurement_gate": "blocked", "device": int(device),
"attempt": attempt, "max_waits": max_waits,
"wait_seconds": wait_s, "reason": reason,
}), file=sys.stderr)
if attempt == max_waits:
break
time.sleep(max(0.0, float(wait_s)))
reason = _gate_violation(int(device)) or "device never became idle"
print(json.dumps({
"measurement_gate": "give_up", "device": int(device),
"checks": max_waits, "reason": reason,
}), file=sys.stderr)
raise SystemExit(GATE_EXIT_CODE)



def load_module(module_path: Path, name: str):
spec = importlib.util.spec_from_file_location(
name, module_path
Expand Down Expand Up @@ -331,6 +487,11 @@ def _env_default(name: str, fallback: int) -> int:
}, sort_keys=True))
return 0

# Everything below initialises the device and measures on it. CPU-only runs
# (--self-test, --prepare-reference) returned above, so they are never
# blocked; every device-touching run has to pass the gate first.
enforce_measurement_gate()

source = args.source.resolve()
fixed_contract = source / "int8_w8a8_gemm_api.py"
if fixed_contract.is_file():
Expand Down
10 changes: 6 additions & 4 deletions metainfer/tasks/dcu_kernel_auto_opt/bridge/dsh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ prompt 从 stdin 传入,stdout 输出逐行 stream-json 事件
WebUI new-task 表单中 dcu-kernel-auto-opt 增加 **Agent framework** 选择:

- `ccb` → Claude Code(模型 `Sonnet` / `Opus`)
- `dsh` → DeepSeek Harness(模型 `deepseek-v4-flash`,即
`deepseek/deepseek-v4-flash-0731`)
- `dsh` → DeepSeek Harness:
- `deepseek-flash-4.1` → `deepseek/deepseek-flash`(**默认**,4.1 Flash)
- `deepseek-v4-flash` → `deepseek/deepseek-v4-flash-0731`(旧 pinned 构建,可复现历史运行)

orchestrator CLI 在 `agent_framework=dsh` 时自动把 `claude_bin` 指向本目录的
`dsh_agent.py`(`--claude-bin` 显式传入时优先),无需改动 MetaInfer 共享代码。
Expand All @@ -55,15 +56,16 @@ runtime 只内置 `deepseek-official` adapter(`llm-deepseek`),它通过

- provider:`deepseek-official`(runtime adapter;TokenHub 经 base_url 覆盖)
- baseURL:`https://tokenhub.tencentmaas.com/plan/v3`
- 模型:`deepseek/deepseek-v4-flash-0731`(默认)
- 模型:`deepseek/deepseek-flash`(默认,= 表单里的 `deepseek-flash-4.1`);
旧 pinned 构建 `deepseek/deepseek-v4-flash-0731`(= `deepseek-v4-flash`)
- API key:`~/.dsh/.credentials.yaml` 的 `TENCENT_API_KEY`

`dsh_agent.py` 的环境变量:

| 变量 | 默认 | 说明 |
|---|---|---|
| `DSH_AGENT_PROVIDER` | `deepseek-official` | DSH runtime provider(dev runtime 仅内置此 adapter) |
| `DSH_AGENT_MODEL` | `deepseek/deepseek-v4-flash-0731` | 模型覆盖 |
| `DSH_AGENT_MODEL` | `deepseek/deepseek-flash` | 默认模型覆盖(只改 `deepseek-flash-4.1` 这个默认标签的 id) |
| `DSH_AGENT_BASE_URL` | `https://tokenhub.tencentmaas.com/plan/v3` | 模型端点(`DEEPSEEK_BASE_URL` 优先) |
| `TENCENT_API_KEY` / `DEEPSEEK_API_KEY` | 凭据文件兜底 | API key |
| `DSH_AGENT_CORDIS` | 本目录 `cordis.yml` | 自定义组合路径 |
Expand Down
36 changes: 25 additions & 11 deletions metainfer/tasks/dcu_kernel_auto_opt/bridge/dsh/dsh_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
DSH_AGENT_PROVIDER provider name for the DSH runtime
(default: deepseek-official — the only adapter
shipped by the dev-checkout runtime)
DSH_AGENT_MODEL model override (default deepseek/deepseek-v4-flash-0731)
DSH_AGENT_MODEL model override (default deepseek/deepseek-flash,
the 4.1 Flash label deepseek-flash-4.1)
DSH_AGENT_BASE_URL model endpoint; falls back to DEEPSEEK_BASE_URL
(default: https://tokenhub.tencentmaas.com/plan/v3)
TENCENT_API_KEY preferred API key (matches ~/.dsh/settings.yaml)
Expand Down Expand Up @@ -71,9 +72,11 @@ def default_provider() -> str:


def default_model() -> str:
"""Default DSH model id (the 4.1 Flash build) unless DSH_AGENT_MODEL pins
another one. Matches orchestrator/config.py DSH_DEFAULT_MODEL_ID."""
return (
os.environ.get("DSH_AGENT_MODEL", "deepseek/deepseek-v4-flash-0731")
.strip() or "deepseek/deepseek-v4-flash-0731"
os.environ.get("DSH_AGENT_MODEL", "deepseek/deepseek-flash")
.strip() or "deepseek/deepseek-flash"
)


Expand Down Expand Up @@ -144,13 +147,20 @@ def default_api_key() -> str:


# --------------------------------------------------------------------------- #
# Model mapping (legacy Claude labels -> this host's DSH model ids)
# Model mapping (legacy Claude labels / form labels -> this host's DSH ids)
# --------------------------------------------------------------------------- #

#: Form labels produced by both MetaInfer task frontends (dcu-kernel-auto-opt's
#: agent_model field and harness-evolve's evolve_model field). Kept in sync with
#: orchestrator/config.py (DSH_MODEL_IDS) — the orchestrator resolves the label
#: itself, so this map only serves callers that hand the wrapper a bare label.
_MODEL_MAP = {
"opus": "deepseek-v4-pro",
"sonnet": "deepseek-v4-flash",
"haiku": "deepseek-v4-flash",
"deepseek-flash-4.1": "deepseek/deepseek-flash",
"deepseek-flash": "deepseek/deepseek-flash",
"deepseek-v4-flash": "deepseek/deepseek-v4-flash-0731",
"opus": "deepseek/deepseek-v4-flash-0731",
"sonnet": "deepseek/deepseek-v4-flash-0731",
"haiku": "deepseek/deepseek-v4-flash-0731",
}


Expand All @@ -160,11 +170,8 @@ def map_model(requested: Optional[str]) -> str:
key = requested.strip().lower()
if key in _MODEL_MAP:
return _MODEL_MAP[key]
if key == "deepseek-v4-flash":
# Bare user-facing label -> the pinned host model id.
return default_model()
if key.startswith("deepseek"):
# Full model id (e.g. deepseek/deepseek-v4-flash-0731): pass through.
# Full model id (e.g. deepseek/deepseek-flash): pass through.
return key
# Unknown label: keep the caller's intent but stay on a known model id.
return default_model()
Expand Down Expand Up @@ -294,6 +301,13 @@ def _headless_backend_main(args: Any, prompt: str) -> int:
sub-agent is still working so SubAgentManager's no-output watchdog never
mistakes a long task for a stuck process; the final assistant text is
emitted as the last message, matching the ccb stream-json contract.

Model selection caveat: the ``dsh --profile headless`` app takes no
``--model`` flag, so it runs whatever the user's DSH settings document
(``~/.dsh/settings.yaml`` -> ``agent-default-model``) selects — the host
default, not ``--model``. Emit the requested model in the system event so
the mismatch is visible instead of silent. The SDK backend below does honour
``--model``.
"""
import subprocess
import tempfile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
#
# Optional env:
# DSH_AGENT_MAX_TOKENS per-request output cap (default 65536)
# DSH_AGENT_MODEL model override (default deepseek/deepseek-v4-flash-0731)
# DSH_AGENT_MODEL model override (default deepseek/deepseek-flash,
# the 4.1 Flash build)
# METAINFER_GPU_IDS restrict GPUs, e.g. 4,5,6,7

set -euo pipefail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def main() -> int:
WORK.mkdir(parents=True, exist_ok=True)
cfg = DeepSeekHarnessConfig(
provider="deepseek-official",
model="deepseek-v4-flash",
model="deepseek/deepseek-flash",
max_tokens=2048,
cwd=str(WORK),
session_root=str(SESSION_ROOT),
Expand Down
Loading
Loading