From b26e8f92d8c09f0434fac1c97d80ea939cfb27e1 Mon Sep 17 00:00:00 2001 From: Reiase Date: Sun, 9 Aug 2026 14:24:28 +0800 Subject: [PATCH 1/6] feat(cost): add evidence-backed cost providers --- docs/design/performance/database.en.md | 25 +- docs/design/performance/database.zh.md | 25 +- docs/design/performance/index.en.md | 6 +- docs/design/performance/index.zh.md | 6 +- docs/design/performance/providers.en.md | 195 +++++++ docs/design/performance/providers.zh.md | 195 +++++++ docs/experiments/vidur-baseline.en.md | 2 + docs/experiments/vidur-baseline.zh.md | 2 + docs/modeling/inference.en.md | 2 +- docs/modeling/inference.zh.md | 2 +- docs/project/status.en.md | 9 +- docs/project/status.zh.md | 9 +- mkdocs.yml | 2 + pyproject.toml | 5 +- .../compiler/analysis/__init__.py | 49 +- .../compiler/analysis/cost/__init__.py | 59 +++ .../compiler/analysis/cost/aiconfigurator.py | 247 +++++++++ .../compiler/analysis/cost/database.py | 249 +++++++++ .../compiler/analysis/cost/importers.py | 337 ++++++++++++ .../compiler/analysis/cost/protocol.py | 427 ++++++++++++++++ .../compiler/analysis/cost/roofline.py | 160 ++++++ .../compiler/analysis/inference_cost.py | 211 +++++++- src/blueprinting/compiler/analysis/vidur.py | 291 +++++++++++ tests/compiler/test_cost_model_providers.py | 481 ++++++++++++++++++ uv.lock | 8 +- 25 files changed, 2946 insertions(+), 58 deletions(-) create mode 100644 docs/design/performance/providers.en.md create mode 100644 docs/design/performance/providers.zh.md create mode 100644 src/blueprinting/compiler/analysis/cost/__init__.py create mode 100644 src/blueprinting/compiler/analysis/cost/aiconfigurator.py create mode 100644 src/blueprinting/compiler/analysis/cost/database.py create mode 100644 src/blueprinting/compiler/analysis/cost/importers.py create mode 100644 src/blueprinting/compiler/analysis/cost/protocol.py create mode 100644 src/blueprinting/compiler/analysis/cost/roofline.py create mode 100644 tests/compiler/test_cost_model_providers.py diff --git a/docs/design/performance/database.en.md b/docs/design/performance/database.en.md index a5057d1..8fe1304 100644 --- a/docs/design/performance/database.en.md +++ b/docs/design/performance/database.en.md @@ -3,11 +3,11 @@ The performance database is a revisioned evidence store behind a normalized query protocol. It answers a precise question—how an architecture component or legal implementation is expected to behave in a declared context—without hiding architecture choices or calibration knobs inside a lookup table. !!! note "Design status" - The general estimator still loads `HardwareProfile` directly. Static inference now distinguishes an admissible cost-provider contract from a read-only baseline contract; the Vidur CSV adapter is baseline-only. The request/result/store design on this page remains the accepted generalization target. + The first general slice is implemented as `CostQuery`, `CostEstimate`, `CostResolver`, `PerformanceDatabase`, and typed providers. Static inference consumes that resolver; training still loads `HardwareProfile` directly pending equivalence migration. `VidurProfileBaseline` remains baseline-only, while the separate `VidurProfileImporter` is an explicit evidence-promotion path. See [Cost Providers and Performance-Data Imports](providers.md). ## Request contract -An `EstimateRequest` identifies all dimensions that may materially affect a result: +The implemented `CostQuery`—the first slice of the broader `EstimateRequest` design—identifies dimensions that may materially affect a task-latency result: ```text subject identity @@ -28,7 +28,7 @@ Optional fields are explicit unknowns, not omitted cache-key dimensions. Provide ## Result contract -An `EstimateResult` contains more than a scalar: +The implemented `CostEstimate` contains more than a scalar: ```text metrics latency, energy, bandwidth, utilization, counters @@ -57,16 +57,15 @@ Every raw sample records units, warm-up, repetition count, synchronization metho ## Provider protocol -A provider exposes four operations conceptually: +A provider currently exposes the following normalized operations; a richer `explain()` view remains planned: ```python -class EstimateProvider(Protocol): +class CostProvider(Protocol): @property def revision(self) -> str: ... - def supports(self, request: EstimateRequest) -> Support: ... - def estimate(self, request: EstimateRequest) -> EstimateResult: ... - def explain(self, result: EstimateResult) -> EvidenceTrace: ... + def supports(self, query: CostQuery) -> CostSupport: ... + def estimate(self, query: CostQuery) -> CostEstimate: ... ``` `supports()` reports domain coverage and required missing fields before expensive evaluation. `estimate()` is deterministic for a request and provider revision unless the result explicitly records a seed and stochastic protocol. @@ -99,10 +98,10 @@ Forbidden inputs include a benchmark case ID, comparison-oracle total time, or a The existing `HardwareProfile` already supplies useful versioned curves for matrix/vector throughput, memory transfer, and collectives. Migration should preserve its behavior behind providers: -1. convert portable tasks into normalized requests; -2. wrap the current profile as an analytical/system-evidence provider; -3. reproduce the current Calculon experiment through the resolver; -4. add a raw measurement provider and evidence manifests; -5. replace direct estimator/profile coupling only after equivalence tests pass. +1. **Done for static inference:** convert portable tasks into normalized queries; +2. **Done:** wrap the current profile as a roofline/system-evidence provider; +3. **Pending for training:** reproduce the current Calculon experiment through the resolver; +4. **Implemented slice:** add exact measured/simulated records with source revisions and file digests; richer environment manifests remain pending; +5. replace training estimator/profile coupling only after equivalence tests pass. This staged adapter keeps the validated workload analysis intact while making provenance, uncertainty, and future hardware simulators first-class. diff --git a/docs/design/performance/database.zh.md b/docs/design/performance/database.zh.md index 54f69b1..c9a40ef 100644 --- a/docs/design/performance/database.zh.md +++ b/docs/design/performance/database.zh.md @@ -3,11 +3,11 @@ 性能数据库是 normalized query protocol 背后的版本化 evidence store。它回答一个精确问题——某个 architecture component 或合法 implementation 在明确 context 中预计如何表现——但不会把 architecture choice 或 calibration knob 隐藏在 lookup table 中。 !!! note "设计状态" - 通用 estimator 仍直接加载 `HardwareProfile`。Static inference 已区分可参与估算的 cost-provider contract 与只读 baseline contract;Vidur CSV adapter 仅属于 baseline。本页的 request/result/store 仍是已接受的通用化目标。 + 第一版通用 slice 已实现为 `CostQuery`、`CostEstimate`、`CostResolver`、`PerformanceDatabase` 与 typed provider。Static inference 已消费该 resolver;training 在 equivalence migration 前仍直接加载 `HardwareProfile`。`VidurProfileBaseline` 保持 baseline-only;独立的 `VidurProfileImporter` 才是显式 evidence promotion 路径。详见 [Cost Provider 与性能数据导入](providers.md)。 ## Request Contract -`EstimateRequest` 标识所有可能实质影响结果的维度: +已实现的 `CostQuery`——更广义 `EstimateRequest` 设计的第一版 slice——标识所有可能实质影响 task-latency result 的维度: ```text subject identity @@ -28,7 +28,7 @@ Optional field 是显式 unknown,而不是从 cache key 中省略的维度。P ## Result Contract -`EstimateResult` 不只是一个标量: +已实现的 `CostEstimate` 不只是一个标量: ```text metrics latency, energy, bandwidth, utilization, counters @@ -57,16 +57,15 @@ Normalization 不会抹掉 provider detail。Provider-specific payload 可以作 ## Provider Protocol -Provider 在概念上暴露四个操作: +Provider 当前暴露以下 normalized operation;更丰富的 `explain()` view 仍属于后续工作: ```python -class EstimateProvider(Protocol): +class CostProvider(Protocol): @property def revision(self) -> str: ... - def supports(self, request: EstimateRequest) -> Support: ... - def estimate(self, request: EstimateRequest) -> EstimateResult: ... - def explain(self, result: EstimateResult) -> EvidenceTrace: ... + def supports(self, query: CostQuery) -> CostSupport: ... + def estimate(self, query: CostQuery) -> CostEstimate: ... ``` `supports()` 在昂贵求值前报告 domain coverage 与缺失的 required field。对于同一 request/provider revision,`estimate()` 必须确定;若使用随机协议,则结果要显式记录 seed 与 stochastic protocol。 @@ -99,10 +98,10 @@ Calibration 从 observation 学习 target-wide 或 implementation-family respons 现有 `HardwareProfile` 已经提供 matrix/vector throughput、memory transfer 与 collective 的有用版本化 curve。迁移应通过 provider 保持现有行为: -1. 把 portable task 转换为 normalized request; -2. 将当前 profile 包装为 analytical/system-evidence provider; -3. 通过 resolver 复现当前 Calculon experiment; -4. 增加 raw measurement provider 与 evidence manifest; -5. 只有 equivalence test 通过后,才替换 estimator/profile 的直接耦合。 +1. **Static inference 已完成:**把 portable task 转换为 normalized query; +2. **已完成:**将当前 profile 包装为 roofline/system-evidence provider; +3. **Training 待完成:**通过 resolver 复现当前 Calculon experiment; +4. **已实现 slice:**加入带 source revision/file digest 的 exact measured/simulated record;更完整 environment manifest 待实现; +5. 只有 equivalence test 通过后,才替换 training estimator/profile 的直接耦合。 这种分阶段 adapter 能保留已验证的 workload analysis,同时让 provenance、uncertainty 与未来 hardware simulator 成为 first-class capability。 diff --git a/docs/design/performance/index.en.md b/docs/design/performance/index.en.md index ad2a330..0bd98cb 100644 --- a/docs/design/performance/index.en.md +++ b/docs/design/performance/index.en.md @@ -76,9 +76,9 @@ This does not mean every stage is assigned a wall-clock duration. Early stages a ## Current implementation boundary -The repository currently provides a typed `HardwareProfile`, peak-only and system-evidence efficiency curves, block/iteration estimates, and an auditable Calculon experiment. Static inference additionally separates an admissible `InferenceCostProvider.resolve()` contract from a read-only `InferenceBaseline.lookup()` contract. Vidur implements only the latter and is consumed by a post-hoc experiment after Blueprinting lowering and costing. These are implemented validation slices, not yet a general architecture-exploration evidence service. +The repository now provides normalized `CostQuery`/`CostEstimate` contracts, ordered `CostResolver` policy, analytical `RooflineCostProvider`, an immutable exact-selector `PerformanceDatabase`, generic simulator table ingestion, and explicit Vidur and AIConfigurator importers. Static inference derives queries from portable task facts and resolves both task and pipeline communication costs. It still separates admissible cost providers from the read-only `InferenceBaseline.lookup()` comparison contract. -The general normalized request/result protocol, resolver/registry, evidence store, uncertainty model, discrete-event simulator, observation ingestion, and calibration service remain target architecture. The inference protocols are migration seams, not the final universal schema. Those general contracts should wrap and then replace direct `HardwareProfile` coupling without changing `PortablePlanIR` or allowing a comparison oracle into derivation. +This remains an implemented slice, not a complete architecture-exploration evidence service. The database supports exact declared selectors and repeated-sample uncertainty, but not calibrated interpolation, a durable append-only raw-evidence service, environment manifests, discrete-event simulation, observation ingestion, or calibration. Training still uses direct `HardwareProfile` costing until Calculon equivalence tests protect its resolver migration. See [Cost Providers and Performance-Data Imports](providers.md) for the executable boundary. ## Design invariants @@ -90,4 +90,4 @@ The general normalized request/result protocol, resolver/registry, evidence stor 6. Simulation and runtime use the same concrete command identities. 7. A cache hit is legal only when the complete semantic context matches. -The [performance database](database.md) specifies evidence storage and resolution. [Simulation and calibration](simulation.md) specify plan-level composition and the feedback loop. +The [cost-provider implementation](providers.md) documents the executable APIs. The [performance database](database.md) specifies the broader storage and resolution design. [Simulation and calibration](simulation.md) specify plan-level composition and the feedback loop. diff --git a/docs/design/performance/index.zh.md b/docs/design/performance/index.zh.md index 1510002..d0fc93a 100644 --- a/docs/design/performance/index.zh.md +++ b/docs/design/performance/index.zh.md @@ -76,9 +76,9 @@ Planner 可以优化 expected latency、conservative bound 或 risk-adjusted obj ## 当前实现边界 -仓库当前提供强类型 `HardwareProfile`、peak-only 与 system-evidence efficiency curve、block/iteration estimate,以及可审计的 Calculon experiment。Static inference 进一步区分可参与估算的 `InferenceCostProvider.resolve()` 与只读的 `InferenceBaseline.lookup()`;Vidur 只实现后者,并在 Blueprinting lowering 和 costing 全部完成后由 post-hoc experiment 使用。这些是已实现的 validation slice,但尚不是通用 architecture-exploration evidence service。 +仓库现在已经提供 normalized `CostQuery`/`CostEstimate` contract、ordered `CostResolver` policy、analytical `RooflineCostProvider`、immutable exact-selector `PerformanceDatabase`、通用 simulator table ingestion,以及显式 Vidur/AIConfigurator importer。Static inference 从 portable task facts 推导 query,并解析 task 与 pipeline communication cost;同时仍严格区分 admissible cost provider 与只读 `InferenceBaseline.lookup()` comparison contract。 -通用 normalized request/result protocol、resolver/registry、evidence store、uncertainty model、discrete-event simulator、observation ingestion 与 calibration service 仍属于目标架构。Inference protocol 是迁移 seam,不是最终 universal schema。通用 contract 应先包裹、再替代对 `HardwareProfile` 的直接耦合,而不改变 `PortablePlanIR`,也不允许 comparison oracle 进入推导。 +这仍是 implemented slice,而不是完整 architecture-exploration evidence service。Database 支持 exact declared selector 与 repeated-sample uncertainty,但还没有 calibrated interpolation、durable append-only raw-evidence service、environment manifest、discrete-event simulation、observation ingestion 或 calibration。Training 在 Calculon equivalence test 能保护 resolver migration 之前,仍直接使用 `HardwareProfile` costing。可运行边界见 [Cost Provider 与性能数据导入](providers.md)。 ## 设计不变量 @@ -90,4 +90,4 @@ Planner 可以优化 expected latency、conservative bound 或 risk-adjusted obj 6. Simulation 与 runtime 使用同一 concrete command identity。 7. 只有完整 semantic context 一致时 cache hit 才合法。 -[性能数据库](database.md)定义 evidence storage 与 resolution;[仿真与校准](simulation.md)定义 plan-level composition 和反馈闭环。 +[Cost Provider 实现](providers.md)说明可运行 API;[性能数据库](database.md)定义更完整的 evidence storage 与 resolution 设计;[仿真与校准](simulation.md)定义 plan-level composition 和反馈闭环。 diff --git a/docs/design/performance/providers.en.md b/docs/design/performance/providers.en.md new file mode 100644 index 0000000..2c0cc6a --- /dev/null +++ b/docs/design/performance/providers.en.md @@ -0,0 +1,195 @@ +# Cost Providers and Performance-Data Imports + +Blueprinting now has a runnable task-cost seam between portable workload facts and plan-level composition. The implementation is intentionally narrower than a complete performance service: it resolves latency for one operator or communication task, preserves source identity, and refuses ambiguous evidence. It does not yet interpolate arbitrary shapes, model contention, or replace schedule simulation. + +## Why this boundary exists + +`PortablePlanIR` owns work—operations, bytes, messages, dependencies, and semantic shape. It must not own a duration measured on one runtime or one GPU. A cost query combines that immutable work with late-bound architecture, runtime, implementation, and deployment context: + +```text +PortablePlanIR task + late-bound CostQueryContext + -> CostQuery + -> CostResolver(ordered providers) + 1. PerformanceDatabaseProvider + 2. another measured/simulated provider + 3. RooflineCostProvider + -> CostEstimate + provider attempts + revisions + -> inference cost view / future schedule simulation +``` + +The resolver selects one provider. It does not multiply corrections or average unrelated sources. A miss may proceed to the next provider; ambiguous or internally conflicting evidence is an error and cannot be hidden by a fallback. + +## Normalized contracts + +`CostQuery` has first-class fields for: + +- subject (`operator` or `communication`), operation, hardware, and datatype; +- operations, read/write bytes, message bytes, participants, network tier, and engine; +- hardware, implementation, runtime, topology, and power-mode revisions; +- operation-specific dimensions such as `m/n/k`, batch, tokens, context, heads, or backend. + +Every field participates in the canonical query digest. Empty optional identity fields mean “unknown”; they do not match a record that explicitly requires a runtime or kernel. + +`CostEstimate` reports latency together with method, match kind, provider/source revisions, raw record IDs, validity selector, uncertainty, components, and assumptions. The current methods are measured, simulated, analytical, calibrated, and vendor-model; the current database provider emits only exact-selector results. + +## Roofline provider + +`RooflineCostProvider` wraps a versioned `HardwareProfile`. For a local operator it computes: + +```text +compute_time = operations / effective_engine_throughput +memory_time = (read_bytes + write_bytes) / effective_memory_bandwidth +roofline = max(compute_time, memory_time) +``` + +Peak-only and system-evidence efficiency modes are explicit. `processing_mode="roofline"` uses the max bound; `"no_overlap"` uses the explicit serialized sum; `"profile"` adopts the legacy profile setting. The estimate exposes both components, arithmetic intensity, and the selected bottleneck. + +Communication queries use the selected `NetworkProfile`: collective volume rule, participant count, bandwidth, efficiency, and launch latency remain visible inputs. This is an analytical collective model, not a claim of cycle-accurate network simulation. + +## Immutable performance database + +A `PerformanceRecord` contains a latency sample, a declared selector, and `EvidenceProvenance`: + +```text +source + source revision +importer revision + source-file digest +method + raw record ID +selector + optional source metadata +``` + +`PerformanceDatabase` is canonically serializable and content-addressed. `PerformanceDatabaseProvider` finds all records whose declared selector is an exact subset match of the query context, then selects the most-specific selector. Repeated samples from the same provenance group are aggregated by median and report population deviation and range. + +Provider construction builds an immutable index by core identity, selector schema, and typed selector values. Query resolution therefore does not linearly scan a full imported corpus; the database digest is computed once and cached as provider identity. + +Two equally specific but different provenance groups are ambiguous. The provider rejects them instead of choosing the fastest row or silently blending revisions. Interpolation is deliberately absent from this first slice; it should later be a named provider with an explicit validity domain and extrapolation distance. + +## Importing a simulator table + +`SimulatorPerformanceImporter` accepts CSV, JSON/JSONL, and—when the `performance-data` extra is installed—Parquet. A `TabularImportSpec` declares every mapping and the latency unit: + +```python +from blueprinting.compiler.analysis import ( + CostSubject, + EstimateMethod, + LatencyUnit, + SimulatorPerformanceImporter, + TabularImportSpec, +) +from blueprinting.compiler.frozen import FrozenDict + +spec = TabularImportSpec( + name="noc-sim-r7", + subject=CostSubject.COMMUNICATION, + source="our-network-simulator", + source_revision="git:4e5c...", + method=EstimateMethod.SIMULATED, + latency_column="latency_us", + latency_unit=LatencyUnit.MICROSECONDS, + operation_column="collective", + hardware="lpu-candidate-17", + datatype_column="dtype", + selector_columns=FrozenDict({ + "message_bytes": "bytes", + "participants": "ranks", + }), + selector_types=FrozenDict({ + "message_bytes": "int", + "participants": "int", + }), + record_id_column="run_id", +) +database = SimulatorPerformanceImporter.from_file("collectives.csv", spec) +``` + +The importer does not infer units or coerce selector columns heuristically. Invalid, empty, non-finite, or incorrectly typed values fail ingestion. + +## Importing Vidur profiles + +`VidurProfileImporter.from_csv()` understands Vidur's attention and MLP profile schemas and converts supported timing columns into normalized records. Attention records preserve phase, batch, context/cache semantics, TP degree, backend, block size, and model shape. Compute records preserve token count and model/parallel dimensions. + +This does **not** change the oracle boundary: + +- `VidurProfileBaseline.lookup()` remains a post-hoc comparison oracle and is not a `CostProvider`; +- `VidurProfileImporter` is an explicit user action that creates a new Blueprinting-owned evidence database; +- only a `PerformanceDatabaseProvider` deliberately installed in a resolver may affect costing. + +The distinction prevents a validation baseline from leaking into lowering while still allowing independently reviewed profile data to become admissible evidence. + +## Importing NVIDIA AIConfigurator data + +The official [AIConfigurator repository](https://github.com/ai-dynamo/aiconfigurator) stores component performance in heterogeneous operation-family Parquet tables. `AIConfiguratorPerformanceImporter` currently has strict adapters for: + +| Upstream table | Normalized subject/operation | Required shape context | +|---|---|---| +| `gemm_perf` | operator / `gemm` | `m`, `n`, `k`, dtype | +| `context_attention_perf` | operator / `attention_core` prefill | batch, input length, local heads, head size, KV dtype | +| `generation_attention_perf` | operator / `attention_core` decode | batch, `isl + step`, local heads, head size, KV dtype | +| `custom_allreduce_perf` | communication / `all_reduce` | message bytes, GPU count, backend | + +AIConfigurator latency is normalized from milliseconds to seconds. Framework, framework version, and kernel source become required runtime/implementation selectors; device and upstream operation identity remain provenance metadata. Therefore an AIConfigurator row cannot match a hardware-only query whose runtime/kernel is unknown. + +```python +database = AIConfiguratorPerformanceImporter.from_file( + "gemm_perf.parquet", + hardware_name="h100-sxm", + source_revision="8fc57cf...", +) +``` + +AIConfigurator's final `best_config_topn.csv` and Pareto outputs describe serving configurations, not atomic operator evidence. They are intentionally not imported as task costs. A future plan-level comparison adapter may consume them without flattening TTFT/TPOT into kernel latency. + +## Using the resolver in inference costing + +Static inference can use the new resolver while the legacy `InferenceCostProvider` seam remains compatible: + +```python +resolver = CostResolver(( + PerformanceDatabaseProvider(database), + RooflineCostProvider(hardware), +)) + +context = CostQueryContext( + runtime="vllm", + runtime_revision="0.24.0", + implementations=FrozenDict({ + "attention_pre_projection": "torch.nn.functional.linear", + "all_reduce": "vLLM_custom_graph", + }), + operation_dimensions=FrozenDict({ + "all_reduce": FrozenDict({"backend": "vllm_graph"}), + }), +) + +estimate = estimate_inference_phase( + plan, + hardware, + cost_resolver=resolver, + cost_context=context, +) +``` + +Inference task queries are derived from canonical `PlanTask.workload`; GEMM dimensions and local attention-head dimensions are derived from model and TP facts. Tensor-parallel collectives and pipeline P2P use the same resolver. All tasks must be covered by an installed provider—normally an exact database followed by roofline—so an unknown task never becomes zero. + +## Implemented boundary and next steps + +Implemented now: + +- normalized immutable query/estimate/support/resolution contracts; +- deterministic ordered resolution with an auditable attempt trace; +- analytical roofline and collective fallback; +- immutable exact-selector database with repeated-sample aggregation; +- generic simulator/profiler table ingestion; +- explicit Vidur and four-family AIConfigurator ingestion; +- inference task and pipeline integration with regression tests. + +Still missing: + +- calibrated interpolation/extrapolation providers and confidence policy; +- append-only raw-sample/environment-manifest storage beyond the portable snapshot; +- training-path migration through resolver equivalence tests; +- first-class KV-head/GQA and runtime legalization context in target binding; +- contention, overlap, queueing, and plan-level discrete-event simulation; +- energy/power metrics as normalized planner objectives; +- observation ingestion and calibration revisions. + +These omissions are important: task latency resolution is an evidence layer, not a completed hardware or serving simulator. diff --git a/docs/design/performance/providers.zh.md b/docs/design/performance/providers.zh.md new file mode 100644 index 0000000..8e8502a --- /dev/null +++ b/docs/design/performance/providers.zh.md @@ -0,0 +1,195 @@ +# Cost Provider 与性能数据导入 + +Blueprinting 现在已经具备一条可运行的 task-cost 接缝,位于 portable workload facts 与 plan-level composition 之间。当前实现刻意小于完整性能服务:它解析单个 operator 或 communication task 的 latency,保留 source identity,并拒绝歧义 evidence;它还不会对任意 shape 做插值、建模 contention,也不替代 schedule simulation。 + +## 为什么要有这条边界 + +`PortablePlanIR` 只拥有 work——operations、bytes、message、dependency 与 semantic shape。它不能拥有某个 runtime 或某张 GPU 上测得的 duration。Cost query 把这些 immutable work 与迟绑定的 architecture、runtime、implementation 和 deployment context 组合起来: + +```text +PortablePlanIR task + late-bound CostQueryContext + -> CostQuery + -> CostResolver(ordered providers) + 1. PerformanceDatabaseProvider + 2. 其他 measured/simulated provider + 3. RooflineCostProvider + -> CostEstimate + provider attempts + revisions + -> inference cost view / future schedule simulation +``` + +Resolver 只选择一个 provider,不会把 correction 相乘,也不会平均无关 source。普通 miss 可以继续尝试下一个 provider;歧义或内部冲突 evidence 是错误,不能被 fallback 掩盖。 + +## Normalized Contract + +`CostQuery` 的 first-class field 包括: + +- subject(`operator` 或 `communication`)、operation、hardware 与 datatype; +- operations、read/write bytes、message bytes、participants、network tier 与 engine; +- hardware、implementation、runtime、topology 与 power-mode revision; +- `m/n/k`、batch、token、context、head 或 backend 等 operation-specific dimension。 + +所有字段都参与 canonical query digest。可选 identity 的空字符串表示“未知”;它不会命中一条明确要求某个 runtime 或 kernel 的 record。 + +`CostEstimate` 除 latency 外,还返回 method、match kind、provider/source revision、raw record ID、validity selector、uncertainty、component 与 assumption。当前 method 包括 measured、simulated、analytical、calibrated 与 vendor-model;当前 database provider 只产生 exact-selector 结果。 + +## Roofline Provider + +`RooflineCostProvider` 包装版本化 `HardwareProfile`。对于 local operator,它计算: + +```text +compute_time = operations / effective_engine_throughput +memory_time = (read_bytes + write_bytes) / effective_memory_bandwidth +roofline = max(compute_time, memory_time) +``` + +Peak-only 与 system-evidence efficiency mode 是显式选项。`processing_mode="roofline"` 使用 max bound;`"no_overlap"` 使用显式串行求和;`"profile"` 采用 legacy profile 的设置。Estimate 会暴露两个 component、arithmetic intensity 与最终 bottleneck。 + +Communication query 使用选定的 `NetworkProfile`:collective volume rule、participant count、bandwidth、efficiency 与 launch latency 都是可见输入。这是 analytical collective model,并不声称 cycle-accurate network simulation。 + +## Immutable 性能数据库 + +每个 `PerformanceRecord` 包含一个 latency sample、声明过的 selector 与 `EvidenceProvenance`: + +```text +source + source revision +importer revision + source-file digest +method + raw record ID +selector + optional source metadata +``` + +`PerformanceDatabase` 支持 canonical serialization,并且 content-addressed。`PerformanceDatabaseProvider` 先寻找 selector 对 query context 做精确子集匹配的 record,再选择 specificity 最高的一组。同一 provenance group 的重复 sample 用 median 聚合,同时报告 population deviation 与 range。 + +Provider 构造时会按 core identity、selector schema 与 typed selector value 建立 immutable index,因此 query resolution 不需要线性扫描完整 imported corpus;database digest 只计算一次,并缓存为 provider identity。 + +如果两个不同 provenance group 具有相同 specificity,它们就是歧义 evidence。Provider 会拒绝,而不是选择最快 row 或静默混合 revision。第一版刻意不实现 interpolation;后续应把它做成具名 provider,并显式声明 validity domain 与 extrapolation distance。 + +## 导入 Simulator 表 + +`SimulatorPerformanceImporter` 支持 CSV、JSON/JSONL,以及安装 `performance-data` extra 后的 Parquet。`TabularImportSpec` 必须声明所有 mapping 与 latency unit: + +```python +from blueprinting.compiler.analysis import ( + CostSubject, + EstimateMethod, + LatencyUnit, + SimulatorPerformanceImporter, + TabularImportSpec, +) +from blueprinting.compiler.frozen import FrozenDict + +spec = TabularImportSpec( + name="noc-sim-r7", + subject=CostSubject.COMMUNICATION, + source="our-network-simulator", + source_revision="git:4e5c...", + method=EstimateMethod.SIMULATED, + latency_column="latency_us", + latency_unit=LatencyUnit.MICROSECONDS, + operation_column="collective", + hardware="lpu-candidate-17", + datatype_column="dtype", + selector_columns=FrozenDict({ + "message_bytes": "bytes", + "participants": "ranks", + }), + selector_types=FrozenDict({ + "message_bytes": "int", + "participants": "int", + }), + record_id_column="run_id", +) +database = SimulatorPerformanceImporter.from_file("collectives.csv", spec) +``` + +Importer 不会猜测 unit,也不会启发式转换 selector column。无效、空、非有限值或类型错误的输入都会使 ingestion 失败。 + +## 导入 Vidur Profile + +`VidurProfileImporter.from_csv()` 理解 Vidur attention 与 MLP profile schema,并把支持的 timing column 转换为 normalized record。Attention record 保留 phase、batch、context/cache semantic、TP degree、backend、block size 与模型 shape;compute record 保留 token count 和 model/parallel dimension。 + +这**不会**改变 oracle boundary: + +- `VidurProfileBaseline.lookup()` 仍是 post-hoc comparison oracle,不是 `CostProvider`; +- `VidurProfileImporter` 是显式 user action,会创建一份新的 Blueprinting-owned evidence database; +- 只有被用户明确安装到 resolver 中的 `PerformanceDatabaseProvider` 才能影响 costing。 + +这样既能防止 validation baseline 泄漏到 lowering,又允许经过独立审查的 profile data 成为 admissible evidence。 + +## 导入 NVIDIA AIConfigurator 数据 + +官方 [AIConfigurator 仓库](https://github.com/ai-dynamo/aiconfigurator)把 component performance 存成按 operation family 区分的异构 Parquet 表。`AIConfiguratorPerformanceImporter` 当前为以下四类表提供 strict adapter: + +| Upstream table | Normalized subject/operation | 必需 shape context | +|---|---|---| +| `gemm_perf` | operator / `gemm` | `m`、`n`、`k`、dtype | +| `context_attention_perf` | operator / `attention_core` prefill | batch、input length、local heads、head size、KV dtype | +| `generation_attention_perf` | operator / `attention_core` decode | batch、`isl + step`、local heads、head size、KV dtype | +| `custom_allreduce_perf` | communication / `all_reduce` | message bytes、GPU count、backend | + +AIConfigurator latency 从毫秒规范化为秒。Framework、framework version 与 kernel source 会成为必须匹配的 runtime/implementation selector;device 与 upstream operation identity 留在 provenance metadata。因此,runtime/kernel 未知的 hardware-only query 不会命中 AIConfigurator row。 + +```python +database = AIConfiguratorPerformanceImporter.from_file( + "gemm_perf.parquet", + hardware_name="h100-sxm", + source_revision="8fc57cf...", +) +``` + +AIConfigurator 的最终 `best_config_topn.csv` 与 Pareto output 描述的是 serving configuration,不是 atomic operator evidence,所以不会被导入成 task cost。未来可以增加 plan-level comparison adapter,但不能把 TTFT/TPOT 压扁成 kernel latency。 + +## 在 Inference Costing 中使用 Resolver + +Static inference 已可使用新 resolver,同时保留 legacy `InferenceCostProvider` seam: + +```python +resolver = CostResolver(( + PerformanceDatabaseProvider(database), + RooflineCostProvider(hardware), +)) + +context = CostQueryContext( + runtime="vllm", + runtime_revision="0.24.0", + implementations=FrozenDict({ + "attention_pre_projection": "torch.nn.functional.linear", + "all_reduce": "vLLM_custom_graph", + }), + operation_dimensions=FrozenDict({ + "all_reduce": FrozenDict({"backend": "vllm_graph"}), + }), +) + +estimate = estimate_inference_phase( + plan, + hardware, + cost_resolver=resolver, + cost_context=context, +) +``` + +Inference task query 直接从 canonical `PlanTask.workload` 推导;GEMM dimension 与 local attention-head dimension 来自 model 和 TP facts。Tensor-parallel collective 与 pipeline P2P 使用同一个 resolver。所有 task 都必须被已安装的 provider 覆盖——通常是 exact database 后接 roofline——unknown task 不会被静默变成零。 + +## 已实现边界与后续工作 + +当前已经实现: + +- normalized immutable query/estimate/support/resolution contract; +- 带 auditable attempt trace 的 deterministic ordered resolution; +- analytical roofline 与 collective fallback; +- immutable exact-selector database 与 repeated-sample aggregation; +- 通用 simulator/profiler table ingestion; +- 显式 Vidur 与四类 AIConfigurator ingestion; +- inference task/pipeline 接入与回归测试。 + +仍未实现: + +- calibrated interpolation/extrapolation provider 与 confidence policy; +- portable snapshot 之外的 append-only raw-sample/environment-manifest store; +- 通过 resolver equivalence test 迁移 training path; +- target binding 中 first-class KV-head/GQA 与 runtime legalization context; +- contention、overlap、queueing 与 plan-level discrete-event simulation; +- 作为 normalized planner objective 的 energy/power metric; +- observation ingestion 与 calibration revision。 + +这些边界非常重要:task latency resolution 是 evidence layer,不是已经完成的 hardware simulator 或 serving simulator。 diff --git a/docs/experiments/vidur-baseline.en.md b/docs/experiments/vidur-baseline.en.md index 22328bb..c449626 100644 --- a/docs/experiments/vidur-baseline.en.md +++ b/docs/experiments/vidur-baseline.en.md @@ -19,6 +19,8 @@ Transformer semantics + mapping + phase context `InferenceCostProvider.resolve()` is the extension point for an admissible Blueprinting performance database or hardware simulator. `InferenceBaseline.lookup()` is the external-oracle interface. `VidurProfileBaseline` implements only `lookup()`, so it cannot be supplied to `estimate_inference_phase()` by accident. +The separate `VidurProfileImporter` can explicitly convert user-supplied profile rows into a `PerformanceDatabase`. That is a different workflow and policy decision: the resulting database affects costing only when its provider is deliberately installed in a `CostResolver`. This experiment continues to use `VidurProfileBaseline` only, so its oracle isolation is unchanged. + The experiment report records `oracle_read_during_lowering = false`, `oracle_read_during_costing = false`, and `fit_against_case_outputs = false`. Per-case correction factors and Vidur durations are forbidden inputs to lowering and costing. ## Comparison contract diff --git a/docs/experiments/vidur-baseline.zh.md b/docs/experiments/vidur-baseline.zh.md index fdc71d7..bcf18fd 100644 --- a/docs/experiments/vidur-baseline.zh.md +++ b/docs/experiments/vidur-baseline.zh.md @@ -19,6 +19,8 @@ Transformer semantics + mapping + phase context `InferenceCostProvider.resolve()` 是 Blueprinting 自有性能数据库或硬件仿真器的扩展点;`InferenceBaseline.lookup()` 是外部 oracle 接口。`VidurProfileBaseline` 只实现 `lookup()`,因此不能被意外传入 `estimate_inference_phase()`。 +独立的 `VidurProfileImporter` 可以显式把用户提供的 profile row 转换成 `PerformanceDatabase`。这是另一条 workflow,也是一项明确 policy decision:只有用户刻意把该 database provider 安装进 `CostResolver`,它才会影响 costing。本实验仍只使用 `VidurProfileBaseline`,因此 oracle isolation 不变。 + Experiment report 会记录 `oracle_read_during_lowering = false`、`oracle_read_during_costing = false` 与 `fit_against_case_outputs = false`。Per-case correction factor 和 Vidur duration 都是 lowering/costing 的禁止输入。 ## 对比契约 diff --git a/docs/modeling/inference.en.md b/docs/modeling/inference.en.md index 772f7e5..27d3654 100644 --- a/docs/modeling/inference.en.md +++ b/docs/modeling/inference.en.md @@ -102,7 +102,7 @@ Comparison is over an explicit semantic intersection. The report contains matche There is no nearest-neighbor or hidden interpolation. An MHA workload requires equal query/KV head counts in both compute and attention records. Matching raw component-profile keys does not establish full decoder-topology equivalence: the current model spec does not yet encode norm placement, residual topology, or gated-MLP choice. The current production inference estimate remains entirely Blueprinting-owned; Vidur is an oracle for measuring where that estimate must improve. -Blueprinting does not vendor the full upstream profiling corpus. A minimal MIT-licensed Phi-2/A100 validation slice is retained for offline CI, with a pinned upstream commit, source blob IDs, an explicit projection rule, and local file digests. Larger experiments keep Vidur data external. A future ingestion command should add environment manifests, units, runtime/kernel versions, and raw-record IDs before profiles enter the general performance database. +Blueprinting does not vendor the full upstream profiling corpus. A minimal MIT-licensed Phi-2/A100 validation slice is retained for offline CI, with a pinned upstream commit, source blob IDs, an explicit projection rule, and local file digests. Larger experiments keep Vidur data external. The implemented `VidurProfileImporter` now normalizes units, creates raw-record IDs, and preserves the source revision/file digest when explicitly promoting profiles into a performance database. Full environment manifests and runtime/kernel identity remain required future evidence work where the upstream schema does not supply them. ## What the serving layer must add diff --git a/docs/modeling/inference.zh.md b/docs/modeling/inference.zh.md index 3919ed7..d651de8 100644 --- a/docs/modeling/inference.zh.md +++ b/docs/modeling/inference.zh.md @@ -102,7 +102,7 @@ Comparison 只发生在显式 semantic intersection 上。Report 给出 matched Adapter 不做 nearest-neighbor 或隐藏插值。MHA workload 还要求 compute 与 attention record 的 query/KV head 数相等。Raw component-profile key 匹配并不证明完整 decoder topology 等价:当前 model spec 还没有编码 norm placement、residual topology 与 gated-MLP choice。Production inference estimate 完全由 Blueprinting 自己产生;Vidur 只是衡量这套机制还应在哪里改进的 oracle。 -Blueprinting 不复制完整 upstream profiling corpus。仓库只保留一份 MIT-licensed Phi-2/A100 最小 validation slice,用于离线 CI,并固定 upstream commit、source blob ID、显式 projection rule 与本地文件 digest;更大规模实验继续让 Vidur 数据保持外部依赖。未来 ingestion command 应在 profile 进入通用性能数据库前补齐 environment manifest、unit、runtime/kernel version 与 raw-record ID。 +Blueprinting 不复制完整 upstream profiling corpus。仓库只保留一份 MIT-licensed Phi-2/A100 最小 validation slice,用于离线 CI,并固定 upstream commit、source blob ID、显式 projection rule 与本地文件 digest;更大规模实验继续让 Vidur 数据保持外部依赖。已实现的 `VidurProfileImporter` 会在用户显式把 profile 晋升为性能数据库 evidence 时规范化 unit、创建 raw-record ID,并保留 source revision/file digest。对于 upstream schema 没有提供的字段,完整 environment manifest 与 runtime/kernel identity 仍是后续必须补齐的 evidence 工作。 ## Serving 层还必须增加什么 diff --git a/docs/project/status.en.md b/docs/project/status.en.md index 614fbb1..7de0dbc 100644 --- a/docs/project/status.en.md +++ b/docs/project/status.en.md @@ -22,6 +22,7 @@ This page separates Blueprinting's hardware-exploration product goals from the e | Static Transformer inference phase planning | **Implemented slice** | independently verified prefill/decode plans, KV capacity, and decoder-block phase composition | | Target-neutral workload/mapping plan | **Implemented slice** | Transformer path reaches `PortablePlanIR` | | Versioned compute/memory/network efficiency profile | **Implemented adapter** | `HardwareProfile` and two analytical estimate modes | +| Normalized task-cost resolution and performance-data ingestion | **Implemented slice** | immutable query/result/store, ordered resolver, roofline fallback, generic simulator tables, Vidur profiles, and four AIConfigurator table families | | Vidur raw component-profile alignment | **Implemented experiment** | exact-key CSV lookup after independent lowering/costing, with component coverage and non-cancelling error attribution | | Calculon/SeqSel workload and cost calibration | **Implemented experiment** | eight-case reproducible report and tests | | First-class hierarchical `ArchitectureBlueprint` | **Planned** | documented component model; no production schema/API | @@ -31,7 +32,7 @@ This page separates Blueprinting's hardware-exploration product goals from the e | Architecture-bound placement, schedule, and memory plan | **Experimental Contract / Planned** | `ConcretePlanIR` has only a generic queue-oriented schema and structural verifier; producer, route/occupancy semantics, and typed target extensions do not exist | | Discrete-event compute/memory/resource simulation | **Planned** | current result is analytical composition, not event simulation | | Timeline analysis/replay bundle | **Planned** | `TimingProjection`, `SimulationTraceIR`, and `TimelineBundle` are design contracts only | -| General network/hardware simulator adapters | **Implemented slice / Planned** | inference cost/baseline protocols exist; general resolver, validity/uncertainty model, and simulator adapters do not | +| General network/hardware simulator adapters | **Implemented slice / Planned** | explicit tabular ingestion and a general resolver exist; simulator execution, calibrated interpolation, contention validity, and environment manifests remain planned | | Bottleneck, utilization, sensitivity, and what-if reports | **Planned** | no general architecture report product | | Energy, area, power, thermal, and cost models | **Planned** | dimensions are specified but no providers exist | | Multi-objective Pareto architecture search | **Planned** | no candidate frontier API | @@ -61,7 +62,7 @@ TransformerModelSpec + inference mapping + request cohort -> phase-neutral inference ModelIR -> independently bound prefill and decode DistributedTaskIR -> phase-local PortablePlanIR with KV state/capacity - -> Blueprinting HardwareProfile/cost-provider estimate + -> CostResolver(exact imported evidence -> explicit roofline fallback) -> optional post-hoc Vidur baseline comparison -> static prefill / decode-step model time and analytical memory report ``` @@ -84,7 +85,7 @@ It cannot yet claim serving-system SLO accuracy: arrivals, queueing, continuous | Transactional analyses/transformations, checkpoints, observers | **Implemented** | `passes/base.py` | | Transformer semantic frontend and workload algebra | **Implemented slice** | `models/transformer.py`, `analysis/transformer_workload.py` | | Distributed and portable mapping derivations | **Implemented slice** | `lowering/transformer.py` | -| Current evidence adapter | **Implemented slice** | `analysis/cost_model.py` | +| Cost protocol, resolver, roofline, database, and external importers | **Implemented slice** | `analysis/cost/`, `analysis/vidur.py`; exact task latency only, not plan simulation | | Static inference frontend, lowering, cost, and request composition | **Implemented slice** | `models/transformer_inference.py`, `analysis/{transformer_inference,inference_cost}.py`, `lowering/transformer_inference.py`, `application/inference.py` | | Vidur raw component-profile alignment | **Implemented experiment** | `analysis/vidur.py` + `experiments/vidur.py`; a minimal licensed CI slice is pinned locally and the full upstream corpus remains external | | Calculon experiment | **Implemented experiment** | `experiments/calculon.py` | @@ -94,7 +95,7 @@ These typed representations, verifiers, derivation transactions, and analyses ar ## Verification baseline -The current test suite covers binding consistency, canonical serialization, verifier rejection, pass transaction rollback, checkpoint observers, workload conservation, Calculon calibration, prefill/decode scaling, KV capacity, static request composition, and baseline-only Vidur comparison. A dedicated CI job runs the eight-case Calculon/SeqSel and three-case pinned Vidur gates on every main-branch pull request and push. It freezes provenance, semantic policy, coverage, comparable-subtotal drift budgets, non-cancelling component errors, aggregate results, and IR digests; it cannot silently regenerate goldens. The Vidur gate is drift detection, not an accuracy certification. Documentation checks enforce complete bilingual page pairs and strict site builds. +The current test suite covers binding consistency, canonical serialization, verifier rejection, pass transaction rollback, checkpoint observers, workload conservation, Calculon calibration, prefill/decode scaling, KV capacity, static request composition, baseline-only Vidur comparison, roofline components, exact/ambiguous database resolution, simulator unit normalization, AIConfigurator CSV/Parquet schemas, explicit Vidur ingestion, and inference resolver fallback. A dedicated CI job runs the eight-case Calculon/SeqSel and three-case pinned Vidur gates on every main-branch pull request and push. It freezes provenance, semantic policy, coverage, comparable-subtotal drift budgets, non-cancelling component errors, aggregate results, and IR digests; it cannot silently regenerate goldens. The Vidur gate is drift detection, not an accuracy certification. Documentation checks enforce complete bilingual page pairs and strict site builds. Status promotion requires an end-to-end product test. For example, introducing `ArchitectureBlueprint` as a dataclass is Contract Only; constructing two different candidates, mapping the same workload, producing comparable results, and preserving provenance is the minimum product-level evidence. diff --git a/docs/project/status.zh.md b/docs/project/status.zh.md index d386d07..be5284f 100644 --- a/docs/project/status.zh.md +++ b/docs/project/status.zh.md @@ -22,6 +22,7 @@ | Static Transformer inference phase planning | **Implemented slice** | 独立验证的 prefill/decode plan、KV 容量以及 decoder-block phase composition | | Target-neutral workload/mapping plan | **Implemented slice** | Transformer path 到达 `PortablePlanIR` | | 版本化 compute/memory/network efficiency profile | **Implemented adapter** | `HardwareProfile` 与两种 analytical estimate mode | +| Normalized task-cost resolution 与性能数据导入 | **Implemented slice** | immutable query/result/store、ordered resolver、roofline fallback、通用 simulator 表、Vidur profile 与四类 AIConfigurator 表 | | Vidur raw component-profile 对齐 | **Implemented experiment** | 独立 lowering/costing 后进行 exact-key CSV lookup,并报告 component coverage 与不可抵消的误差归因 | | Calculon/SeqSel workload 与 cost calibration | **Implemented experiment** | 8 case 可复现 report 与 test | | First-class hierarchical `ArchitectureBlueprint` | **Planned** | 已定义 component model;无 production schema/API | @@ -31,7 +32,7 @@ | Architecture-bound placement、schedule 与 memory plan | **Experimental Contract / Planned** | `ConcretePlanIR` 只有通用 queue-oriented schema 与 structural verifier;producer、route/occupancy semantic 和 typed target extension 尚无 | | Discrete-event compute/memory/resource simulation | **Planned** | 当前结果是 analytical composition,不是 event simulation | | Timeline analysis/replay bundle | **Planned** | `TimingProjection`、`SimulationTraceIR`、`TimelineBundle` 只有 design contract | -| 通用 network/hardware simulator adapter | **Implemented slice / Planned** | inference cost/baseline protocol 已存在;通用 resolver、validity/uncertainty model 和 simulator adapter 尚无 | +| 通用 network/hardware simulator adapter | **Implemented slice / Planned** | 显式 tabular ingestion 与通用 resolver 已存在;simulator execution、calibrated interpolation、contention validity 与 environment manifest 仍未实现 | | Bottleneck、utilization、sensitivity 与 what-if report | **Planned** | 无通用 architecture report product | | Energy、area、power、thermal 与 cost model | **Planned** | 已定义维度,但无 provider | | Multi-objective Pareto architecture search | **Planned** | 无 candidate frontier API | @@ -61,7 +62,7 @@ TransformerModelSpec + inference mapping + request cohort -> phase-neutral inference ModelIR -> 分别绑定的 prefill/decode DistributedTaskIR -> 携带 KV state/capacity 的 phase-local PortablePlanIR - -> Blueprinting HardwareProfile/cost-provider estimate + -> CostResolver(exact imported evidence -> 显式 roofline fallback) -> optional post-hoc Vidur baseline comparison -> 静态 prefill / decode-step model time 与解析 memory report ``` @@ -84,7 +85,7 @@ TransformerModelSpec + inference mapping + request cohort | Transactional analysis/transformation、checkpoint、observer | **Implemented** | `passes/base.py` | | Transformer semantic frontend 与 workload algebra | **Implemented slice** | `models/transformer.py`、`analysis/transformer_workload.py` | | Distributed/portable mapping derivation | **Implemented slice** | `lowering/transformer.py` | -| 当前 evidence adapter | **Implemented slice** | `analysis/cost_model.py` | +| Cost protocol、resolver、roofline、database 与外部 importer | **Implemented slice** | `analysis/cost/`、`analysis/vidur.py`;仅覆盖 exact task latency,不是 plan simulation | | Static inference frontend、lowering、cost 与 request composition | **Implemented slice** | `models/transformer_inference.py`、`analysis/{transformer_inference,inference_cost}.py`、`lowering/transformer_inference.py`、`application/inference.py` | | Vidur raw component-profile 对齐 | **Implemented experiment** | `analysis/vidur.py` + `experiments/vidur.py`;最小带许可证 CI slice 固定在本地,完整 upstream corpus 仍保持外部依赖 | | Calculon experiment | **Implemented experiment** | `experiments/calculon.py` | @@ -94,7 +95,7 @@ TransformerModelSpec + inference mapping + request cohort ## 验证基线 -当前 test suite 覆盖 binding consistency、canonical serialization、verifier rejection、pass transaction rollback、checkpoint observer、workload conservation、Calculon calibration、prefill/decode scaling、KV 容量、static request composition 与 baseline-only Vidur comparison。独立 CI job 会在每次面向 main 的 PR 和 push 上执行 8-case Calculon/SeqSel 与 3-case 固定 Vidur gate,同时冻结 provenance、semantic policy、coverage、comparable-subtotal drift budget、不可抵消的 component error、aggregate result 与 IR digest,且不能静默重生成 golden。Vidur gate 只用于 drift detection,不是 accuracy certification。文档检查强制完整双语 page pair 与 strict site build。 +当前 test suite 覆盖 binding consistency、canonical serialization、verifier rejection、pass transaction rollback、checkpoint observer、workload conservation、Calculon calibration、prefill/decode scaling、KV 容量、static request composition、baseline-only Vidur comparison、roofline component、exact/ambiguous database resolution、simulator unit normalization、AIConfigurator CSV/Parquet schema、显式 Vidur ingestion 与 inference resolver fallback。独立 CI job 会在每次面向 main 的 PR 和 push 上执行 8-case Calculon/SeqSel 与 3-case 固定 Vidur gate,同时冻结 provenance、semantic policy、coverage、comparable-subtotal drift budget、不可抵消的 component error、aggregate result 与 IR digest,且不能静默重生成 golden。Vidur gate 只用于 drift detection,不是 accuracy certification。文档检查强制完整双语 page pair 与 strict site build。 能力升级需要端到端 product test。例如只增加 `ArchitectureBlueprint` dataclass 仍是 Contract Only;至少要构造两个不同 candidate、映射同一 workload、产生可比较 result 并保持 provenance,才能形成产品级证据。 diff --git a/mkdocs.yml b/mkdocs.yml index 51bec1a..dfcae82 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,6 +54,7 @@ nav: - Workload and Mapping Model: modeling/workload.md - Inference Planning and Serving Simulation: modeling/inference.md - Performance Evidence and Cost Models: design/performance/index.md + - Cost Providers and Data Imports: design/performance/providers.md - Performance Database: design/performance/database.md - Simulation and Calibration: design/performance/simulation.md - Experiments: @@ -111,6 +112,7 @@ plugins: Workload and Mapping Model: 工作负载与映射模型 Inference Planning and Serving Simulation: 推理规划与 Serving 仿真 Performance Evidence and Cost Models: 性能证据与 Cost Model + Cost Providers and Data Imports: Cost Provider 与数据导入 Performance Database: 性能数据库 Simulation and Calibration: 仿真与校准 Experiments: 实验 diff --git a/pyproject.toml b/pyproject.toml index 155f6e3..c986b4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,9 @@ dependencies = [ ] [project.optional-dependencies] +performance-data = [ + "pyarrow>=12.0.0", +] legacy-ui = [ "streamlit>=1.53,<2", "streamlit-extras>=0.6,<1", @@ -81,7 +84,7 @@ docs = [ "pymdown-extensions>=10.21.3,<11.0.0", ] all = [ - "blueprinting[full,dev,docs]", + "blueprinting[full,dev,docs,performance-data]", ] [project.urls] diff --git a/src/blueprinting/compiler/analysis/__init__.py b/src/blueprinting/compiler/analysis/__init__.py index 085acf0..ee7f5bd 100644 --- a/src/blueprinting/compiler/analysis/__init__.py +++ b/src/blueprinting/compiler/analysis/__init__.py @@ -1,5 +1,28 @@ """Exact workload analyses and evidence-backed cost models.""" +from .cost import ( + AIConfiguratorPerformanceImporter, + AIConfiguratorTable, + CostEstimate, + CostNotAvailableError, + CostProvider, + CostQuery, + CostQueryContext, + CostResolution, + CostResolver, + CostSubject, + EstimateMatch, + EstimateMethod, + EvidenceProvenance, + LatencyUnit, + PerformanceDatabase, + PerformanceDatabaseProvider, + PerformanceRecord, + RooflineCostProvider, + SimulatorPerformanceImporter, + TabularImportSpec, + TabularPerformanceImporter, +) from .cost_model import ( BlockEstimate, CalibrationMode, @@ -13,6 +36,7 @@ InferencePhaseEstimate, InferencePhaseMemory, InferenceTaskEstimate, + cost_query_for_inference_task, estimate_inference_phase, inference_evidence_query_for, ) @@ -35,13 +59,26 @@ TrainingPhase, compile_transformer_block, ) -from .vidur import VidurProfileBaseline +from .vidur import VidurProfileBaseline, VidurProfileImporter __all__ = [ "BlockEstimate", "BlockMemoryFacts", "CalibrationMode", + "AIConfiguratorPerformanceImporter", + "AIConfiguratorTable", + "CostEstimate", + "CostNotAvailableError", + "CostProvider", + "CostQuery", + "CostQueryContext", + "CostResolution", + "CostResolver", + "CostSubject", "EngineKind", + "EstimateMatch", + "EstimateMethod", + "EvidenceProvenance", "HardwareProfile", "InferenceBlockMemoryFacts", "InferenceBaseline", @@ -54,14 +91,24 @@ "InferenceTaskEstimate", "IterationEstimate", "IterationMemory", + "LatencyUnit", "PhaseWork", + "PerformanceDatabase", + "PerformanceDatabaseProvider", + "PerformanceRecord", "PrimitiveInvocation", + "RooflineCostProvider", + "SimulatorPerformanceImporter", + "TabularImportSpec", + "TabularPerformanceImporter", "TrainingPhase", "compile_transformer_block", "compile_transformer_inference_block", + "cost_query_for_inference_task", "estimate_block", "estimate_inference_phase", "estimate_iteration", "inference_evidence_query_for", "VidurProfileBaseline", + "VidurProfileImporter", ] diff --git a/src/blueprinting/compiler/analysis/cost/__init__.py b/src/blueprinting/compiler/analysis/cost/__init__.py new file mode 100644 index 0000000..deb7e90 --- /dev/null +++ b/src/blueprinting/compiler/analysis/cost/__init__.py @@ -0,0 +1,59 @@ +"""Unified cost-query, provider, evidence-store, and import APIs.""" + +from .aiconfigurator import AIConfiguratorPerformanceImporter, AIConfiguratorTable +from .database import EvidenceProvenance, PerformanceDatabase, PerformanceDatabaseProvider, PerformanceRecord +from .importers import ( + LatencyUnit, + SimulatorPerformanceImporter, + TabularImportSpec, + TabularPerformanceImporter, +) +from .protocol import ( + CostEstimate, + CostModelError, + CostNotAvailableError, + CostProvider, + CostQuery, + CostQueryContext, + CostResolution, + CostResolver, + CostSubject, + CostSupport, + EstimateMatch, + EstimateMethod, + EstimateUncertainty, + InvalidCostEvidenceError, + ProviderAttempt, + SupportStatus, +) +from .roofline import RooflineCostProvider + +__all__ = [ + "AIConfiguratorPerformanceImporter", + "AIConfiguratorTable", + "CostEstimate", + "CostModelError", + "CostNotAvailableError", + "CostProvider", + "CostQuery", + "CostQueryContext", + "CostResolution", + "CostResolver", + "CostSubject", + "CostSupport", + "EstimateMatch", + "EstimateMethod", + "EstimateUncertainty", + "EvidenceProvenance", + "InvalidCostEvidenceError", + "LatencyUnit", + "PerformanceDatabase", + "PerformanceDatabaseProvider", + "PerformanceRecord", + "ProviderAttempt", + "RooflineCostProvider", + "SimulatorPerformanceImporter", + "SupportStatus", + "TabularImportSpec", + "TabularPerformanceImporter", +] diff --git a/src/blueprinting/compiler/analysis/cost/aiconfigurator.py b/src/blueprinting/compiler/analysis/cost/aiconfigurator.py new file mode 100644 index 0000000..804e19e --- /dev/null +++ b/src/blueprinting/compiler/analysis/cost/aiconfigurator.py @@ -0,0 +1,247 @@ +"""Adapters for NVIDIA AIConfigurator operator performance tables. + +AIConfigurator uses one schema per operation family. This adapter supports +the stable GEMM, context/decode attention, and custom all-reduce schemas +explicitly instead of guessing columns from an arbitrary Parquet file. +""" + +from __future__ import annotations + +import hashlib +import math +from enum import Enum +from pathlib import Path +from typing import Any + +from ...codec import content_digest +from ...frozen import FrozenDict +from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord +from .importers import read_tabular_rows +from .protocol import CostSubject, EstimateMethod + + +class AIConfiguratorTable(Enum): + GEMM = "gemm" + CONTEXT_ATTENTION = "context_attention" + GENERATION_ATTENTION = "generation_attention" + CUSTOM_ALL_REDUCE = "custom_allreduce" + + +_FILE_TABLES = { + "gemm_perf.parquet": AIConfiguratorTable.GEMM, + "gemm_perf.csv": AIConfiguratorTable.GEMM, + "context_attention_perf.parquet": AIConfiguratorTable.CONTEXT_ATTENTION, + "context_attention_perf.csv": AIConfiguratorTable.CONTEXT_ATTENTION, + "generation_attention_perf.parquet": AIConfiguratorTable.GENERATION_ATTENTION, + "generation_attention_perf.csv": AIConfiguratorTable.GENERATION_ATTENTION, + "custom_allreduce_perf.parquet": AIConfiguratorTable.CUSTOM_ALL_REDUCE, + "custom_allreduce_perf.csv": AIConfiguratorTable.CUSTOM_ALL_REDUCE, +} + +_DATATYPE_ALIASES = { + "half": "float16", + "fp16": "float16", + "float16": "float16", + "bf16": "bfloat16", + "bfloat16": "bfloat16", +} + + +def _required(row: dict[str, Any], column: str, row_number: int) -> Any: + if column not in row: + raise ValueError(f"AIConfigurator row {row_number} is missing column {column!r}") + value = row[column] + if value is None or (isinstance(value, str) and not value.strip()): + raise ValueError(f"AIConfigurator row {row_number} has an empty {column!r}") + if isinstance(value, float) and not math.isfinite(value): + raise ValueError(f"AIConfigurator row {row_number} has non-finite {column!r}") + return value + + +def _integer(row: dict[str, Any], column: str, row_number: int) -> int: + value = _required(row, column, row_number) + try: + numeric = float(value) + except (TypeError, ValueError) as error: + raise ValueError(f"AIConfigurator row {row_number} has non-numeric {column!r}") from error + if not math.isfinite(numeric) or not numeric.is_integer(): + raise ValueError(f"AIConfigurator row {row_number} has non-integer {column!r}") + return int(numeric) + + +def _text(row: dict[str, Any], column: str, row_number: int) -> str: + return str(_required(row, column, row_number)).strip() + + +def _datatype(row: dict[str, Any], column: str, row_number: int) -> str: + value = _text(row, column, row_number).lower() + return _DATATYPE_ALIASES.get(value, value) + + +def _latency_seconds(row: dict[str, Any], row_number: int) -> float: + value = _required(row, "latency", row_number) + try: + latency_ms = float(value) + except (TypeError, ValueError) as error: + raise ValueError(f"AIConfigurator row {row_number} has invalid latency") from error + if not math.isfinite(latency_ms) or latency_ms < 0: + raise ValueError(f"AIConfigurator row {row_number} has invalid latency") + return latency_ms * 1e-3 + + +def _runtime_selector(row: dict[str, Any], row_number: int) -> dict[str, Any]: + return { + "runtime": _text(row, "framework", row_number).lower(), + "runtime_revision": _text(row, "version", row_number), + "implementation": _text(row, "kernel_source", row_number), + } + + +class AIConfiguratorPerformanceImporter: + IMPORTER_REVISION = "blueprinting-aiconfigurator-perf-v1" + + @classmethod + def from_file( + cls, + path: str | Path, + *, + hardware_name: str, + source_revision: str, + table: AIConfiguratorTable | None = None, + database_name: str | None = None, + ) -> PerformanceDatabase: + source = Path(path) + if not isinstance(hardware_name, str) or not hardware_name: + raise ValueError("hardware_name must be a non-empty string") + if not isinstance(source_revision, str) or not source_revision: + raise ValueError("source_revision must be a non-empty string") + if table is None: + table = _FILE_TABLES.get(source.name) + if table is None: + raise ValueError("cannot infer AIConfigurator table family from file name; pass table explicitly") + if not isinstance(table, AIConfiguratorTable): + raise TypeError("table must be AIConfiguratorTable") + payload = source.read_bytes() + data_digest = hashlib.sha256(payload).hexdigest() + rows = read_tabular_rows(source) + provenance = EvidenceProvenance( + source="nvidia-aiconfigurator", + source_revision=source_revision, + importer=cls.IMPORTER_REVISION, + data_digest=data_digest, + method=EstimateMethod.MEASURED, + metadata=FrozenDict({"table": table.value, "file_name": source.name}), + ) + records = tuple( + cls._record( + row, + row_number, + table=table, + hardware_name=hardware_name, + provenance=provenance, + data_digest=data_digest, + ) + for row_number, row in enumerate(rows, start=1) + ) + return PerformanceDatabase( + name=database_name or f"aiconfigurator-{hardware_name}-{table.value}", + records=records, + metadata=FrozenDict( + { + "source": "nvidia-aiconfigurator", + "source_revision": source_revision, + "data_digest": data_digest, + "importer": cls.IMPORTER_REVISION, + "table": table.value, + } + ), + ) + + @classmethod + def _record( + cls, + row: dict[str, Any], + row_number: int, + *, + table: AIConfiguratorTable, + hardware_name: str, + provenance: EvidenceProvenance, + data_digest: str, + ) -> PerformanceRecord: + selector = _runtime_selector(row, row_number) + metadata: dict[str, Any] = { + "device": _text(row, "device", row_number), + "upstream_operation": _text(row, "op_name", row_number), + "source_table": table.value, + } + if table is AIConfiguratorTable.GEMM: + operation = "gemm" + subject = CostSubject.OPERATOR + datatype = _datatype(row, "gemm_dtype", row_number) + selector.update( + { + "m": _integer(row, "m", row_number), + "n": _integer(row, "n", row_number), + "k": _integer(row, "k", row_number), + } + ) + elif table in {AIConfiguratorTable.CONTEXT_ATTENTION, AIConfiguratorTable.GENERATION_ATTENTION}: + operation = "attention_core" + subject = CostSubject.OPERATOR + datatype = _datatype(row, "attn_dtype", row_number) + is_context = table is AIConfiguratorTable.CONTEXT_ATTENTION + input_length = _integer(row, "isl", row_number) + step = _integer(row, "step", row_number) + selector.update( + { + "semantic_operation": "attention_core", + "phase": "prefill" if is_context else "decode", + "batch_size": _integer(row, "batch_size", row_number), + "query_tokens": input_length if is_context else 1, + "context_tokens": input_length if is_context else input_length + step, + "local_attention_heads": _integer(row, "num_heads", row_number), + "local_kv_heads": _integer(row, "num_key_value_heads", row_number), + "head_size": _integer(row, "head_dim", row_number), + "beam_width": _integer(row, "beam_width", row_number), + "window_size": _integer(row, "window_size", row_number), + "kv_cache_datatype": _datatype(row, "kv_cache_dtype", row_number), + } + ) + else: + operation = "all_reduce" + subject = CostSubject.COMMUNICATION + datatype = _datatype(row, "allreduce_dtype", row_number) + selector.update( + { + "participants": _integer(row, "num_gpus", row_number), + "message_bytes": _integer(row, "message_size", row_number), + "backend": _text(row, "backend", row_number), + } + ) + for optional in ("power", "power_limit"): + if optional in row and row[optional] is not None: + value = float(row[optional]) + if math.isfinite(value): + metadata[optional] = value + record_id = content_digest( + FrozenDict( + { + "importer": cls.IMPORTER_REVISION, + "data_digest": data_digest, + "table": table.value, + "row": row_number, + } + ), + "performance-record-id", + ) + return PerformanceRecord( + record_id=record_id, + subject=subject, + operation=operation, + hardware=hardware_name, + datatype=datatype, + seconds=_latency_seconds(row, row_number), + selector=FrozenDict(selector), + provenance=provenance, + metadata=FrozenDict(metadata), + ) diff --git a/src/blueprinting/compiler/analysis/cost/database.py b/src/blueprinting/compiler/analysis/cost/database.py new file mode 100644 index 0000000..30ce9bd --- /dev/null +++ b/src/blueprinting/compiler/analysis/cost/database.py @@ -0,0 +1,249 @@ +"""Immutable performance records and an exact-selector database provider.""" + +from __future__ import annotations + +import math +import statistics +from collections import defaultdict +from dataclasses import dataclass, field +from functools import cached_property + +from ...codec import canonical_dumps, canonical_loads, content_digest, record_type +from ...frozen import FrozenDict +from .protocol import ( + CostEstimate, + CostProvider, + CostQuery, + CostSubject, + CostSupport, + EstimateMatch, + EstimateMethod, + EstimateUncertainty, + InvalidCostEvidenceError, +) + + +@record_type("compiler.analysis.cost.provenance.v1") +@dataclass(frozen=True) +class EvidenceProvenance: + source: str + source_revision: str + importer: str + data_digest: str + method: EstimateMethod + metadata: FrozenDict = field(default_factory=FrozenDict) + + def __post_init__(self) -> None: + for name in ("source", "source_revision", "importer", "data_digest"): + value = getattr(self, name) + if not isinstance(value, str) or not value: + raise ValueError(f"{name} must be a non-empty string") + if not isinstance(self.method, EstimateMethod): + raise TypeError("method must be EstimateMethod") + object.__setattr__(self, "metadata", FrozenDict(self.metadata)) + + +@record_type("compiler.analysis.cost.performance_record.v1") +@dataclass(frozen=True) +class PerformanceRecord: + record_id: str + subject: CostSubject + operation: str + hardware: str + datatype: str + seconds: float + selector: FrozenDict + provenance: EvidenceProvenance + metadata: FrozenDict = field(default_factory=FrozenDict) + + def __post_init__(self) -> None: + for name in ("record_id", "operation", "hardware", "datatype"): + value = getattr(self, name) + if not isinstance(value, str) or not value: + raise ValueError(f"{name} must be a non-empty string") + if not isinstance(self.subject, CostSubject): + raise TypeError("subject must be CostSubject") + if ( + isinstance(self.seconds, bool) + or not isinstance(self.seconds, (int, float)) + or not math.isfinite(self.seconds) + or self.seconds < 0 + ): + raise ValueError("record seconds must be finite and non-negative") + if not isinstance(self.provenance, EvidenceProvenance): + raise TypeError("provenance must be EvidenceProvenance") + object.__setattr__(self, "selector", FrozenDict(self.selector)) + object.__setattr__(self, "metadata", FrozenDict(self.metadata)) + duplicate_identity = {"subject", "operation", "hardware", "datatype"}.intersection(self.selector) + if duplicate_identity: + raise ValueError(f"record selector duplicates core identity: {', '.join(sorted(duplicate_identity))}") + + +@record_type("compiler.analysis.cost.performance_database.v1") +@dataclass(frozen=True) +class PerformanceDatabase: + name: str + records: tuple[PerformanceRecord, ...] + metadata: FrozenDict = field(default_factory=FrozenDict) + + def __post_init__(self) -> None: + if not isinstance(self.name, str) or not self.name: + raise ValueError("database name must be a non-empty string") + object.__setattr__(self, "records", tuple(self.records)) + object.__setattr__(self, "metadata", FrozenDict(self.metadata)) + if not self.records or any(not isinstance(record, PerformanceRecord) for record in self.records): + raise ValueError("performance database requires typed records") + record_ids = tuple(record.record_id for record in self.records) + if len(set(record_ids)) != len(record_ids): + raise ValueError("performance database record IDs must be unique") + + @cached_property + def revision(self) -> str: + return content_digest(self, "performance-database") + + def to_json(self) -> str: + return canonical_dumps(self) + + @classmethod + def from_json(cls, payload: str) -> PerformanceDatabase: + result = canonical_loads(payload) + if not isinstance(result, cls): + raise TypeError("payload does not contain a PerformanceDatabase") + return result + + @classmethod + def merge(cls, name: str, databases: tuple[PerformanceDatabase, ...]) -> PerformanceDatabase: + databases = tuple(databases) + if not databases: + raise ValueError("at least one performance database is required") + return cls( + name=name, + records=tuple(record for database in databases for record in database.records), + metadata=FrozenDict( + { + "merged_revisions": tuple(database.revision for database in databases), + "merged_names": tuple(database.name for database in databases), + } + ), + ) + + +class PerformanceDatabaseProvider(CostProvider): + """Select the most-specific matching selector without interpolation.""" + + def __init__(self, database: PerformanceDatabase) -> None: + if not isinstance(database, PerformanceDatabase): + raise TypeError("database must be PerformanceDatabase") + self._database = database + self._name = f"performance-db:{database.name}" + self._revision = database.revision + index = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) + for record in database.records: + core = (record.subject, record.operation, record.hardware, record.datatype) + selector_keys = tuple(record.selector) + typed_values = tuple((type(record.selector[key]), record.selector[key]) for key in selector_keys) + index[core][selector_keys][typed_values].append(record) + self._index = { + core: { + selector_keys: {values: tuple(records) for values, records in value_index.items()} + for selector_keys, value_index in selector_index.items() + } + for core, selector_index in index.items() + } + + @property + def database(self) -> PerformanceDatabase: + return self._database + + @property + def name(self) -> str: + return self._name + + @property + def revision(self) -> str: + return self._revision + + def _selected_records(self, query: CostQuery) -> tuple[PerformanceRecord, ...] | None: + core = (query.subject, query.operation, query.hardware, query.datatype) + selector_index = self._index.get(core) + if selector_index is None: + return None + context = query.match_context + candidate_groups = [] + for selector_keys, value_index in selector_index.items(): + if any(key not in context for key in selector_keys): + continue + typed_values = tuple((type(context[key]), context[key]) for key in selector_keys) + records = value_index.get(typed_values) + if records is not None: + candidate_groups.append(records) + candidates = tuple(record for records in candidate_groups for record in records) + if not candidates: + return None + specificity = max(len(record.selector) for record in candidates) + candidates = tuple(record for record in candidates if len(record.selector) == specificity) + groups: dict[tuple[object, ...], list[PerformanceRecord]] = defaultdict(list) + for record in candidates: + provenance = record.provenance + key = ( + record.selector, + provenance.source, + provenance.source_revision, + provenance.importer, + provenance.method, + provenance.data_digest, + ) + groups[key].append(record) + if len(groups) > 1: + descriptions = sorted( + f"{items[0].provenance.source}@{items[0].provenance.source_revision}:{dict(items[0].selector)}" + for items in groups.values() + ) + raise InvalidCostEvidenceError( + "multiple equally specific evidence groups match the query: " + "; ".join(descriptions) + ) + return tuple(next(iter(groups.values()))) + + def supports(self, query: CostQuery) -> CostSupport: + try: + records = self._selected_records(query) + except InvalidCostEvidenceError as error: + return CostSupport.invalid(str(error)) + if records is None: + return CostSupport.unavailable("no exact selector is covered by this database") + return CostSupport.available(f"{len(records)} raw sample(s), selector specificity {len(records[0].selector)}") + + def estimate(self, query: CostQuery) -> CostEstimate: + records = self._selected_records(query) + if records is None: + raise InvalidCostEvidenceError("database provider was asked to estimate an unsupported query") + values = tuple(float(record.seconds) for record in records) + seconds = statistics.median(values) + deviation = statistics.pstdev(values) if len(values) > 1 else 0.0 + provenance = records[0].provenance + selector = records[0].selector + uncovered = tuple(sorted(set(query.match_context) - set(selector))) + assumptions = () + if uncovered: + assumptions = ( + "evidence matches an exact declared selector; dimensions not declared by the source are not " + "claimed as controlled", + ) + return CostEstimate( + seconds=seconds, + provider=self.name, + provider_revision=self.revision, + source_revision=provenance.source_revision, + method=provenance.method, + match=EstimateMatch.EXACT_SELECTOR, + uncertainty=EstimateUncertainty( + sample_count=len(values), + standard_deviation_seconds=deviation, + lower_bound_seconds=min(values), + upper_bound_seconds=max(values), + ), + raw_record_ids=tuple(sorted(record.record_id for record in records)), + validity_domain=selector, + components=FrozenDict({"median_seconds": seconds}), + assumptions=assumptions, + ) diff --git a/src/blueprinting/compiler/analysis/cost/importers.py b/src/blueprinting/compiler/analysis/cost/importers.py new file mode 100644 index 0000000..f3eca27 --- /dev/null +++ b/src/blueprinting/compiler/analysis/cost/importers.py @@ -0,0 +1,337 @@ +"""Strict tabular ingestion for simulator and profiler performance evidence.""" + +from __future__ import annotations + +import csv +import hashlib +import json +import math +from dataclasses import dataclass, field +from enum import Enum +from pathlib import Path +from typing import Any + +from ...codec import content_digest +from ...frozen import FrozenDict +from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord +from .protocol import CostSubject, EstimateMethod + + +class LatencyUnit(Enum): + SECONDS = "s" + MILLISECONDS = "ms" + MICROSECONDS = "us" + NANOSECONDS = "ns" + + @property + def seconds_multiplier(self) -> float: + return { + LatencyUnit.SECONDS: 1.0, + LatencyUnit.MILLISECONDS: 1e-3, + LatencyUnit.MICROSECONDS: 1e-6, + LatencyUnit.NANOSECONDS: 1e-9, + }[self] + + +_SCALAR_TYPES = frozenset({"string", "int", "float", "bool"}) + + +@dataclass(frozen=True) +class TabularImportSpec: + """Declarative mapping from one external table into normalized records.""" + + name: str + subject: CostSubject + source: str + source_revision: str + method: EstimateMethod + latency_column: str + latency_unit: LatencyUnit + operation: str | None = None + operation_column: str | None = None + hardware: str | None = None + hardware_column: str | None = None + datatype: str | None = None + datatype_column: str | None = None + selector_columns: FrozenDict = field(default_factory=FrozenDict) + selector_types: FrozenDict = field(default_factory=FrozenDict) + constant_selectors: FrozenDict = field(default_factory=FrozenDict) + dimensions_json_column: str | None = None + metadata_columns: tuple[str, ...] = () + record_id_column: str | None = None + operation_aliases: FrozenDict = field(default_factory=FrozenDict) + datatype_aliases: FrozenDict = field(default_factory=FrozenDict) + + def __post_init__(self) -> None: + for name in ("name", "source", "source_revision", "latency_column"): + value = getattr(self, name) + if not isinstance(value, str) or not value: + raise ValueError(f"{name} must be a non-empty string") + if not isinstance(self.subject, CostSubject): + raise TypeError("subject must be CostSubject") + if not isinstance(self.method, EstimateMethod): + raise TypeError("method must be EstimateMethod") + if not isinstance(self.latency_unit, LatencyUnit): + raise TypeError("latency_unit must be LatencyUnit") + for value_name, column_name in ( + ("operation", "operation_column"), + ("hardware", "hardware_column"), + ("datatype", "datatype_column"), + ): + value = getattr(self, value_name) + column = getattr(self, column_name) + if (value is None) == (column is None): + raise ValueError(f"provide exactly one of {value_name} and {column_name}") + for item, name in ((value, value_name), (column, column_name)): + if item is not None and (not isinstance(item, str) or not item): + raise ValueError(f"{name} must be a non-empty string when present") + for name in ( + "selector_columns", + "selector_types", + "constant_selectors", + "operation_aliases", + "datatype_aliases", + ): + object.__setattr__(self, name, FrozenDict(getattr(self, name))) + object.__setattr__(self, "metadata_columns", tuple(self.metadata_columns)) + if any(not isinstance(item, str) or not item for item in self.metadata_columns): + raise ValueError("metadata columns must be non-empty strings") + for name in ("dimensions_json_column", "record_id_column"): + value = getattr(self, name) + if value is not None and (not isinstance(value, str) or not value): + raise ValueError(f"{name} must be a non-empty string when present") + if set(self.selector_columns) != set(self.selector_types): + missing = sorted(set(self.selector_columns).symmetric_difference(self.selector_types)) + raise ValueError(f"selector columns and selector types must have identical keys: {missing}") + invalid_types = {value for value in self.selector_types.values() if value not in _SCALAR_TYPES} + if invalid_types: + raise ValueError(f"unsupported selector types: {', '.join(sorted(invalid_types))}") + collisions = set(self.selector_columns).intersection(self.constant_selectors) + if collisions: + raise ValueError(f"selector columns collide with constants: {', '.join(sorted(collisions))}") + duplicate_identity = {"subject", "operation", "hardware", "datatype"}.intersection( + set(self.selector_columns).union(self.constant_selectors) + ) + if duplicate_identity: + raise ValueError(f"selectors duplicate core identity: {', '.join(sorted(duplicate_identity))}") + + +def _python_scalar(value: Any) -> Any: + if value is None or isinstance(value, (str, bool, int, float)): + return value + item = getattr(value, "item", None) + if callable(item): + return item() + return value + + +def read_tabular_rows(path: str | Path) -> tuple[dict[str, Any], ...]: + """Read CSV, JSON/JSONL, or Parquet without changing source values.""" + + source = Path(path) + suffix = source.suffix.lower() + if suffix == ".csv": + with source.open(newline="", encoding="utf-8") as stream: + reader = csv.DictReader(stream) + if reader.fieldnames is None: + raise ValueError(f"tabular evidence {source} has no CSV header") + rows = tuple(dict(row) for row in reader) + elif suffix in {".jsonl", ".ndjson"}: + rows = tuple(json.loads(line) for line in source.read_text(encoding="utf-8").splitlines() if line.strip()) + elif suffix == ".json": + payload = json.loads(source.read_text(encoding="utf-8")) + if not isinstance(payload, list): + raise ValueError("JSON performance data must be an array of row objects") + rows = tuple(payload) + elif suffix == ".parquet": + try: + import pandas as pd + + frame = pd.read_parquet(source) + except ImportError as error: + raise ImportError( + "Parquet import requires an engine such as pyarrow; install blueprinting[performance-data]" + ) from error + rows = tuple(frame.to_dict(orient="records")) + else: + raise ValueError(f"unsupported performance table format: {suffix or ''}") + if not rows: + raise ValueError(f"tabular evidence {source} has no rows") + if any(not isinstance(row, dict) for row in rows): + raise ValueError("every performance table row must be an object") + return tuple({str(key): _python_scalar(value) for key, value in row.items()} for row in rows) + + +def _required(row: dict[str, Any], column: str, row_number: int) -> Any: + if column not in row: + raise ValueError(f"row {row_number} is missing required column {column!r}") + value = _python_scalar(row[column]) + if value is None or (isinstance(value, str) and not value.strip()): + raise ValueError(f"row {row_number} has an empty required value in {column!r}") + if isinstance(value, float) and not math.isfinite(value): + raise ValueError(f"row {row_number} has a non-finite value in {column!r}") + return value + + +def _parse_scalar(value: Any, type_name: str, *, column: str, row_number: int) -> str | int | float | bool: + value = _python_scalar(value) + if type_name == "string": + if not isinstance(value, str): + value = str(value) + if not value: + raise ValueError(f"row {row_number} has an empty string in {column!r}") + return value + if type_name == "bool": + if isinstance(value, bool): + return value + normalized = str(value).strip().lower() + if normalized in {"true", "1"}: + return True + if normalized in {"false", "0"}: + return False + raise ValueError(f"row {row_number} has an invalid boolean in {column!r}") + try: + numeric = float(value) + except (TypeError, ValueError) as error: + raise ValueError(f"row {row_number} has a non-numeric value in {column!r}") from error + if not math.isfinite(numeric): + raise ValueError(f"row {row_number} has a non-finite value in {column!r}") + if type_name == "float": + return numeric + if not numeric.is_integer(): + raise ValueError(f"row {row_number} has a non-integer value in {column!r}") + return int(numeric) + + +def _identity( + row: dict[str, Any], + row_number: int, + *, + constant: str | None, + column: str | None, + aliases: FrozenDict | None = None, +) -> str: + value = constant if constant is not None else _required(row, column or "", row_number) + result = str(value).strip() + if aliases is not None: + result = aliases.get(result, result) + if not isinstance(result, str) or not result: + raise ValueError(f"row {row_number} resolves to an empty identity") + return result + + +class TabularPerformanceImporter: + """Import simulator/profiler tables using an explicit, revisioned schema.""" + + IMPORTER_REVISION = "blueprinting-tabular-performance-v1" + + @classmethod + def from_file(cls, path: str | Path, spec: TabularImportSpec) -> PerformanceDatabase: + if not isinstance(spec, TabularImportSpec): + raise TypeError("spec must be TabularImportSpec") + source = Path(path) + payload = source.read_bytes() + data_digest = hashlib.sha256(payload).hexdigest() + rows = read_tabular_rows(source) + provenance = EvidenceProvenance( + source=spec.source, + source_revision=spec.source_revision, + importer=cls.IMPORTER_REVISION, + data_digest=data_digest, + method=spec.method, + metadata=FrozenDict({"file_name": source.name}), + ) + records = [] + for row_number, row in enumerate(rows, start=1): + latency = _parse_scalar( + _required(row, spec.latency_column, row_number), + "float", + column=spec.latency_column, + row_number=row_number, + ) + if latency < 0: + raise ValueError(f"row {row_number} has negative latency") + selector = spec.constant_selectors.to_dict() + for name, column in spec.selector_columns.items(): + selector[name] = _parse_scalar( + _required(row, column, row_number), + spec.selector_types[name], + column=column, + row_number=row_number, + ) + if spec.dimensions_json_column is not None: + raw_dimensions = _required(row, spec.dimensions_json_column, row_number) + dimensions = json.loads(raw_dimensions) if isinstance(raw_dimensions, str) else raw_dimensions + if not isinstance(dimensions, dict): + raise ValueError(f"row {row_number} dimensions JSON must be an object") + overlap = set(selector).intersection(dimensions) + if overlap: + raise ValueError(f"row {row_number} dimensions collide with selectors: {sorted(overlap)}") + selector.update(dimensions) + metadata = {column: _required(row, column, row_number) for column in spec.metadata_columns} + if spec.record_id_column is not None: + record_id = str(_required(row, spec.record_id_column, row_number)) + else: + record_id = content_digest( + FrozenDict( + { + "importer": cls.IMPORTER_REVISION, + "data_digest": data_digest, + "row": row_number, + } + ), + "performance-record-id", + ) + records.append( + PerformanceRecord( + record_id=record_id, + subject=spec.subject, + operation=_identity( + row, + row_number, + constant=spec.operation, + column=spec.operation_column, + aliases=spec.operation_aliases, + ), + hardware=_identity( + row, + row_number, + constant=spec.hardware, + column=spec.hardware_column, + ), + datatype=_identity( + row, + row_number, + constant=spec.datatype, + column=spec.datatype_column, + aliases=spec.datatype_aliases, + ), + seconds=latency * spec.latency_unit.seconds_multiplier, + selector=FrozenDict(selector), + provenance=provenance, + metadata=FrozenDict(metadata), + ) + ) + return PerformanceDatabase( + name=spec.name, + records=tuple(records), + metadata=FrozenDict( + { + "source": spec.source, + "source_revision": spec.source_revision, + "data_digest": data_digest, + "importer": cls.IMPORTER_REVISION, + "file_name": source.name, + } + ), + ) + + +class SimulatorPerformanceImporter(TabularPerformanceImporter): + """Named entry point for simulator outputs using ``TabularImportSpec``.""" + + @classmethod + def from_file(cls, path: str | Path, spec: TabularImportSpec) -> PerformanceDatabase: + if spec.method is not EstimateMethod.SIMULATED: + raise ValueError("simulator imports must declare method=EstimateMethod.SIMULATED") + return super().from_file(path, spec) diff --git a/src/blueprinting/compiler/analysis/cost/protocol.py b/src/blueprinting/compiler/analysis/cost/protocol.py new file mode 100644 index 0000000..3b6516e --- /dev/null +++ b/src/blueprinting/compiler/analysis/cost/protocol.py @@ -0,0 +1,427 @@ +"""Normalized task-cost contracts and deterministic evidence resolution. + +The protocol keeps workload facts, evidence selection, and the resulting +estimate separate. Providers answer one immutable :class:`CostQuery`; the +resolver selects exactly one answer and records every attempted provider. +""" + +from __future__ import annotations + +import math +from collections.abc import Mapping +from dataclasses import dataclass, field +from enum import Enum +from typing import Protocol, runtime_checkable + +from ...codec import content_digest, enum_type, record_type +from ...frozen import FrozenDict + + +class CostModelError(RuntimeError): + """Base class for cost-model protocol failures.""" + + +class CostNotAvailableError(CostModelError): + """Raised when no provider can answer a query.""" + + +class InvalidCostEvidenceError(CostModelError): + """Raised when available evidence is ambiguous or internally invalid.""" + + +@enum_type("compiler.analysis.cost.subject.v1") +class CostSubject(Enum): + OPERATOR = "operator" + COMMUNICATION = "communication" + + +@enum_type("compiler.analysis.cost.method.v1") +class EstimateMethod(Enum): + MEASURED = "measured" + SIMULATED = "simulated" + ANALYTICAL = "analytical" + CALIBRATED = "calibrated" + VENDOR_MODEL = "vendor_model" + + +@enum_type("compiler.analysis.cost.match.v1") +class EstimateMatch(Enum): + EXACT_SELECTOR = "exact_selector" + INTERPOLATED = "interpolated" + EXTRAPOLATED = "extrapolated" + ANALYTICAL = "analytical" + FALLBACK = "fallback" + + +@enum_type("compiler.analysis.cost.support_status.v1") +class SupportStatus(Enum): + AVAILABLE = "available" + UNAVAILABLE = "unavailable" + INVALID = "invalid" + + +_RESERVED_DIMENSIONS = frozenset( + { + "subject", + "operation", + "hardware", + "datatype", + "operations", + "read_bytes", + "write_bytes", + "message_bytes", + "participants", + "network_tier", + "engine", + "hardware_revision", + "implementation", + "implementation_revision", + "runtime", + "runtime_revision", + "topology", + "power_mode", + } +) + + +def _required_text(value: str, name: str) -> None: + if not isinstance(value, str) or not value.strip(): + raise ValueError(f"{name} must be a non-empty string") + + +def _optional_text(value: str, name: str) -> None: + if not isinstance(value, str): + raise TypeError(f"{name} must be a string") + + +def _non_negative_integer(value: int, name: str) -> None: + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise ValueError(f"{name} must be a non-negative integer") + + +@record_type("compiler.analysis.cost.query.v1") +@dataclass(frozen=True) +class CostQuery: + """One fully identified task-cost question. + + ``dimensions`` carries shape or domain-specific selectors. Fields that + affect every provider (work, deployment, and implementation identity) are + first-class so they cannot disappear from cache identity accidentally. + Empty strings mean explicitly unknown, not a wildcard claim by a provider. + """ + + subject: CostSubject + operation: str + hardware: str + datatype: str + operations: int = 0 + read_bytes: int = 0 + write_bytes: int = 0 + message_bytes: int = 0 + participants: int = 1 + network_tier: int = 0 + engine: str = "" + hardware_revision: str = "" + implementation: str = "" + implementation_revision: str = "" + runtime: str = "" + runtime_revision: str = "" + topology: str = "" + power_mode: str = "" + dimensions: FrozenDict = field(default_factory=FrozenDict) + + def __post_init__(self) -> None: + if not isinstance(self.subject, CostSubject): + raise TypeError("subject must be CostSubject") + for name in ("operation", "hardware", "datatype"): + _required_text(getattr(self, name), name) + for name in ( + "engine", + "hardware_revision", + "implementation", + "implementation_revision", + "runtime", + "runtime_revision", + "topology", + "power_mode", + ): + _optional_text(getattr(self, name), name) + for name in ("operations", "read_bytes", "write_bytes", "message_bytes", "network_tier"): + _non_negative_integer(getattr(self, name), name) + if isinstance(self.participants, bool) or not isinstance(self.participants, int) or self.participants <= 0: + raise ValueError("participants must be a positive integer") + object.__setattr__(self, "dimensions", FrozenDict(self.dimensions)) + collisions = _RESERVED_DIMENSIONS.intersection(self.dimensions) + if collisions: + raise ValueError(f"dimensions use reserved names: {', '.join(sorted(collisions))}") + + @property + def digest(self) -> str: + return content_digest(self, "cost-query") + + @property + def match_context(self) -> FrozenDict: + """Return the flat context against which evidence selectors match.""" + + context = self.dimensions.to_dict() + context.update( + { + "operations": self.operations, + "read_bytes": self.read_bytes, + "write_bytes": self.write_bytes, + "message_bytes": self.message_bytes, + "participants": self.participants, + "network_tier": self.network_tier, + } + ) + for name in ( + "engine", + "hardware_revision", + "implementation", + "implementation_revision", + "runtime", + "runtime_revision", + "topology", + "power_mode", + ): + value = getattr(self, name) + if value: + context[name] = value + return FrozenDict(context) + + +@record_type("compiler.analysis.cost.query_context.v1") +@dataclass(frozen=True) +class CostQueryContext: + """Deployment/implementation facts supplied after portable planning. + + Maps are keyed by semantic operation name. A generic operation key (for + example ``gemm``) is used as a fallback when no semantic key is present. + """ + + runtime: str = "" + runtime_revision: str = "" + topology: str = "" + power_mode: str = "" + implementations: FrozenDict = field(default_factory=FrozenDict) + implementation_revisions: FrozenDict = field(default_factory=FrozenDict) + dimensions: FrozenDict = field(default_factory=FrozenDict) + operation_dimensions: FrozenDict = field(default_factory=FrozenDict) + + def __post_init__(self) -> None: + for name in ("runtime", "runtime_revision", "topology", "power_mode"): + _optional_text(getattr(self, name), name) + if self.runtime: + object.__setattr__(self, "runtime", self.runtime.strip().lower()) + for name in ("implementations", "implementation_revisions", "dimensions", "operation_dimensions"): + object.__setattr__(self, name, FrozenDict(getattr(self, name))) + for name, values in ( + ("implementations", self.implementations), + ("implementation_revisions", self.implementation_revisions), + ): + if any(not isinstance(value, str) or not value for value in values.values()): + raise ValueError(f"{name} values must be non-empty strings") + for operation, dimensions in self.operation_dimensions.items(): + if not isinstance(dimensions, Mapping): + raise TypeError(f"operation dimensions for {operation!r} must be a mapping") + + def implementation_for(self, semantic_operation: str, operation: str) -> str: + return self.implementations.get(semantic_operation, self.implementations.get(operation, "")) + + def implementation_revision_for(self, semantic_operation: str, operation: str) -> str: + return self.implementation_revisions.get( + semantic_operation, + self.implementation_revisions.get(operation, ""), + ) + + def dimensions_for(self, semantic_operation: str, operation: str) -> FrozenDict: + result = self.dimensions.to_dict() + specific = self.operation_dimensions.get(semantic_operation, self.operation_dimensions.get(operation, {})) + overlap = set(result).intersection(specific) + conflicts = tuple(key for key in overlap if result[key] != specific[key]) + if conflicts: + raise ValueError(f"operation-specific dimensions conflict with global context: {sorted(conflicts)}") + result.update(specific) + return FrozenDict(result) + + +@record_type("compiler.analysis.cost.uncertainty.v1") +@dataclass(frozen=True) +class EstimateUncertainty: + sample_count: int = 0 + standard_deviation_seconds: float | None = None + lower_bound_seconds: float | None = None + upper_bound_seconds: float | None = None + confidence: float | None = None + + def __post_init__(self) -> None: + _non_negative_integer(self.sample_count, "sample_count") + for name in ("standard_deviation_seconds", "lower_bound_seconds", "upper_bound_seconds"): + value = getattr(self, name) + if value is not None and ( + isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0 + ): + raise ValueError(f"{name} must be finite and non-negative when present") + if self.confidence is not None and ( + isinstance(self.confidence, bool) + or not isinstance(self.confidence, (int, float)) + or not math.isfinite(self.confidence) + or not 0 <= self.confidence <= 1 + ): + raise ValueError("confidence must be in [0, 1] when present") + if ( + self.lower_bound_seconds is not None + and self.upper_bound_seconds is not None + and self.lower_bound_seconds > self.upper_bound_seconds + ): + raise ValueError("uncertainty lower bound cannot exceed upper bound") + + +@record_type("compiler.analysis.cost.estimate.v1") +@dataclass(frozen=True) +class CostEstimate: + seconds: float + provider: str + provider_revision: str + source_revision: str + method: EstimateMethod + match: EstimateMatch + uncertainty: EstimateUncertainty = field(default_factory=EstimateUncertainty) + raw_record_ids: tuple[str, ...] = () + validity_domain: FrozenDict = field(default_factory=FrozenDict) + components: FrozenDict = field(default_factory=FrozenDict) + assumptions: tuple[str, ...] = () + + def __post_init__(self) -> None: + if ( + isinstance(self.seconds, bool) + or not isinstance(self.seconds, (int, float)) + or not math.isfinite(self.seconds) + or self.seconds < 0 + ): + raise ValueError("estimate seconds must be finite and non-negative") + for name in ("provider", "provider_revision", "source_revision"): + _required_text(getattr(self, name), name) + if not isinstance(self.method, EstimateMethod): + raise TypeError("method must be EstimateMethod") + if not isinstance(self.match, EstimateMatch): + raise TypeError("match must be EstimateMatch") + if not isinstance(self.uncertainty, EstimateUncertainty): + raise TypeError("uncertainty must be EstimateUncertainty") + object.__setattr__(self, "raw_record_ids", tuple(self.raw_record_ids)) + object.__setattr__(self, "validity_domain", FrozenDict(self.validity_domain)) + object.__setattr__(self, "components", FrozenDict(self.components)) + object.__setattr__(self, "assumptions", tuple(self.assumptions)) + if any(not isinstance(item, str) or not item for item in self.raw_record_ids): + raise ValueError("raw record IDs must be non-empty strings") + if any(not isinstance(item, str) or not item for item in self.assumptions): + raise ValueError("assumptions must be non-empty strings") + + +@dataclass(frozen=True) +class CostSupport: + status: SupportStatus + reason: str + missing_fields: tuple[str, ...] = () + + def __post_init__(self) -> None: + if not isinstance(self.status, SupportStatus): + raise TypeError("status must be SupportStatus") + _required_text(self.reason, "support reason") + object.__setattr__(self, "missing_fields", tuple(self.missing_fields)) + if any(not isinstance(item, str) or not item for item in self.missing_fields): + raise ValueError("missing fields must be non-empty strings") + + @classmethod + def available(cls, reason: str = "query is covered") -> CostSupport: + return cls(SupportStatus.AVAILABLE, reason) + + @classmethod + def unavailable(cls, reason: str, *missing_fields: str) -> CostSupport: + return cls(SupportStatus.UNAVAILABLE, reason, tuple(missing_fields)) + + @classmethod + def invalid(cls, reason: str) -> CostSupport: + return cls(SupportStatus.INVALID, reason) + + +@dataclass(frozen=True) +class ProviderAttempt: + provider: str + revision: str + support: CostSupport + + +@dataclass(frozen=True) +class CostResolution: + query_digest: str + estimate: CostEstimate + attempts: tuple[ProviderAttempt, ...] + resolver_revision: str + + +@runtime_checkable +class CostProvider(Protocol): + @property + def name(self) -> str: ... + + @property + def revision(self) -> str: ... + + def supports(self, query: CostQuery) -> CostSupport: ... + + def estimate(self, query: CostQuery) -> CostEstimate: ... + + +class CostResolver: + """Ordered, deterministic provider selection without anonymous blending.""" + + def __init__(self, providers: tuple[CostProvider, ...], *, policy_name: str = "ordered-first-supported-v1") -> None: + self._providers = tuple(providers) + _required_text(policy_name, "policy_name") + identities = tuple((provider.name, provider.revision) for provider in self._providers) + if len(set(identities)) != len(identities): + raise ValueError("resolver providers must have unique name/revision identities") + self._policy_name = policy_name + self._revision = content_digest( + FrozenDict({"policy": policy_name, "providers": identities}), + "cost-resolver", + ) + + @property + def providers(self) -> tuple[CostProvider, ...]: + return self._providers + + @property + def revision(self) -> str: + return self._revision + + def try_resolve(self, query: CostQuery) -> CostResolution | None: + if not isinstance(query, CostQuery): + raise TypeError("query must be CostQuery") + attempts: list[ProviderAttempt] = [] + for provider in self._providers: + support = provider.supports(query) + if not isinstance(support, CostSupport): + raise TypeError(f"provider {provider.name!r} returned an invalid support result") + attempts.append(ProviderAttempt(provider.name, provider.revision, support)) + if support.status is SupportStatus.INVALID: + raise InvalidCostEvidenceError( + f"provider {provider.name!r} found invalid evidence for {query.digest}: {support.reason}" + ) + if support.status is SupportStatus.UNAVAILABLE: + continue + estimate = provider.estimate(query) + if not isinstance(estimate, CostEstimate): + raise TypeError(f"provider {provider.name!r} returned an invalid estimate") + if estimate.provider != provider.name or estimate.provider_revision != provider.revision: + raise InvalidCostEvidenceError(f"provider {provider.name!r} returned inconsistent provenance identity") + return CostResolution(query.digest, estimate, tuple(attempts), self.revision) + return None + + def resolve(self, query: CostQuery) -> CostResolution: + resolution = self.try_resolve(query) + if resolution is None: + attempted = ", ".join(provider.name for provider in self._providers) or "none" + raise CostNotAvailableError(f"no cost provider covers query {query.digest}; attempted: {attempted}") + return resolution diff --git a/src/blueprinting/compiler/analysis/cost/roofline.py b/src/blueprinting/compiler/analysis/cost/roofline.py new file mode 100644 index 0000000..19e036b --- /dev/null +++ b/src/blueprinting/compiler/analysis/cost/roofline.py @@ -0,0 +1,160 @@ +"""Analytical compute/memory roofline and collective communication provider.""" + +from __future__ import annotations + +from ...codec import content_digest +from ...frozen import FrozenDict +from ..cost_model import CalibrationMode, HardwareProfile +from .protocol import ( + CostEstimate, + CostProvider, + CostQuery, + CostSubject, + CostSupport, + EstimateMatch, + EstimateMethod, + EstimateUncertainty, + InvalidCostEvidenceError, + SupportStatus, +) + + +class RooflineCostProvider(CostProvider): + """Cost portable work with explicit peak/evidence rates. + + Local operator latency is ``max(compute, memory)`` by default. The legacy + profile's ``no_overlap`` behavior remains available as an explicit option; + it is never selected by an implicit correction factor. + """ + + def __init__( + self, + hardware: HardwareProfile, + *, + mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, + processing_mode: str = "roofline", + ) -> None: + if not isinstance(hardware, HardwareProfile): + raise TypeError("hardware must be HardwareProfile") + if not isinstance(mode, CalibrationMode): + raise TypeError("mode must be CalibrationMode") + if processing_mode == "profile": + processing_mode = hardware.processing_mode + if processing_mode not in {"roofline", "no_overlap"}: + raise ValueError("processing_mode must be roofline, no_overlap, or profile") + self._hardware = hardware + self._mode = mode + self._processing_mode = processing_mode + self._name = f"roofline:{hardware.name}" + self._revision = content_digest( + FrozenDict( + { + "provider": "blueprinting-roofline-v1", + "hardware_revision": hardware.evidence_revision, + "calibration_mode": mode.value, + "processing_mode": processing_mode, + } + ), + "cost-provider", + ) + + @property + def name(self) -> str: + return self._name + + @property + def revision(self) -> str: + return self._revision + + @property + def hardware(self) -> HardwareProfile: + return self._hardware + + def supports(self, query: CostQuery) -> CostSupport: + if query.hardware != self._hardware.name: + return CostSupport.unavailable("query targets a different hardware profile") + if query.datatype != self._hardware.datatype: + return CostSupport.unavailable("query datatype is not covered by this hardware profile") + if query.hardware_revision and query.hardware_revision != self._hardware.evidence_revision: + return CostSupport.unavailable("query requires a different hardware evidence revision") + if query.subject is CostSubject.OPERATOR: + if query.engine not in {"matrix", "vector"}: + return CostSupport.unavailable( + "operator roofline requires engine='matrix' or engine='vector'", "engine" + ) + return CostSupport.available("compute/memory roofline is defined") + if query.network_tier >= len(self._hardware.networks): + return CostSupport.unavailable("hardware profile does not define the requested network tier") + network = self._hardware.networks[query.network_tier] + if query.operation not in network.operations: + return CostSupport.unavailable("network tier does not define the requested communication operation") + return CostSupport.available("analytical collective model is defined") + + def estimate(self, query: CostQuery) -> CostEstimate: + support = self.supports(query) + if support.status is not SupportStatus.AVAILABLE: + raise InvalidCostEvidenceError(f"roofline provider cannot estimate query: {support.reason}") + + compute_seconds = 0.0 + memory_seconds = 0.0 + network_seconds = 0.0 + bottleneck = "none" + assumptions: tuple[str, ...] + if query.subject is CostSubject.OPERATOR: + processor = self._hardware.matrix if query.engine == "matrix" else self._hardware.vector + if query.operations: + compute_seconds = query.operations / processor.throughput(query.operations, self._mode) + transferred_bytes = query.read_bytes + query.write_bytes + if transferred_bytes: + memory_seconds = transferred_bytes / self._hardware.memory.throughput(transferred_bytes, self._mode) + if self._processing_mode == "roofline": + seconds = max(compute_seconds, memory_seconds) + bottleneck = "compute" if compute_seconds >= memory_seconds else "memory" + assumptions = ("compute and memory service overlap perfectly at the roofline bound",) + else: + seconds = compute_seconds + memory_seconds + bottleneck = "serialized-compute-memory" + assumptions = ("compute and memory service are serialized",) + else: + network = self._hardware.networks[query.network_tier] + network_seconds = network.time( + query.operation, + query.message_bytes, + query.participants, + self._mode, + ) + seconds = network_seconds + bottleneck = "network" + assumptions = ("collective cost follows the selected network tier's volume and latency model",) + + return CostEstimate( + seconds=seconds, + provider=self.name, + provider_revision=self.revision, + source_revision=self._hardware.evidence_revision, + method=EstimateMethod.ANALYTICAL, + match=EstimateMatch.ANALYTICAL, + uncertainty=EstimateUncertainty(), + validity_domain=FrozenDict( + { + "hardware": self._hardware.name, + "datatype": self._hardware.datatype, + "calibration_mode": self._mode.value, + "processing_mode": self._processing_mode, + } + ), + components=FrozenDict( + { + "compute_seconds": compute_seconds, + "memory_seconds": memory_seconds, + "network_seconds": network_seconds, + "arithmetic_intensity": ( + query.operations / (query.read_bytes + query.write_bytes) + if query.read_bytes + query.write_bytes + else 0.0 + ), + "bottleneck": bottleneck, + } + ), + assumptions=assumptions, + ) diff --git a/src/blueprinting/compiler/analysis/inference_cost.py b/src/blueprinting/compiler/analysis/inference_cost.py index e01a998..9f6257b 100644 --- a/src/blueprinting/compiler/analysis/inference_cost.py +++ b/src/blueprinting/compiler/analysis/inference_cost.py @@ -9,6 +9,7 @@ from ..ir import CollectiveKind, PlanBuffer, PlanTask, PortablePlanIR from ..models.transformer import TransformerModelSpec from ..models.transformer_inference import TransformerInferenceExecutionSpec +from .cost import CostQuery, CostQueryContext, CostResolver, CostSubject from .cost_model import CalibrationMode, HardwareProfile from .inference_evidence import InferenceCostProvider, InferenceEvidenceQuery from .transformer_inference import InferenceInvocation @@ -25,7 +26,10 @@ class InferenceTaskEstimate: total_seconds: float evidence_provider: str evidence_revision: str + evidence_source_revision: str + evidence_record_ids: tuple[str, ...] evidence_match: str + evidence_method: str @dataclass(frozen=True) @@ -86,6 +90,106 @@ def inference_evidence_query_for( ) +_GEMM_PRIMITIVES = frozenset( + { + "attention_pre_projection", + "attention_post_projection", + "mlp_up_projection", + "mlp_down_projection", + } +) + + +def _merge_dimensions(base: dict[str, object], extra: FrozenDict) -> FrozenDict: + overlap = set(base).intersection(extra) + conflicts = tuple(key for key in overlap if base[key] != extra[key]) + if conflicts: + raise ValueError(f"cost context conflicts with canonical workload dimensions: {sorted(conflicts)}") + base.update(extra) + return FrozenDict(base) + + +def cost_query_for_inference_task( + task: PlanTask, + *, + hardware: HardwareProfile, + execution: TransformerInferenceExecutionSpec, + model: TransformerModelSpec, + batch_size: int, + query_tokens: int, + context_tokens: int, + context: CostQueryContext = CostQueryContext(), +) -> CostQuery: + """Build a normalized cost request directly from portable workload facts.""" + + if not isinstance(context, CostQueryContext): + raise TypeError("context must be CostQueryContext") + invocation = _invocation_from_plan_task(task) + primitive = invocation.primitive + operation = "gemm" if primitive in _GEMM_PRIMITIVES else primitive + subject = CostSubject.COMMUNICATION if invocation.engine is EngineKind.COLLECTIVE else CostSubject.OPERATOR + tensor_parallel = execution.tensor_parallel + dimensions: dict[str, object] = { + "semantic_operation": primitive, + "source_layer": invocation.source_layer, + "phase": invocation.phase.value, + "model_name": model.name, + "model_sequence_length": model.sequence_length, + "hidden_size": model.hidden_size, + "feedforward_size": model.feedforward_size, + "attention_heads": model.attention_heads, + "kv_heads": model.attention_heads, + "local_attention_heads": model.attention_heads // tensor_parallel, + "local_kv_heads": model.attention_heads // tensor_parallel, + "head_size": model.attention_head_size, + "batch_size": batch_size, + "query_tokens": query_tokens, + "context_tokens": context_tokens, + "tensor_parallel": tensor_parallel, + "num_tokens": batch_size * query_tokens, + "use_gated_mlp": False, + "beam_width": 1, + "window_size": 0, + "kv_cache_datatype": execution.datatype, + } + if primitive in _GEMM_PRIMITIVES: + tokens = batch_size * query_tokens + local_hidden = model.hidden_size // tensor_parallel + local_feedforward = model.feedforward_size // tensor_parallel + m = tokens + if primitive == "attention_pre_projection": + n, k = 3 * local_hidden, model.hidden_size + elif primitive == "attention_post_projection": + n, k = model.hidden_size, local_hidden + elif primitive == "mlp_up_projection": + n, k = local_feedforward, model.hidden_size + else: + n, k = model.hidden_size, local_feedforward + dimensions.update({"m": m, "n": n, "k": k}) + dimensions = _merge_dimensions(dimensions, context.dimensions_for(primitive, operation)).to_dict() + return CostQuery( + subject=subject, + operation=operation, + hardware=hardware.name, + datatype=execution.datatype, + operations=task.workload.operations, + read_bytes=task.workload.read_bytes, + write_bytes=task.workload.write_bytes, + message_bytes=task.workload.message_bytes, + participants=tensor_parallel if subject is CostSubject.COMMUNICATION else 1, + network_tier=invocation.network_tier or 0, + engine=invocation.engine.value, + hardware_revision=hardware.evidence_revision, + implementation=context.implementation_for(primitive, operation), + implementation_revision=context.implementation_revision_for(primitive, operation), + runtime=context.runtime, + runtime_revision=context.runtime_revision, + topology=context.topology, + power_mode=context.power_mode, + dimensions=FrozenDict(dimensions), + ) + + def _task_estimate( task: PlanTask, *, @@ -97,6 +201,8 @@ def _task_estimate( context_tokens: int, mode: CalibrationMode, cost_provider: InferenceCostProvider | None, + cost_resolver: CostResolver | None, + cost_context: CostQueryContext, ) -> InferenceTaskEstimate: invocation = _invocation_from_plan_task(task) work = invocation.work @@ -122,9 +228,33 @@ def _task_estimate( analytical_seconds = hardware.processing_time(compute_seconds, memory_seconds) + network_seconds provider_name = "analytical-system-profile" revision = hardware.evidence_revision + source_revision = hardware.evidence_revision + record_ids: tuple[str, ...] = () match = mode.value + method = "analytical" total_seconds = analytical_seconds - if cost_provider is not None: + if cost_resolver is not None: + resolution = cost_resolver.resolve( + cost_query_for_inference_task( + task, + model=model, + execution=execution, + hardware=hardware, + batch_size=batch_size, + query_tokens=query_tokens, + context_tokens=context_tokens, + context=cost_context, + ) + ) + evidence = resolution.estimate + total_seconds = evidence.seconds + provider_name = evidence.provider + revision = evidence.provider_revision + source_revision = evidence.source_revision + record_ids = evidence.raw_record_ids + match = evidence.match.value + method = evidence.method.value + elif cost_provider is not None: evidence = cost_provider.resolve( inference_evidence_query_for( invocation, @@ -140,7 +270,9 @@ def _task_estimate( total_seconds = evidence.seconds provider_name = evidence.provider revision = evidence.revision + source_revision = evidence.revision match = evidence.match + method = "external" return InferenceTaskEstimate( invocation=invocation, compute_seconds=compute_seconds, @@ -150,7 +282,10 @@ def _task_estimate( total_seconds=total_seconds, evidence_provider=provider_name, evidence_revision=revision, + evidence_source_revision=source_revision, + evidence_record_ids=record_ids, evidence_match=match, + evidence_method=method, ) @@ -227,6 +362,8 @@ def estimate_inference_phase( mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, *, cost_provider: InferenceCostProvider | None = None, + cost_resolver: CostResolver | None = None, + cost_context: CostQueryContext = CostQueryContext(), ) -> InferencePhaseEstimate: """Cost one prefill or decode phase point without queueing assumptions.""" @@ -241,6 +378,10 @@ def estimate_inference_phase( raise TypeError("portable inference plan is missing InferencePhase") if hardware.datatype != execution.datatype: raise ValueError("hardware profile datatype does not match inference execution datatype") + if cost_provider is not None and cost_resolver is not None: + raise ValueError("cost_provider and cost_resolver are mutually exclusive") + if not isinstance(cost_context, CostQueryContext): + raise TypeError("cost_context must be CostQueryContext") batch_size = plan.attributes.get("batch_size") query_tokens = plan.attributes.get("query_tokens") context_tokens = plan.attributes.get("context_tokens") @@ -260,25 +401,61 @@ def estimate_inference_phase( context_tokens=context_tokens, mode=mode, cost_provider=cost_provider, + cost_resolver=cost_resolver, + cost_context=cost_context, ) ) block_seconds = sum(item.total_seconds for item in task_estimates) transformer_seconds = block_seconds * model.block_count pipeline_seconds = 0.0 + pipeline_estimate = None if execution.pipeline_parallel > 1: - try: - network = hardware.networks[execution.pipeline_parallel_network] - except IndexError as error: - raise ValueError( - f"hardware profile does not define network tier {execution.pipeline_parallel_network}" - ) from error - pipeline_seconds = (execution.pipeline_parallel - 1) * network.time( - "p2p", - _concrete_buffer_size(next(buffer for buffer in plan.buffers if buffer.id == plan.inputs[0])), - 2, - mode, - ) + boundary_bytes = _concrete_buffer_size(next(buffer for buffer in plan.buffers if buffer.id == plan.inputs[0])) + if cost_resolver is None: + try: + network = hardware.networks[execution.pipeline_parallel_network] + except IndexError as error: + raise ValueError( + f"hardware profile does not define network tier {execution.pipeline_parallel_network}" + ) from error + one_hop_seconds = network.time("p2p", boundary_bytes, 2, mode) + else: + pipeline_dimensions = { + "semantic_operation": "p2p", + "phase": phase.value, + "model_name": model.name, + "batch_size": batch_size, + "query_tokens": query_tokens, + "context_tokens": context_tokens, + "pipeline_parallel": execution.pipeline_parallel, + } + pipeline_dimensions = _merge_dimensions( + pipeline_dimensions, + cost_context.dimensions_for("p2p", "p2p"), + ) + pipeline_estimate = cost_resolver.resolve( + CostQuery( + subject=CostSubject.COMMUNICATION, + operation="p2p", + hardware=hardware.name, + datatype=execution.datatype, + message_bytes=boundary_bytes, + participants=2, + network_tier=execution.pipeline_parallel_network, + engine=EngineKind.COLLECTIVE.value, + hardware_revision=hardware.evidence_revision, + implementation=cost_context.implementation_for("p2p", "p2p"), + implementation_revision=cost_context.implementation_revision_for("p2p", "p2p"), + runtime=cost_context.runtime, + runtime_revision=cost_context.runtime_revision, + topology=cost_context.topology, + power_mode=cost_context.power_mode, + dimensions=pipeline_dimensions, + ) + ).estimate + one_hop_seconds = pipeline_estimate.seconds + pipeline_seconds = (execution.pipeline_parallel - 1) * one_hop_seconds blocks_per_stage = model.block_count // execution.pipeline_parallel boundary_bytes = _concrete_buffer_size(next(buffer for buffer in plan.buffers if buffer.id == plan.inputs[0])) @@ -291,6 +468,14 @@ def estimate_inference_phase( revisions = {hardware.evidence_revision: "analytical-system-profile"} for item in task_estimates: revisions[item.evidence_revision] = item.evidence_provider + if item.evidence_source_revision != item.evidence_revision: + revisions[item.evidence_source_revision] = f"source:{item.evidence_provider}" + if cost_resolver is not None: + revisions[cost_resolver.revision] = "cost-resolver-policy" + if pipeline_estimate is not None: + revisions[pipeline_estimate.provider_revision] = pipeline_estimate.provider + if pipeline_estimate.source_revision != pipeline_estimate.provider_revision: + revisions[pipeline_estimate.source_revision] = f"source:{pipeline_estimate.provider}" return InferencePhaseEstimate( mode=mode, phase=phase, diff --git a/src/blueprinting/compiler/analysis/vidur.py b/src/blueprinting/compiler/analysis/vidur.py index fe3e376..0969571 100644 --- a/src/blueprinting/compiler/analysis/vidur.py +++ b/src/blueprinting/compiler/analysis/vidur.py @@ -16,6 +16,8 @@ from ..bindings import InferencePhase from ..codec import content_digest from ..frozen import FrozenDict +from .cost.database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord +from .cost.protocol import CostSubject, EstimateMethod from .inference_evidence import InferenceEvidenceQuery, InferenceEvidenceResult _COMPUTE_COLUMNS = { @@ -30,6 +32,27 @@ "residual_add": "time_stats.add.median", } +_SOURCE_LAYERS = { + "input_layernorm": "attention.input_norm", + "attention_pre_projection": "attention.qkv", + "attention_rope": "attention.rope", + "attention_post_projection": "attention.output", + "post_attention_layernorm": "mlp.input_norm", + "mlp_up_projection": "mlp.up", + "mlp_activation": "mlp.activation", + "mlp_down_projection": "mlp.down", + "residual_add": "mlp.residual", +} + +_GEMM_PRIMITIVES = frozenset( + { + "attention_pre_projection", + "attention_post_projection", + "mlp_up_projection", + "mlp_down_projection", + } +) + def _read_rows(path: Path, *, required: frozenset[str], timing_columns: frozenset[str]) -> tuple[dict[str, str], ...]: with path.open(newline="", encoding="utf-8") as stream: @@ -281,3 +304,271 @@ def _compute_values(self, query: InferenceEvidenceQuery) -> tuple[float, ...]: if matches and value is not None: values.append(value) return tuple(values) + + +def _strict_integer(row: dict[str, str], key: str, row_number: int, table: str) -> int: + value = _integer(row, key) + if value is None: + raise ValueError(f"Vidur {table} row {row_number} has invalid {key!r}") + return value + + +def _strict_boolean(row: dict[str, str], key: str, row_number: int, table: str) -> bool: + value = _boolean(row, key) + if value is None: + raise ValueError(f"Vidur {table} row {row_number} has invalid {key!r}") + return value + + +class VidurProfileImporter: + """Explicitly promote Vidur profile rows into a cost evidence database. + + This is deliberately separate from :class:`VidurProfileBaseline`. Calling + the importer is the policy decision that makes user-supplied profile data + admissible to a ``CostResolver``; baseline lookup remains post-hoc only. + """ + + IMPORTER_REVISION = "blueprinting-vidur-profile-v1" + + @classmethod + def from_csv( + cls, + *, + attention_csv: str | Path, + compute_csv: str | Path, + model_name: str, + hardware_name: str, + source_revision: str, + datatype: str = "float16", + database_name: str | None = None, + ) -> PerformanceDatabase: + for name, value in ( + ("model_name", model_name), + ("hardware_name", hardware_name), + ("source_revision", source_revision), + ("datatype", datatype), + ): + if not isinstance(value, str) or not value: + raise ValueError(f"{name} must be a non-empty string") + attention_path = Path(attention_csv) + compute_path = Path(compute_csv) + digester = hashlib.sha256() + for path in (attention_path, compute_path): + payload = path.read_bytes() + digester.update(path.name.encode("utf-8")) + digester.update(len(payload).to_bytes(8, "big")) + digester.update(payload) + data_digest = digester.hexdigest() + attention_rows = _read_rows( + attention_path, + required=frozenset( + { + "n_embd", + "n_q_head", + "n_kv_head", + "num_tensor_parallel_workers", + "batch_size", + "prefill_chunk_size", + "kv_cache_size", + "is_prefill", + "attention_backend", + "block_size", + "max_model_len", + } + ), + timing_columns=frozenset( + { + "time_stats.attn_prefill.median", + "time_stats.attn_decode.median", + "time_stats.attn_kv_cache_save.median", + } + ), + ) + compute_rows = _read_rows( + compute_path, + required=frozenset( + { + "n_embd", + "n_expanded_embd", + "n_head", + "n_kv_head", + "num_tensor_parallel_workers", + "num_tokens", + "use_gated_mlp", + } + ), + timing_columns=frozenset(_COMPUTE_COLUMNS.values()), + ) + provenance = EvidenceProvenance( + source="vidur-profile", + source_revision=source_revision, + importer=cls.IMPORTER_REVISION, + data_digest=data_digest, + method=EstimateMethod.MEASURED, + metadata=FrozenDict( + { + "attention_file": attention_path.name, + "compute_file": compute_path.name, + } + ), + ) + records = [ + *cls._attention_records( + attention_rows, + model_name=model_name, + hardware_name=hardware_name, + datatype=datatype, + provenance=provenance, + data_digest=data_digest, + ), + *cls._compute_records( + compute_rows, + model_name=model_name, + hardware_name=hardware_name, + datatype=datatype, + provenance=provenance, + data_digest=data_digest, + ), + ] + return PerformanceDatabase( + name=database_name or f"vidur-{model_name}-{hardware_name}", + records=tuple(records), + metadata=FrozenDict( + { + "source": "vidur-profile", + "source_revision": source_revision, + "data_digest": data_digest, + "importer": cls.IMPORTER_REVISION, + } + ), + ) + + @classmethod + def _record_id(cls, data_digest: str, table: str, row_number: int, metric: str) -> str: + return content_digest( + FrozenDict( + { + "importer": cls.IMPORTER_REVISION, + "data_digest": data_digest, + "table": table, + "row": row_number, + "metric": metric, + } + ), + "performance-record-id", + ) + + @classmethod + def _attention_records( + cls, + rows: tuple[dict[str, str], ...], + *, + model_name: str, + hardware_name: str, + datatype: str, + provenance: EvidenceProvenance, + data_digest: str, + ) -> tuple[PerformanceRecord, ...]: + records = [] + for row_number, row in enumerate(rows, start=1): + prefill = _strict_boolean(row, "is_prefill", row_number, "attention") + batch_size = _strict_integer(row, "batch_size", row_number, "attention") + prefill_chunk = _strict_integer(row, "prefill_chunk_size", row_number, "attention") + cache_size = _strict_integer(row, "kv_cache_size", row_number, "attention") + phase = "prefill" if prefill else "decode" + query_tokens = prefill_chunk if prefill else 1 + context_tokens = prefill_chunk if prefill else cache_size + 1 + selector = FrozenDict( + { + "semantic_operation": "attention_core", + "phase": phase, + "model_name": model_name, + "model_sequence_length": _strict_integer(row, "max_model_len", row_number, "attention"), + "hidden_size": _strict_integer(row, "n_embd", row_number, "attention"), + "attention_heads": _strict_integer(row, "n_q_head", row_number, "attention"), + "kv_heads": _strict_integer(row, "n_kv_head", row_number, "attention"), + "batch_size": batch_size, + "query_tokens": query_tokens, + "context_tokens": context_tokens, + "tensor_parallel": _strict_integer(row, "num_tensor_parallel_workers", row_number, "attention"), + "block_size": _strict_integer(row, "block_size", row_number, "attention"), + "implementation": row["attention_backend"], + } + ) + metrics = ( + ( + "attention_core", + "time_stats.attn_prefill.median" if prefill else "time_stats.attn_decode.median", + "attention.core", + ), + ("attention_kv_cache_save", "time_stats.attn_kv_cache_save.median", "attention.kv_cache"), + ) + for primitive, metric, source_layer in metrics: + milliseconds = _milliseconds(row, metric) + if milliseconds is None: + continue + record_selector = selector.to_dict() + record_selector["semantic_operation"] = primitive + record_selector["source_layer"] = source_layer + records.append( + PerformanceRecord( + record_id=cls._record_id(data_digest, "attention", row_number, metric), + subject=CostSubject.OPERATOR, + operation=primitive, + hardware=hardware_name, + datatype=datatype, + seconds=milliseconds * 1e-3, + selector=FrozenDict(record_selector), + provenance=provenance, + metadata=FrozenDict({"upstream_metric": metric}), + ) + ) + return tuple(records) + + @classmethod + def _compute_records( + cls, + rows: tuple[dict[str, str], ...], + *, + model_name: str, + hardware_name: str, + datatype: str, + provenance: EvidenceProvenance, + data_digest: str, + ) -> tuple[PerformanceRecord, ...]: + records = [] + for row_number, row in enumerate(rows, start=1): + shared_selector = { + "model_name": model_name, + "hidden_size": _strict_integer(row, "n_embd", row_number, "compute"), + "feedforward_size": _strict_integer(row, "n_expanded_embd", row_number, "compute"), + "attention_heads": _strict_integer(row, "n_head", row_number, "compute"), + "kv_heads": _strict_integer(row, "n_kv_head", row_number, "compute"), + "tensor_parallel": _strict_integer(row, "num_tensor_parallel_workers", row_number, "compute"), + "num_tokens": _strict_integer(row, "num_tokens", row_number, "compute"), + "use_gated_mlp": _strict_boolean(row, "use_gated_mlp", row_number, "compute"), + } + for primitive, metric in _COMPUTE_COLUMNS.items(): + milliseconds = _milliseconds(row, metric) + if milliseconds is None: + continue + operation = "gemm" if primitive in _GEMM_PRIMITIVES else primitive + selector = { + **shared_selector, + "semantic_operation": primitive, + "source_layer": _SOURCE_LAYERS[primitive], + } + records.append( + PerformanceRecord( + record_id=cls._record_id(data_digest, "compute", row_number, metric), + subject=CostSubject.OPERATOR, + operation=operation, + hardware=hardware_name, + datatype=datatype, + seconds=milliseconds * 1e-3, + selector=FrozenDict(selector), + provenance=provenance, + metadata=FrozenDict({"upstream_metric": metric}), + ) + ) + return tuple(records) diff --git a/tests/compiler/test_cost_model_providers.py b/tests/compiler/test_cost_model_providers.py new file mode 100644 index 0000000..185f3b6 --- /dev/null +++ b/tests/compiler/test_cost_model_providers.py @@ -0,0 +1,481 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from blueprinting.compiler.analysis import ( + AIConfiguratorPerformanceImporter, + CalibrationMode, + CostQuery, + CostQueryContext, + CostResolver, + CostSubject, + EstimateMethod, + EvidenceProvenance, + HardwareProfile, + LatencyUnit, + PerformanceDatabase, + PerformanceDatabaseProvider, + PerformanceRecord, + RooflineCostProvider, + SimulatorPerformanceImporter, + TabularImportSpec, + VidurProfileImporter, + cost_query_for_inference_task, + estimate_inference_phase, +) +from blueprinting.compiler.analysis.cost import InvalidCostEvidenceError +from blueprinting.compiler.bindings import InferencePhase +from blueprinting.compiler.frozen import FrozenDict +from blueprinting.compiler.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass +from blueprinting.compiler.models import ( + TransformerInferenceExecutionSpec, + TransformerModelSpec, + build_transformer_inference_model_ir, + inference_compilation_session_for, +) +from blueprinting.compiler.passes import PassManager, PassPipeline + +ROOT = Path(__file__).resolve().parents[2] + + +def _hardware(name: str = "fixture-hardware") -> HardwareProfile: + return HardwareProfile.from_mapping( + name, + json.loads((ROOT / "data" / "systems" / "a100_80g.json").read_text(encoding="utf-8")), + datatype="float16", + ) + + +def _provenance(*, digest: str = "fixture-data") -> EvidenceProvenance: + return EvidenceProvenance( + source="fixture-simulator", + source_revision="sim-r1", + importer="fixture-importer-v1", + data_digest=digest, + method=EstimateMethod.SIMULATED, + ) + + +def test_roofline_exposes_components_and_uses_max_bound(): + hardware = _hardware() + provider = RooflineCostProvider( + hardware, + mode=CalibrationMode.PEAK_ONLY, + processing_mode="roofline", + ) + query = CostQuery( + subject=CostSubject.OPERATOR, + operation="gemm", + hardware=hardware.name, + datatype=hardware.datatype, + operations=312_000_000_000_000, + read_bytes=4_096_000_000_000, + engine="matrix", + hardware_revision=hardware.evidence_revision, + ) + + estimate = provider.estimate(query) + + assert estimate.seconds == pytest.approx(2.0) + assert estimate.components["compute_seconds"] == pytest.approx(1.0) + assert estimate.components["memory_seconds"] == pytest.approx(2.0) + assert estimate.components["bottleneck"] == "memory" + assert estimate.method is EstimateMethod.ANALYTICAL + + +def test_database_aggregates_only_one_exact_provenance_group_and_round_trips(): + records = tuple( + PerformanceRecord( + record_id=f"sample-{index}", + subject=CostSubject.OPERATOR, + operation="gemm", + hardware="fixture-hardware", + datatype="float16", + seconds=seconds, + selector=FrozenDict({"m": 8, "n": 16, "k": 32}), + provenance=_provenance(), + ) + for index, seconds in enumerate((0.001, 0.003), start=1) + ) + database = PerformanceDatabase("fixture", records) + restored = PerformanceDatabase.from_json(database.to_json()) + provider = PerformanceDatabaseProvider(restored) + query = CostQuery( + CostSubject.OPERATOR, + "gemm", + "fixture-hardware", + "float16", + engine="matrix", + dimensions=FrozenDict({"m": 8, "n": 16, "k": 32, "semantic_operation": "mlp_up_projection"}), + ) + + result = provider.estimate(query) + + assert restored.revision == database.revision + assert result.seconds == pytest.approx(0.002) + assert result.uncertainty.sample_count == 2 + assert result.uncertainty.standard_deviation_seconds == pytest.approx(0.001) + assert result.raw_record_ids == ("sample-1", "sample-2") + + +def test_ambiguous_database_evidence_is_not_hidden_by_roofline_fallback(): + selector = FrozenDict({"m": 8}) + records = ( + PerformanceRecord( + "one", + CostSubject.OPERATOR, + "gemm", + "fixture-hardware", + "float16", + 0.001, + selector, + _provenance(digest="run-one"), + ), + PerformanceRecord( + "two", + CostSubject.OPERATOR, + "gemm", + "fixture-hardware", + "float16", + 0.002, + selector, + _provenance(digest="run-two"), + ), + ) + resolver = CostResolver( + ( + PerformanceDatabaseProvider(PerformanceDatabase("ambiguous", records)), + RooflineCostProvider(_hardware()), + ) + ) + query = CostQuery( + CostSubject.OPERATOR, + "gemm", + "fixture-hardware", + "float16", + engine="matrix", + dimensions=FrozenDict({"m": 8}), + ) + + with pytest.raises(InvalidCostEvidenceError, match="equally specific"): + resolver.resolve(query) + + +def test_simulator_import_maps_units_and_communication_selectors(tmp_path: Path): + source = tmp_path / "network.csv" + source.write_text( + "run_id,op,dtype,latency_us,message_size,participants\nrun-7,all_reduce,float16,125.5,1048576,8\n", + encoding="utf-8", + ) + spec = TabularImportSpec( + name="network-sim", + subject=CostSubject.COMMUNICATION, + source="network-simulator", + source_revision="network-sim-r7", + method=EstimateMethod.SIMULATED, + latency_column="latency_us", + latency_unit=LatencyUnit.MICROSECONDS, + operation_column="op", + hardware="candidate-lpu", + datatype_column="dtype", + selector_columns=FrozenDict({"message_bytes": "message_size", "participants": "participants"}), + selector_types=FrozenDict({"message_bytes": "int", "participants": "int"}), + record_id_column="run_id", + ) + + database = SimulatorPerformanceImporter.from_file(source, spec) + query = CostQuery( + CostSubject.COMMUNICATION, + "all_reduce", + "candidate-lpu", + "float16", + message_bytes=1_048_576, + participants=8, + ) + result = PerformanceDatabaseProvider(database).estimate(query) + + assert result.seconds == pytest.approx(125.5e-6) + assert result.method is EstimateMethod.SIMULATED + assert result.raw_record_ids == ("run-7",) + + +def test_aiconfigurator_imports_gemm_and_custom_allreduce_schemas(tmp_path: Path): + gemm = tmp_path / "gemm_perf.csv" + gemm.write_text( + "framework,version,device,op_name,kernel_source,gemm_dtype,m,n,k,latency\n" + "VLLM,0.24.0,NVIDIA H100 80GB HBM3,gemm,torch.nn.functional.linear,bfloat16,8,16,32,1.5\n", + encoding="utf-8", + ) + gemm_db = AIConfiguratorPerformanceImporter.from_file( + gemm, + hardware_name="h100-sxm", + source_revision="aic-commit", + ) + gemm_query = CostQuery( + CostSubject.OPERATOR, + "gemm", + "h100-sxm", + "bfloat16", + engine="matrix", + implementation="torch.nn.functional.linear", + runtime="vllm", + runtime_revision="0.24.0", + dimensions=FrozenDict({"m": 8, "n": 16, "k": 32}), + ) + assert PerformanceDatabaseProvider(gemm_db).estimate(gemm_query).seconds == pytest.approx(1.5e-3) + + communication = tmp_path / "custom_allreduce_perf.csv" + communication.write_text( + "framework,version,device,op_name,kernel_source,allreduce_dtype,num_gpus,message_size,latency,backend\n" + "vLLM,0.24.0,NVIDIA H100 80GB HBM3,all_reduce,vLLM_custom_graph,bfloat16,8,1048576,0.25,vllm_graph\n", + encoding="utf-8", + ) + communication_db = AIConfiguratorPerformanceImporter.from_file( + communication, + hardware_name="h100-sxm", + source_revision="aic-commit", + ) + communication_query = CostQuery( + CostSubject.COMMUNICATION, + "all_reduce", + "h100-sxm", + "bfloat16", + message_bytes=1_048_576, + participants=8, + implementation="vLLM_custom_graph", + runtime="vllm", + runtime_revision="0.24.0", + dimensions=FrozenDict({"backend": "vllm_graph"}), + ) + assert PerformanceDatabaseProvider(communication_db).estimate(communication_query).seconds == pytest.approx(0.25e-3) + + +def test_aiconfigurator_parquet_path_uses_the_same_strict_schema(tmp_path: Path): + pandas = pytest.importorskip("pandas") + pytest.importorskip("pyarrow") + source = tmp_path / "gemm_perf.parquet" + pandas.DataFrame( + [ + { + "framework": "SGLang", + "version": "0.5.14", + "device": "NVIDIA H100 80GB HBM3", + "op_name": "gemm", + "kernel_source": "torch.mm", + "gemm_dtype": "bfloat16", + "m": 4, + "n": 8, + "k": 16, + "latency": 0.75, + } + ] + ).to_parquet(source) + + database = AIConfiguratorPerformanceImporter.from_file( + source, + hardware_name="h100-sxm", + source_revision="aic-commit", + ) + + assert len(database.records) == 1 + assert database.records[0].seconds == pytest.approx(0.75e-3) + assert database.records[0].selector["runtime"] == "sglang" + + +@pytest.mark.parametrize( + ("file_name", "upstream_operation", "isl", "step", "phase", "query_tokens", "context_tokens"), + ( + ("context_attention_perf.csv", "context_attention", 128, 0, "prefill", 128, 128), + ("generation_attention_perf.csv", "generation_attention", 128, 7, "decode", 1, 135), + ), +) +def test_aiconfigurator_attention_tables_normalize_phase_and_visible_context( + tmp_path: Path, + file_name: str, + upstream_operation: str, + isl: int, + step: int, + phase: str, + query_tokens: int, + context_tokens: int, +): + source = tmp_path / file_name + source.write_text( + "framework,version,device,op_name,kernel_source,batch_size,isl,num_heads,num_key_value_heads," + "head_dim,beam_width,attn_dtype,kv_cache_dtype,step,window_size,latency\n" + f"VLLM,0.24.0,NVIDIA H100 80GB HBM3,{upstream_operation},vllm_flash_attn_fa3," + f"4,{isl},4,4,128,1,bfloat16,bfloat16,{step},0,2.0\n", + encoding="utf-8", + ) + database = AIConfiguratorPerformanceImporter.from_file( + source, + hardware_name="h100-sxm", + source_revision="aic-commit", + ) + query = CostQuery( + CostSubject.OPERATOR, + "attention_core", + "h100-sxm", + "bfloat16", + engine="matrix", + implementation="vllm_flash_attn_fa3", + runtime="vllm", + runtime_revision="0.24.0", + dimensions=FrozenDict( + { + "semantic_operation": "attention_core", + "phase": phase, + "batch_size": 4, + "query_tokens": query_tokens, + "context_tokens": context_tokens, + "local_attention_heads": 4, + "local_kv_heads": 4, + "head_size": 128, + "beam_width": 1, + "window_size": 0, + "kv_cache_datatype": "bfloat16", + } + ), + ) + + assert PerformanceDatabaseProvider(database).estimate(query).seconds == pytest.approx(2e-3) + + +def test_vidur_import_is_explicit_and_preserves_exact_profile_context(): + profile = ROOT / "data" / "validation" / "vidur" / "phi2_a100_tp1" + database = VidurProfileImporter.from_csv( + attention_csv=profile / "attention.csv", + compute_csv=profile / "mlp.csv", + model_name="microsoft/phi-2", + hardware_name="a100_80g", + source_revision="8383d2935bc62723a212090baa9f98ada206fc14", + ) + query = CostQuery( + CostSubject.OPERATOR, + "attention_core", + "a100_80g", + "float16", + engine="matrix", + implementation="AttentionBackend.FLASH_ATTENTION", + dimensions=FrozenDict( + { + "semantic_operation": "attention_core", + "source_layer": "attention.core", + "phase": "decode", + "model_name": "microsoft/phi-2", + "model_sequence_length": 4096, + "hidden_size": 2560, + "attention_heads": 32, + "kv_heads": 32, + "batch_size": 1, + "query_tokens": 1, + "context_tokens": 33, + "tensor_parallel": 1, + "block_size": 16, + } + ), + ) + + result = PerformanceDatabaseProvider(database).estimate(query) + + assert result.seconds == pytest.approx(9e-6) + assert result.method is EstimateMethod.MEASURED + assert result.source_revision == "8383d2935bc62723a212090baa9f98ada206fc14" + + +def _inference_fixture(): + model = TransformerModelSpec( + name="cost-inference", + hidden_size=64, + feedforward_size=256, + sequence_length=512, + attention_heads=8, + attention_head_size=8, + block_count=8, + ) + execution = TransformerInferenceExecutionSpec( + world_size=4, + tensor_parallel=2, + pipeline_parallel=2, + replicas=1, + datatype="float16", + tensor_parallel_network=0, + pipeline_parallel_network=0, + ) + plan = ( + PassManager() + .run( + PassPipeline.of(DistributeTransformerInferencePass(), PlanTransformerInferencePass()), + build_transformer_inference_model_ir(model), + session=inference_compilation_session_for( + model, + execution, + phase=InferencePhase.DECODE, + batch_size=3, + context_tokens=96, + ), + ) + .ir + ) + return model, execution, plan + + +def test_inference_costing_uses_database_then_explicit_roofline_fallback(): + model, execution, plan = _inference_fixture() + hardware = _hardware() + attention_task = next(task for task in plan.tasks if task.workload.attributes.get("primitive") == "attention_core") + query = cost_query_for_inference_task( + attention_task, + hardware=hardware, + execution=execution, + model=model, + batch_size=3, + query_tokens=1, + context_tokens=96, + context=CostQueryContext(), + ) + record = PerformanceRecord( + "attention-sim-1", + CostSubject.OPERATOR, + "attention_core", + hardware.name, + execution.datatype, + 0.123, + FrozenDict( + { + "semantic_operation": "attention_core", + "context_tokens": 96, + "batch_size": 3, + } + ), + _provenance(), + ) + database_provider = PerformanceDatabaseProvider(PerformanceDatabase("inference", (record,))) + resolver = CostResolver( + ( + database_provider, + RooflineCostProvider(hardware, mode=CalibrationMode.PEAK_ONLY), + ) + ) + + estimate = estimate_inference_phase( + plan, + hardware, + mode=CalibrationMode.PEAK_ONLY, + cost_resolver=resolver, + ) + attention = next(item for item in estimate.tasks if item.invocation.primitive == "attention_core") + fallback = next(item for item in estimate.tasks if item.invocation.primitive == "input_layernorm") + + assert query.operation == "attention_core" + assert attention.total_seconds == pytest.approx(0.123) + assert attention.evidence_provider == database_provider.name + assert attention.evidence_source_revision == "sim-r1" + assert attention.evidence_record_ids == ("attention-sim-1",) + assert attention.evidence_method == "simulated" + assert fallback.evidence_provider == f"roofline:{hardware.name}" + assert fallback.evidence_method == "analytical" + assert estimate.evidence_revisions[resolver.revision] == "cost-resolver-policy" diff --git a/uv.lock b/uv.lock index 882cbaa..cba0196 100644 --- a/uv.lock +++ b/uv.lock @@ -364,6 +364,7 @@ all = [ { name = "mkdocs-static-i18n", extra = ["material"] }, { name = "mkdocstrings", extra = ["python"] }, { name = "plotly" }, + { name = "pyarrow" }, { name = "pymdown-extensions" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -402,6 +403,9 @@ legacy-ui = [ { name = "streamlit" }, { name = "streamlit-extras" }, ] +performance-data = [ + { name = "pyarrow" }, +] [package.dev-dependencies] dev = [ @@ -437,6 +441,8 @@ requires-dist = [ { name = "plotly", marker = "extra == 'all'", specifier = ">=5.0.0" }, { name = "plotly", marker = "extra == 'full'", specifier = ">=5.0.0" }, { name = "psutil", specifier = ">=5.9.0" }, + { name = "pyarrow", marker = "extra == 'all'", specifier = ">=12.0.0" }, + { name = "pyarrow", marker = "extra == 'performance-data'", specifier = ">=12.0.0" }, { name = "pymdown-extensions", marker = "extra == 'all'", specifier = ">=10.21.3,<11.0.0" }, { name = "pymdown-extensions", marker = "extra == 'docs'", specifier = ">=10.21.3,<11.0.0" }, { name = "pytest", marker = "extra == 'all'", specifier = ">=7.0.0" }, @@ -464,7 +470,7 @@ requires-dist = [ { name = "watchdog", marker = "extra == 'all'", specifier = ">=3.0.0" }, { name = "watchdog", marker = "extra == 'dev'", specifier = ">=3.0.0" }, ] -provides-extras = ["all", "dev", "docs", "full", "legacy-ui"] +provides-extras = ["all", "dev", "docs", "full", "legacy-ui", "performance-data"] [package.metadata.requires-dev] dev = [ From 740b96e30f6202b4896cc7725c5f77591072483e Mon Sep 17 00:00:00 2001 From: Reiase Date: Sun, 9 Aug 2026 14:36:44 +0800 Subject: [PATCH 2/6] refactor: promote analysis to top-level package --- docs/design/modules.en.md | 7 ++++--- docs/design/modules.zh.md | 7 ++++--- docs/design/passes/transformer.en.md | 4 ++-- docs/design/passes/transformer.zh.md | 4 ++-- docs/design/performance/providers.en.md | 2 +- docs/design/performance/providers.zh.md | 2 +- docs/experiments/calculon-calibration.en.md | 4 ++-- docs/experiments/calculon-calibration.zh.md | 4 ++-- docs/modeling/inference.en.md | 2 +- docs/modeling/inference.zh.md | 2 +- src/blueprinting/{compiler => }/analysis/__init__.py | 0 .../{compiler => }/analysis/cost/__init__.py | 0 .../{compiler => }/analysis/cost/aiconfigurator.py | 4 ++-- .../{compiler => }/analysis/cost/database.py | 6 ++++-- .../{compiler => }/analysis/cost/importers.py | 4 ++-- .../{compiler => }/analysis/cost/protocol.py | 6 ++++-- .../{compiler => }/analysis/cost/roofline.py | 4 ++-- .../{compiler => }/analysis/cost_model.py | 11 +++++++---- .../{compiler => }/analysis/inference_cost.py | 10 +++++----- .../{compiler => }/analysis/inference_evidence.py | 2 +- .../{compiler => }/analysis/transformer_inference.py | 12 +++++++----- .../{compiler => }/analysis/transformer_workload.py | 8 +++++--- src/blueprinting/{compiler => }/analysis/vidur.py | 6 +++--- src/blueprinting/application/analysis.py | 4 ++-- src/blueprinting/application/inference.py | 2 +- src/blueprinting/compiler/experiments/calculon.py | 4 ++-- src/blueprinting/compiler/experiments/regression.py | 2 +- src/blueprinting/compiler/experiments/vidur.py | 2 +- src/blueprinting/compiler/lowering/transformer.py | 2 +- .../compiler/lowering/transformer_inference.py | 4 ++-- src/blueprinting/workbench/nicegui_ui.py | 2 +- src/blueprinting/workbench/streamlit_ui.py | 2 +- .../test_cost_model_providers.py | 4 ++-- tests/analysis/test_package_boundary.py | 11 +++++++++++ tests/application/test_analysis_service.py | 2 +- tests/compiler/test_calculon_calibration.py | 4 ++-- tests/compiler/test_transformer_inference.py | 2 +- 37 files changed, 91 insertions(+), 67 deletions(-) rename src/blueprinting/{compiler => }/analysis/__init__.py (100%) rename src/blueprinting/{compiler => }/analysis/cost/__init__.py (100%) rename src/blueprinting/{compiler => }/analysis/cost/aiconfigurator.py (99%) rename src/blueprinting/{compiler => }/analysis/cost/database.py (98%) rename src/blueprinting/{compiler => }/analysis/cost/importers.py (99%) rename src/blueprinting/{compiler => }/analysis/cost/protocol.py (98%) rename src/blueprinting/{compiler => }/analysis/cost/roofline.py (98%) rename src/blueprinting/{compiler => }/analysis/cost_model.py (98%) rename src/blueprinting/{compiler => }/analysis/inference_cost.py (98%) rename src/blueprinting/{compiler => }/analysis/inference_evidence.py (98%) rename src/blueprinting/{compiler => }/analysis/transformer_inference.py (96%) rename src/blueprinting/{compiler => }/analysis/transformer_workload.py (99%) rename src/blueprinting/{compiler => }/analysis/vidur.py (99%) rename tests/{compiler => analysis}/test_cost_model_providers.py (99%) create mode 100644 tests/analysis/test_package_boundary.py diff --git a/docs/design/modules.en.md b/docs/design/modules.en.md index 4c14448..8d1d8aa 100644 --- a/docs/design/modules.en.md +++ b/docs/design/modules.en.md @@ -123,7 +123,7 @@ bindings/session ───┘ │ observation / calibration ``` -The current code lives under the historical package path `src/blueprinting/compiler/`. The path describes the implementation technique and remains for compatibility; new frontends, targets, providers, and emitters extend this one semantic foundation rather than creating a parallel analysis stack. +The canonical IR, binding, pass, and lowering infrastructure lives under `src/blueprinting/compiler/`. The analytical subsystem is a sibling package at `src/blueprinting/analysis/`: the compiler materializes explicit workload and plan facts, while analysis evaluates those facts against analytical models and external evidence. Analysis may depend on canonical compiler contracts; callers must not treat cost evidence as an implicit lowering decision. ## Current source map @@ -133,7 +133,8 @@ The current code lives under the historical package path `src/blueprinting/compi | Canonical formal representations (`*IR`) | `compiler/ir/` | Implemented contracts | | Bindings and sessions | `compiler/{bindings,session}.py` | Implemented | | Analysis/transformation transactions | `compiler/passes/base.py` | Implemented | -| Transformer frontend and workload analysis | `compiler/models/`, `compiler/analysis/` | Implemented slice | +| Transformer frontend | `compiler/models/` | Implemented slice | +| Workload and cost analysis | `analysis/` | Implemented slice | | Transformer derivation passes | `compiler/lowering/transformer.py` | Implemented through portable plan | -| Current hardware evidence adapter | `compiler/analysis/cost_model.py` | Implemented slice | +| Current hardware evidence adapter | `analysis/cost_model.py`, `analysis/cost/` | Implemented slice | | Architecture model/search, evidence service, simulation, emission | Accepted boundaries | Planned | diff --git a/docs/design/modules.zh.md b/docs/design/modules.zh.md index 6809556..aa5e4a9 100644 --- a/docs/design/modules.zh.md +++ b/docs/design/modules.zh.md @@ -123,7 +123,7 @@ bindings/session ───┘ │ observation / calibration ``` -当前代码位于历史 package path `src/blueprinting/compiler/`。这个路径描述实现技术,并因兼容性保留;新的 frontend、target、provider 与 emitter 扩展同一语义基础,而不是创建平行 analysis stack。 +Canonical IR、binding、pass 与 lowering 基础设施位于 `src/blueprinting/compiler/`。分析子系统则是同级的 `src/blueprinting/analysis/`:compiler 产出显式 workload 与 plan facts,analysis 再用解析模型和外部证据评估这些事实。Analysis 可以依赖 canonical compiler contract,但调用方不能把 cost evidence 当作隐式 lowering 决策。 ## 当前源码映射 @@ -133,7 +133,8 @@ bindings/session ───┘ │ | Canonical 形式化表示(`*IR`) | `compiler/ir/` | Implemented contracts | | Binding 与 session | `compiler/{bindings,session}.py` | Implemented | | Analysis/transformation transaction | `compiler/passes/base.py` | Implemented | -| Transformer frontend 与 workload analysis | `compiler/models/`、`compiler/analysis/` | Implemented slice | +| Transformer frontend | `compiler/models/` | Implemented slice | +| Workload 与 cost analysis | `analysis/` | Implemented slice | | Transformer derivation pass | `compiler/lowering/transformer.py` | Implemented through portable plan | -| 当前 hardware evidence adapter | `compiler/analysis/cost_model.py` | Implemented slice | +| 当前 hardware evidence adapter | `analysis/cost_model.py`、`analysis/cost/` | Implemented slice | | Architecture model/search、evidence service、simulation、emission | Accepted boundary | Planned | diff --git a/docs/design/passes/transformer.en.md b/docs/design/passes/transformer.en.md index 9d982ee..24a4aad 100644 --- a/docs/design/passes/transformer.en.md +++ b/docs/design/passes/transformer.en.md @@ -87,9 +87,9 @@ The derivation does not compensate for a discrepancy by reading a reference late | Concern | Source | Tests | |---|---|---| | Typed Transformer specifications | `src/blueprinting/compiler/models/transformer.py` | binding and calibration tests | -| Workload algebra | `src/blueprinting/compiler/analysis/transformer_workload.py` | `tests/compiler/test_calculon_calibration.py` | +| Workload algebra | `src/blueprinting/analysis/transformer_workload.py` | `tests/compiler/test_calculon_calibration.py` | | Two derivation passes | `src/blueprinting/compiler/lowering/transformer.py` | canonical representation and calibration tests | | Transaction/checkpoints | `src/blueprinting/compiler/passes/base.py` | `tests/compiler/test_pass_manager.py` | -| Evidence-derived estimates | `src/blueprinting/compiler/analysis/cost_model.py` | calibration tests | +| Evidence-derived estimates | `src/blueprinting/analysis/cost_model.py` | calibration tests | The [Calculon calibration experiment](../../experiments/calculon-calibration.md) is the end-to-end audit of this implemented slice. diff --git a/docs/design/passes/transformer.zh.md b/docs/design/passes/transformer.zh.md index 594b11b..7876935 100644 --- a/docs/design/passes/transformer.zh.md +++ b/docs/design/passes/transformer.zh.md @@ -87,9 +87,9 @@ Observer 可以把这些 facts 与 framework trace 或 reference model 对比并 | 关注点 | 源码 | 测试 | |---|---|---| | 强类型 Transformer specification | `src/blueprinting/compiler/models/transformer.py` | binding 与 calibration tests | -| 工作量代数 | `src/blueprinting/compiler/analysis/transformer_workload.py` | `tests/compiler/test_calculon_calibration.py` | +| 工作量代数 | `src/blueprinting/analysis/transformer_workload.py` | `tests/compiler/test_calculon_calibration.py` | | 两个 derivation pass | `src/blueprinting/compiler/lowering/transformer.py` | canonical representation 与 calibration tests | | 事务与 checkpoint | `src/blueprinting/compiler/passes/base.py` | `tests/compiler/test_pass_manager.py` | -| Evidence-derived estimate | `src/blueprinting/compiler/analysis/cost_model.py` | calibration tests | +| Evidence-derived estimate | `src/blueprinting/analysis/cost_model.py` | calibration tests | [Calculon 校准实验](../../experiments/calculon-calibration.md)是这条已实现纵向切片的端到端审计。 diff --git a/docs/design/performance/providers.en.md b/docs/design/performance/providers.en.md index 2c0cc6a..24a794d 100644 --- a/docs/design/performance/providers.en.md +++ b/docs/design/performance/providers.en.md @@ -68,7 +68,7 @@ Two equally specific but different provenance groups are ambiguous. The provider `SimulatorPerformanceImporter` accepts CSV, JSON/JSONL, and—when the `performance-data` extra is installed—Parquet. A `TabularImportSpec` declares every mapping and the latency unit: ```python -from blueprinting.compiler.analysis import ( +from blueprinting.analysis import ( CostSubject, EstimateMethod, LatencyUnit, diff --git a/docs/design/performance/providers.zh.md b/docs/design/performance/providers.zh.md index 8e8502a..eda8ca1 100644 --- a/docs/design/performance/providers.zh.md +++ b/docs/design/performance/providers.zh.md @@ -68,7 +68,7 @@ Provider 构造时会按 core identity、selector schema 与 typed selector valu `SimulatorPerformanceImporter` 支持 CSV、JSON/JSONL,以及安装 `performance-data` extra 后的 Parquet。`TabularImportSpec` 必须声明所有 mapping 与 latency unit: ```python -from blueprinting.compiler.analysis import ( +from blueprinting.analysis import ( CostSubject, EstimateMethod, LatencyUnit, diff --git a/docs/experiments/calculon-calibration.en.md b/docs/experiments/calculon-calibration.en.md index f4f2123..fa04b39 100644 --- a/docs/experiments/calculon-calibration.en.md +++ b/docs/experiments/calculon-calibration.en.md @@ -149,9 +149,9 @@ The original eight parametrized training regressions remain in `tests/compiler/t Implementation map: - `compiler/models/transformer.py`: typed frontend and execution facts; -- `compiler/analysis/transformer_workload.py`: static operation/byte analysis; +- `analysis/transformer_workload.py`: static operation/byte analysis; - `compiler/lowering/transformer.py`: the two canonical derivation passes; -- `compiler/analysis/cost_model.py`: peak-only and evidence-backed views; +- `analysis/cost_model.py`: peak-only and evidence-backed views; - `compiler/experiments/calculon.py`: oracle adapter, audit, and report. - `compiler/experiments/regression.py`: strict cross-domain baseline gate and diagnostics. diff --git a/docs/experiments/calculon-calibration.zh.md b/docs/experiments/calculon-calibration.zh.md index faae8b7..1134dad 100644 --- a/docs/experiments/calculon-calibration.zh.md +++ b/docs/experiments/calculon-calibration.zh.md @@ -149,9 +149,9 @@ uv run pytest -m baseline_regression tests/regression 实现映射: - `compiler/models/transformer.py`:typed frontend 与 execution facts; -- `compiler/analysis/transformer_workload.py`:静态 operation/byte analysis; +- `analysis/transformer_workload.py`:静态 operation/byte analysis; - `compiler/lowering/transformer.py`:两个 canonical derivation pass; -- `compiler/analysis/cost_model.py`:peak-only 与 evidence-backed view; +- `analysis/cost_model.py`:peak-only 与 evidence-backed view; - `compiler/experiments/calculon.py`:oracle adapter、audit 与 report。 - `compiler/experiments/regression.py`:严格的跨域 baseline gate 与诊断。 diff --git a/docs/modeling/inference.en.md b/docs/modeling/inference.en.md index 27d3654..28a25a9 100644 --- a/docs/modeling/inference.en.md +++ b/docs/modeling/inference.en.md @@ -70,7 +70,7 @@ It is multiplied by the number of blocks in one pipeline stage. Weight storage i `VidurProfileBaseline.from_csv(...)` consumes user-supplied Vidur `attention.csv` and compute/MLP CSV files. The caller must pin an upstream revision, hardware identity, attention backend, and cache block size. The adapter hashes the inputs and identity into a baseline revision, converts Vidur's millisecond medians to seconds, and only returns a reference when model dimensions, maximum sequence length, TP, batch/token shape, phase, backend, block size, and context match exactly. Vidur records decode `kv_cache_size` before the current token is appended; Blueprinting records the visible context after append, so the adapter makes the explicit relation `vidur_kv_cache_size = context_tokens - 1`. ```python -from blueprinting.compiler.analysis import HardwareProfile, VidurProfileBaseline +from blueprinting.analysis import HardwareProfile, VidurProfileBaseline from blueprinting.compiler.bindings import InferencePhase from blueprinting.compiler.experiments import VidurExperimentCase, run_vidur_experiment from blueprinting.compiler.models import TransformerInferenceExecutionSpec, TransformerModelSpec diff --git a/docs/modeling/inference.zh.md b/docs/modeling/inference.zh.md index d651de8..ef2b611 100644 --- a/docs/modeling/inference.zh.md +++ b/docs/modeling/inference.zh.md @@ -70,7 +70,7 @@ mean decode-step model time = decode total / (O-1), when O > 1 `VidurProfileBaseline.from_csv(...)` 读取用户提供的 Vidur `attention.csv` 与 compute/MLP CSV。调用者必须固定 upstream revision、hardware identity、attention backend 与 cache block size。Adapter 把输入文件和 identity 一起哈希为 baseline revision,将 Vidur 的毫秒 median 转为秒;只有 model dimension、maximum sequence length、TP、batch/token shape、phase、backend、block size 与 context 完全匹配时才返回 reference。Vidur 的 decode `kv_cache_size` 表示当前 token 写入前的长度,而 Blueprinting 的 context 表示写入后 attention 可见的长度,因此 adapter 显式使用 `vidur_kv_cache_size = context_tokens - 1`。 ```python -from blueprinting.compiler.analysis import HardwareProfile, VidurProfileBaseline +from blueprinting.analysis import HardwareProfile, VidurProfileBaseline from blueprinting.compiler.bindings import InferencePhase from blueprinting.compiler.experiments import VidurExperimentCase, run_vidur_experiment from blueprinting.compiler.models import TransformerInferenceExecutionSpec, TransformerModelSpec diff --git a/src/blueprinting/compiler/analysis/__init__.py b/src/blueprinting/analysis/__init__.py similarity index 100% rename from src/blueprinting/compiler/analysis/__init__.py rename to src/blueprinting/analysis/__init__.py diff --git a/src/blueprinting/compiler/analysis/cost/__init__.py b/src/blueprinting/analysis/cost/__init__.py similarity index 100% rename from src/blueprinting/compiler/analysis/cost/__init__.py rename to src/blueprinting/analysis/cost/__init__.py diff --git a/src/blueprinting/compiler/analysis/cost/aiconfigurator.py b/src/blueprinting/analysis/cost/aiconfigurator.py similarity index 99% rename from src/blueprinting/compiler/analysis/cost/aiconfigurator.py rename to src/blueprinting/analysis/cost/aiconfigurator.py index 804e19e..2f9ce16 100644 --- a/src/blueprinting/compiler/analysis/cost/aiconfigurator.py +++ b/src/blueprinting/analysis/cost/aiconfigurator.py @@ -13,8 +13,8 @@ from pathlib import Path from typing import Any -from ...codec import content_digest -from ...frozen import FrozenDict +from ...compiler.codec import content_digest +from ...compiler.frozen import FrozenDict from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .importers import read_tabular_rows from .protocol import CostSubject, EstimateMethod diff --git a/src/blueprinting/compiler/analysis/cost/database.py b/src/blueprinting/analysis/cost/database.py similarity index 98% rename from src/blueprinting/compiler/analysis/cost/database.py rename to src/blueprinting/analysis/cost/database.py index 30ce9bd..3df6945 100644 --- a/src/blueprinting/compiler/analysis/cost/database.py +++ b/src/blueprinting/analysis/cost/database.py @@ -8,8 +8,8 @@ from dataclasses import dataclass, field from functools import cached_property -from ...codec import canonical_dumps, canonical_loads, content_digest, record_type -from ...frozen import FrozenDict +from ...compiler.codec import canonical_dumps, canonical_loads, content_digest, record_type +from ...compiler.frozen import FrozenDict from .protocol import ( CostEstimate, CostProvider, @@ -22,6 +22,8 @@ InvalidCostEvidenceError, ) +# Keep the legacy codec namespace as a stable serialized identity. + @record_type("compiler.analysis.cost.provenance.v1") @dataclass(frozen=True) diff --git a/src/blueprinting/compiler/analysis/cost/importers.py b/src/blueprinting/analysis/cost/importers.py similarity index 99% rename from src/blueprinting/compiler/analysis/cost/importers.py rename to src/blueprinting/analysis/cost/importers.py index f3eca27..cc9b5a6 100644 --- a/src/blueprinting/compiler/analysis/cost/importers.py +++ b/src/blueprinting/analysis/cost/importers.py @@ -11,8 +11,8 @@ from pathlib import Path from typing import Any -from ...codec import content_digest -from ...frozen import FrozenDict +from ...compiler.codec import content_digest +from ...compiler.frozen import FrozenDict from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .protocol import CostSubject, EstimateMethod diff --git a/src/blueprinting/compiler/analysis/cost/protocol.py b/src/blueprinting/analysis/cost/protocol.py similarity index 98% rename from src/blueprinting/compiler/analysis/cost/protocol.py rename to src/blueprinting/analysis/cost/protocol.py index 3b6516e..a92db9b 100644 --- a/src/blueprinting/compiler/analysis/cost/protocol.py +++ b/src/blueprinting/analysis/cost/protocol.py @@ -13,8 +13,10 @@ from enum import Enum from typing import Protocol, runtime_checkable -from ...codec import content_digest, enum_type, record_type -from ...frozen import FrozenDict +from ...compiler.codec import content_digest, enum_type, record_type +from ...compiler.frozen import FrozenDict + +# Keep the legacy codec namespace as a stable serialized identity. class CostModelError(RuntimeError): diff --git a/src/blueprinting/compiler/analysis/cost/roofline.py b/src/blueprinting/analysis/cost/roofline.py similarity index 98% rename from src/blueprinting/compiler/analysis/cost/roofline.py rename to src/blueprinting/analysis/cost/roofline.py index 19e036b..9a774d8 100644 --- a/src/blueprinting/compiler/analysis/cost/roofline.py +++ b/src/blueprinting/analysis/cost/roofline.py @@ -2,8 +2,8 @@ from __future__ import annotations -from ...codec import content_digest -from ...frozen import FrozenDict +from ...compiler.codec import content_digest +from ...compiler.frozen import FrozenDict from ..cost_model import CalibrationMode, HardwareProfile from .protocol import ( CostEstimate, diff --git a/src/blueprinting/compiler/analysis/cost_model.py b/src/blueprinting/analysis/cost_model.py similarity index 98% rename from src/blueprinting/compiler/analysis/cost_model.py rename to src/blueprinting/analysis/cost_model.py index 9ee0890..3d74242 100644 --- a/src/blueprinting/compiler/analysis/cost_model.py +++ b/src/blueprinting/analysis/cost_model.py @@ -20,10 +20,10 @@ from enum import Enum from typing import Any -from ..codec import content_digest, enum_type, record_type -from ..frozen import FrozenDict -from ..ir import CollectiveKind, PortablePlanIR -from ..models.transformer import ( +from ..compiler.codec import content_digest, enum_type, record_type +from ..compiler.frozen import FrozenDict +from ..compiler.ir import CollectiveKind, PortablePlanIR +from ..compiler.models.transformer import ( RecomputePolicy, TensorParallelCommunication, TransformerExecutionSpec, @@ -36,6 +36,9 @@ TrainingPhase, ) +# Codec tags are stable wire identities; the legacy namespace survives the +# Python package move so existing snapshots and performance evidence still load. + @enum_type("compiler.analysis.calibration_mode") class CalibrationMode(Enum): diff --git a/src/blueprinting/compiler/analysis/inference_cost.py b/src/blueprinting/analysis/inference_cost.py similarity index 98% rename from src/blueprinting/compiler/analysis/inference_cost.py rename to src/blueprinting/analysis/inference_cost.py index 9f6257b..e3328e0 100644 --- a/src/blueprinting/compiler/analysis/inference_cost.py +++ b/src/blueprinting/analysis/inference_cost.py @@ -4,11 +4,11 @@ from dataclasses import dataclass -from ..bindings import InferencePhase -from ..frozen import FrozenDict -from ..ir import CollectiveKind, PlanBuffer, PlanTask, PortablePlanIR -from ..models.transformer import TransformerModelSpec -from ..models.transformer_inference import TransformerInferenceExecutionSpec +from ..compiler.bindings import InferencePhase +from ..compiler.frozen import FrozenDict +from ..compiler.ir import CollectiveKind, PlanBuffer, PlanTask, PortablePlanIR +from ..compiler.models.transformer import TransformerModelSpec +from ..compiler.models.transformer_inference import TransformerInferenceExecutionSpec from .cost import CostQuery, CostQueryContext, CostResolver, CostSubject from .cost_model import CalibrationMode, HardwareProfile from .inference_evidence import InferenceCostProvider, InferenceEvidenceQuery diff --git a/src/blueprinting/compiler/analysis/inference_evidence.py b/src/blueprinting/analysis/inference_evidence.py similarity index 98% rename from src/blueprinting/compiler/analysis/inference_evidence.py rename to src/blueprinting/analysis/inference_evidence.py index a856526..3086dd3 100644 --- a/src/blueprinting/compiler/analysis/inference_evidence.py +++ b/src/blueprinting/analysis/inference_evidence.py @@ -11,7 +11,7 @@ from dataclasses import dataclass from typing import Protocol, runtime_checkable -from ..bindings import InferencePhase +from ..compiler.bindings import InferencePhase @dataclass(frozen=True) diff --git a/src/blueprinting/compiler/analysis/transformer_inference.py b/src/blueprinting/analysis/transformer_inference.py similarity index 96% rename from src/blueprinting/compiler/analysis/transformer_inference.py rename to src/blueprinting/analysis/transformer_inference.py index e54d695..b37b873 100644 --- a/src/blueprinting/compiler/analysis/transformer_inference.py +++ b/src/blueprinting/analysis/transformer_inference.py @@ -10,13 +10,15 @@ from dataclasses import dataclass -from ..bindings import InferencePhase -from ..codec import record_type -from ..ir import CollectiveKind -from ..models.transformer import TransformerModelSpec -from ..models.transformer_inference import TransformerInferenceExecutionSpec +from ..compiler.bindings import InferencePhase +from ..compiler.codec import record_type +from ..compiler.ir import CollectiveKind +from ..compiler.models.transformer import TransformerModelSpec +from ..compiler.models.transformer_inference import TransformerInferenceExecutionSpec from .transformer_workload import EngineKind, PhaseWork +# Keep the legacy codec namespace as a stable serialized identity. + @record_type("compiler.analysis.inference_invocation.v1") @dataclass(frozen=True) diff --git a/src/blueprinting/compiler/analysis/transformer_workload.py b/src/blueprinting/analysis/transformer_workload.py similarity index 99% rename from src/blueprinting/compiler/analysis/transformer_workload.py rename to src/blueprinting/analysis/transformer_workload.py index 841e41b..4522fb6 100644 --- a/src/blueprinting/compiler/analysis/transformer_workload.py +++ b/src/blueprinting/analysis/transformer_workload.py @@ -11,15 +11,17 @@ from dataclasses import dataclass, replace from enum import Enum -from ..codec import enum_type, record_type -from ..ir import CollectiveKind -from ..models.transformer import ( +from ..compiler.codec import enum_type, record_type +from ..compiler.ir import CollectiveKind +from ..compiler.models.transformer import ( RecomputePolicy, TensorParallelCommunication, TransformerExecutionSpec, TransformerModelSpec, ) +# Keep the legacy codec namespace as a stable serialized identity. + @enum_type("compiler.analysis.engine_kind") class EngineKind(Enum): diff --git a/src/blueprinting/compiler/analysis/vidur.py b/src/blueprinting/analysis/vidur.py similarity index 99% rename from src/blueprinting/compiler/analysis/vidur.py rename to src/blueprinting/analysis/vidur.py index 0969571..1ae4a18 100644 --- a/src/blueprinting/compiler/analysis/vidur.py +++ b/src/blueprinting/analysis/vidur.py @@ -13,9 +13,9 @@ import statistics from pathlib import Path -from ..bindings import InferencePhase -from ..codec import content_digest -from ..frozen import FrozenDict +from ..compiler.bindings import InferencePhase +from ..compiler.codec import content_digest +from ..compiler.frozen import FrozenDict from .cost.database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .cost.protocol import CostSubject, EstimateMethod from .inference_evidence import InferenceEvidenceQuery, InferenceEvidenceResult diff --git a/src/blueprinting/application/analysis.py b/src/blueprinting/application/analysis.py index 9fe5933..e99ba1c 100644 --- a/src/blueprinting/application/analysis.py +++ b/src/blueprinting/application/analysis.py @@ -16,7 +16,7 @@ from itertools import product from typing import TYPE_CHECKING, Any -from blueprinting.compiler.analysis import CalibrationMode, HardwareProfile, estimate_iteration +from blueprinting.analysis import CalibrationMode, HardwareProfile, estimate_iteration from blueprinting.compiler.codec import content_digest from blueprinting.compiler.errors import ( CompilerError, @@ -37,7 +37,7 @@ LOGGER = logging.getLogger(__name__) if TYPE_CHECKING: - from blueprinting.compiler.analysis import InferenceCostProvider + from blueprinting.analysis import InferenceCostProvider from .inference import InferenceAnalysisDraft, InferenceAnalysisOutcome, InferenceAnalysisService diff --git a/src/blueprinting/application/inference.py b/src/blueprinting/application/inference.py index 0d1ba8d..5ac76e7 100644 --- a/src/blueprinting/application/inference.py +++ b/src/blueprinting/application/inference.py @@ -14,7 +14,7 @@ from dataclasses import dataclass, replace from typing import Any -from blueprinting.compiler.analysis import ( +from blueprinting.analysis import ( CalibrationMode, HardwareProfile, InferenceCostProvider, diff --git a/src/blueprinting/compiler/experiments/calculon.py b/src/blueprinting/compiler/experiments/calculon.py index c7a872b..b723459 100644 --- a/src/blueprinting/compiler/experiments/calculon.py +++ b/src/blueprinting/compiler/experiments/calculon.py @@ -21,13 +21,13 @@ from calculon.llm import Llm from calculon.system import System -from ..analysis.cost_model import ( +from ...analysis.cost_model import ( CalibrationMode, HardwareProfile, IterationEstimate, estimate_iteration, ) -from ..analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase +from ...analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase from ..ir import PortablePlanIR from ..lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass from ..models import ( diff --git a/src/blueprinting/compiler/experiments/regression.py b/src/blueprinting/compiler/experiments/regression.py index 75612ad..66865be 100644 --- a/src/blueprinting/compiler/experiments/regression.py +++ b/src/blueprinting/compiler/experiments/regression.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import Any -from ..analysis import HardwareProfile, VidurProfileBaseline +from ...analysis import HardwareProfile, VidurProfileBaseline from ..bindings import InferencePhase from ..models import TransformerInferenceExecutionSpec, TransformerModelSpec from .calculon import CalculonExperimentReport, discover_seqsel_tab5_cases, run_calculon_experiment diff --git a/src/blueprinting/compiler/experiments/vidur.py b/src/blueprinting/compiler/experiments/vidur.py index ee4a6d4..595ae85 100644 --- a/src/blueprinting/compiler/experiments/vidur.py +++ b/src/blueprinting/compiler/experiments/vidur.py @@ -12,7 +12,7 @@ from dataclasses import dataclass from typing import Any -from ..analysis import ( +from ...analysis import ( CalibrationMode, HardwareProfile, InferenceBaseline, diff --git a/src/blueprinting/compiler/lowering/transformer.py b/src/blueprinting/compiler/lowering/transformer.py index 1381c77..7d0b37c 100644 --- a/src/blueprinting/compiler/lowering/transformer.py +++ b/src/blueprinting/compiler/lowering/transformer.py @@ -2,7 +2,7 @@ from __future__ import annotations -from ..analysis.transformer_workload import ( +from ...analysis.transformer_workload import ( EngineKind, PrimitiveInvocation, compile_transformer_block, diff --git a/src/blueprinting/compiler/lowering/transformer_inference.py b/src/blueprinting/compiler/lowering/transformer_inference.py index f8f6151..c3f8c2e 100644 --- a/src/blueprinting/compiler/lowering/transformer_inference.py +++ b/src/blueprinting/compiler/lowering/transformer_inference.py @@ -2,12 +2,12 @@ from __future__ import annotations -from ..analysis.transformer_inference import ( +from ...analysis.transformer_inference import ( InferenceBlockMemoryFacts, InferenceInvocation, compile_transformer_inference_block, ) -from ..analysis.transformer_workload import EngineKind +from ...analysis.transformer_workload import EngineKind from ..axes import BindingAxis from ..bindings import InferencePhase, WorkloadMode from ..frozen import FrozenDict diff --git a/src/blueprinting/workbench/nicegui_ui.py b/src/blueprinting/workbench/nicegui_ui.py index 933d9e6..e99e7ff 100644 --- a/src/blueprinting/workbench/nicegui_ui.py +++ b/src/blueprinting/workbench/nicegui_ui.py @@ -14,6 +14,7 @@ from nicegui import run, ui +from blueprinting.analysis import CalibrationMode from blueprinting.application import ( AnalysisDiagnostic, AnalysisDraft, @@ -23,7 +24,6 @@ SweepReport, SweepRequest, ) -from blueprinting.compiler.analysis import CalibrationMode from .catalog import ConfigCatalog, default_catalog from .nicegui_theme import METRIC_COLORS, WORKBENCH_CSS diff --git a/src/blueprinting/workbench/streamlit_ui.py b/src/blueprinting/workbench/streamlit_ui.py index 6a32372..6b3636d 100644 --- a/src/blueprinting/workbench/streamlit_ui.py +++ b/src/blueprinting/workbench/streamlit_ui.py @@ -10,6 +10,7 @@ import pandas as pd import streamlit as st +from blueprinting.analysis import CalibrationMode from blueprinting.application import ( AnalysisDiagnostic, AnalysisDraft, @@ -19,7 +20,6 @@ SweepReport, SweepRequest, ) -from blueprinting.compiler.analysis import CalibrationMode from .catalog import ConfigCatalog, default_catalog diff --git a/tests/compiler/test_cost_model_providers.py b/tests/analysis/test_cost_model_providers.py similarity index 99% rename from tests/compiler/test_cost_model_providers.py rename to tests/analysis/test_cost_model_providers.py index 185f3b6..b81e24e 100644 --- a/tests/compiler/test_cost_model_providers.py +++ b/tests/analysis/test_cost_model_providers.py @@ -5,7 +5,7 @@ import pytest -from blueprinting.compiler.analysis import ( +from blueprinting.analysis import ( AIConfiguratorPerformanceImporter, CalibrationMode, CostQuery, @@ -26,7 +26,7 @@ cost_query_for_inference_task, estimate_inference_phase, ) -from blueprinting.compiler.analysis.cost import InvalidCostEvidenceError +from blueprinting.analysis.cost import InvalidCostEvidenceError from blueprinting.compiler.bindings import InferencePhase from blueprinting.compiler.frozen import FrozenDict from blueprinting.compiler.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass diff --git a/tests/analysis/test_package_boundary.py b/tests/analysis/test_package_boundary.py new file mode 100644 index 0000000..53e34bc --- /dev/null +++ b/tests/analysis/test_package_boundary.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +import importlib.util + +import blueprinting.analysis as analysis + + +def test_analysis_is_a_top_level_blueprinting_package() -> None: + assert analysis.__name__ == "blueprinting.analysis" + assert importlib.util.find_spec("blueprinting.analysis") is not None + assert importlib.util.find_spec("blueprinting.compiler.analysis") is None diff --git a/tests/application/test_analysis_service.py b/tests/application/test_analysis_service.py index 2ad50b1..4a4c1a9 100644 --- a/tests/application/test_analysis_service.py +++ b/tests/application/test_analysis_service.py @@ -2,8 +2,8 @@ import pickle +from blueprinting.analysis import CalibrationMode from blueprinting.application import AnalysisDraft, BlueprintingService, SweepRequest -from blueprinting.compiler.analysis import CalibrationMode from blueprinting.compiler.frozen import FrozenDict from blueprinting.compiler.models import TransformerModelSpec, build_transformer_model_ir from blueprinting.workbench import default_catalog diff --git a/tests/compiler/test_calculon_calibration.py b/tests/compiler/test_calculon_calibration.py index eb25e37..a2b30d8 100644 --- a/tests/compiler/test_calculon_calibration.py +++ b/tests/compiler/test_calculon_calibration.py @@ -5,8 +5,8 @@ import pytest -from blueprinting.compiler.analysis.cost_model import CalibrationMode, HardwareProfile, estimate_iteration -from blueprinting.compiler.analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase +from blueprinting.analysis.cost_model import CalibrationMode, HardwareProfile, estimate_iteration +from blueprinting.analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase from blueprinting.compiler.experiments import discover_seqsel_tab5_cases, run_calculon_experiment from blueprinting.compiler.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass from blueprinting.compiler.models import ( diff --git a/tests/compiler/test_transformer_inference.py b/tests/compiler/test_transformer_inference.py index 82c2388..eddc624 100644 --- a/tests/compiler/test_transformer_inference.py +++ b/tests/compiler/test_transformer_inference.py @@ -5,7 +5,7 @@ import pytest -from blueprinting.compiler.analysis import ( +from blueprinting.analysis import ( HardwareProfile, InferenceCostProvider, InferenceEvidenceQuery, From 00ff790a6486ed500aa93c9b6c6424aaa558ee44 Mon Sep 17 00:00:00 2001 From: Reiase Date: Sun, 9 Aug 2026 16:36:17 +0800 Subject: [PATCH 3/6] refactor: rename formal compiler to synthesizer --- AGENTS.md | 10 +- README.md | 20 +- ...th.svg => implemented-derivation-path.svg} | 2 +- docs/assets/architecture/pass-transaction.svg | 2 +- docs/contributing/documentation.en.md | 4 +- docs/contributing/documentation.zh.md | 4 +- docs/design/index.en.md | 2 +- docs/design/index.zh.md | 2 +- docs/design/modules.en.md | 16 +- docs/design/modules.zh.md | 16 +- docs/design/passes/index.en.md | 4 +- docs/design/passes/index.zh.md | 4 +- docs/design/passes/transformer.en.md | 14 +- docs/design/passes/transformer.zh.md | 14 +- docs/design/performance/providers.en.md | 2 +- docs/design/performance/providers.zh.md | 2 +- ...tion-model.en.md => synthesis-model.en.md} | 2 +- ...tion-model.zh.md => synthesis-model.zh.md} | 2 +- docs/experiments/calculon-calibration.en.md | 10 +- docs/experiments/calculon-calibration.zh.md | 10 +- docs/experiments/vidur-baseline.en.md | 6 +- docs/experiments/vidur-baseline.zh.md | 6 +- docs/modeling/inference.en.md | 10 +- docs/modeling/inference.zh.md | 10 +- .../adr/0001-synthesizer-package.en.md | 86 +++++ .../adr/0001-synthesizer-package.zh.md | 86 +++++ docs/project/decisions.en.md | 3 +- docs/project/decisions.zh.md | 3 +- docs/project/status.en.md | 6 +- docs/project/status.zh.md | 6 +- examples/calculon_calibration.py | 8 +- examples/calculon_calibration_result.json | 354 +++++++++--------- mkdocs.yml | 4 +- src/blueprinting/analysis/__init__.py | 8 +- .../analysis/cost/aiconfigurator.py | 4 +- src/blueprinting/analysis/cost/database.py | 4 +- src/blueprinting/analysis/cost/importers.py | 4 +- src/blueprinting/analysis/cost/protocol.py | 4 +- src/blueprinting/analysis/cost/roofline.py | 4 +- src/blueprinting/analysis/cost_model.py | 10 +- src/blueprinting/analysis/inference_cost.py | 10 +- .../analysis/inference_evidence.py | 2 +- .../analysis/transformer_inference.py | 12 +- .../analysis/transformer_workload.py | 8 +- src/blueprinting/analysis/vidur.py | 6 +- src/blueprinting/application/analysis.py | 24 +- src/blueprinting/application/inference.py | 40 +- .../{compiler => synthesizer}/__init__.py | 10 +- .../{compiler => synthesizer}/axes.py | 0 .../{compiler => synthesizer}/bindings.py | 10 +- .../{compiler => synthesizer}/codec.py | 42 ++- .../{compiler => synthesizer}/errors.py | 26 +- .../experiments/__init__.py | 2 +- .../experiments/calculon.py | 28 +- .../experiments/regression.py | 2 +- .../experiments/vidur.py | 42 +-- .../{compiler => synthesizer}/expr.py | 0 .../{compiler => synthesizer}/frozen.py | 4 +- .../{compiler => synthesizer}/ids.py | 4 +- .../{compiler => synthesizer}/ir/__init__.py | 0 .../{compiler => synthesizer}/ir/common.py | 4 +- .../ir/concrete_plan.py | 0 .../ir/distributed.py | 0 .../{compiler => synthesizer}/ir/machine.py | 0 .../{compiler => synthesizer}/ir/model.py | 0 .../ir/portable_plan.py | 0 .../lowering/__init__.py | 0 .../lowering/transformer.py | 10 +- .../lowering/transformer_inference.py | 10 +- .../models/__init__.py | 10 +- .../models/transformer.py | 10 +- .../models/transformer_inference.py | 8 +- .../passes/__init__.py | 4 +- .../{compiler => synthesizer}/passes/base.py | 40 +- .../{compiler => synthesizer}/session.py | 18 +- tests/analysis/test_cost_model_providers.py | 14 +- tests/analysis/test_package_boundary.py | 18 +- tests/application/test_analysis_service.py | 4 +- .../regression/test_baseline_quality_gate.py | 2 +- tests/{compiler => synthesizer}/conftest.py | 6 +- .../test_bindings.py | 49 ++- .../test_calculon_calibration.py | 22 +- .../test_canonical_ir.py | 6 +- .../test_pass_manager.py | 62 +-- .../test_transformer_inference.py | 32 +- .../test_verifiers.py | 4 +- 86 files changed, 821 insertions(+), 552 deletions(-) rename docs/assets/architecture/{implemented-compile-path.svg => implemented-derivation-path.svg} (99%) rename docs/design/{compilation-model.en.md => synthesis-model.en.md} (96%) rename docs/design/{compilation-model.zh.md => synthesis-model.zh.md} (96%) create mode 100644 docs/project/adr/0001-synthesizer-package.en.md create mode 100644 docs/project/adr/0001-synthesizer-package.zh.md rename src/blueprinting/{compiler => synthesizer}/__init__.py (90%) rename src/blueprinting/{compiler => synthesizer}/axes.py (100%) rename src/blueprinting/{compiler => synthesizer}/bindings.py (97%) rename src/blueprinting/{compiler => synthesizer}/codec.py (80%) rename src/blueprinting/{compiler => synthesizer}/errors.py (83%) rename src/blueprinting/{compiler => synthesizer}/experiments/__init__.py (94%) rename src/blueprinting/{compiler => synthesizer}/experiments/calculon.py (95%) rename src/blueprinting/{compiler => synthesizer}/experiments/regression.py (99%) rename src/blueprinting/{compiler => synthesizer}/experiments/vidur.py (91%) rename src/blueprinting/{compiler => synthesizer}/expr.py (100%) rename src/blueprinting/{compiler => synthesizer}/frozen.py (96%) rename src/blueprinting/{compiler => synthesizer}/ids.py (96%) rename src/blueprinting/{compiler => synthesizer}/ir/__init__.py (100%) rename src/blueprinting/{compiler => synthesizer}/ir/common.py (99%) rename src/blueprinting/{compiler => synthesizer}/ir/concrete_plan.py (100%) rename src/blueprinting/{compiler => synthesizer}/ir/distributed.py (100%) rename src/blueprinting/{compiler => synthesizer}/ir/machine.py (100%) rename src/blueprinting/{compiler => synthesizer}/ir/model.py (100%) rename src/blueprinting/{compiler => synthesizer}/ir/portable_plan.py (100%) rename src/blueprinting/{compiler => synthesizer}/lowering/__init__.py (100%) rename src/blueprinting/{compiler => synthesizer}/lowering/transformer.py (97%) rename src/blueprinting/{compiler => synthesizer}/lowering/transformer_inference.py (98%) rename src/blueprinting/{compiler => synthesizer}/models/__init__.py (75%) rename src/blueprinting/{compiler => synthesizer}/models/transformer.py (98%) rename src/blueprinting/{compiler => synthesizer}/models/transformer_inference.py (98%) rename src/blueprinting/{compiler => synthesizer}/passes/__init__.py (95%) rename src/blueprinting/{compiler => synthesizer}/passes/base.py (94%) rename src/blueprinting/{compiler => synthesizer}/session.py (81%) rename tests/{compiler => synthesizer}/conftest.py (99%) rename tests/{compiler => synthesizer}/test_bindings.py (57%) rename tests/{compiler => synthesizer}/test_calculon_calibration.py (85%) rename tests/{compiler => synthesizer}/test_canonical_ir.py (94%) rename tests/{compiler => synthesizer}/test_pass_manager.py (77%) rename tests/{compiler => synthesizer}/test_transformer_inference.py (90%) rename tests/{compiler => synthesizer}/test_verifiers.py (97%) diff --git a/AGENTS.md b/AGENTS.md index bc7afad..ae4f1d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,7 +94,7 @@ Pass pipeline 依据声明式 contract 编排,不得依赖具体 Python pass Binding 分为独立维度:workload、strategy、target、deployment、calibration。 -- workload/strategy 可以逐步特化,但必须显式记录在 typed derivation context;当前代码名为 `CompilationSession`; +- workload/strategy 可以逐步特化,但必须显式记录在 typed derivation context;当前代码名为 `SynthesisSession`; - target/deployment 只能在 portable plan 之后进入; - 同一个 `PortablePlanIR` 必须能绑定到多个实质不同的硬件目标; - target 变化不得改变 `ModelIR`、`DistributedTaskIR` 或 `PortablePlanIR` digest; @@ -142,7 +142,8 @@ handling。若 LPU 的 issue cycle/slot 具有 correctness 含义,它在 targe ## 9. 当前实现边界 -当前形式化分析实现仍位于历史 package path `src/blueprinting/compiler/`。该路径为兼容性保留,不定义产品架构。已经实现: +当前 canonical 表示与形式化推导机制位于 `src/blueprinting/synthesizer/`,分析与证据评估位于同级 +`src/blueprinting/analysis/`。Synthesizer 表示 formal plan synthesis 的实现边界,不是产品身份、RTL 综合器或独立 Compiler 组件。已经实现: - 五层 canonical IR 的 immutable schema、serialization 和 structural verifier;其中后两层仍是 experimental contract; - stable ID、lineage、typed scalar expression、binding/session; @@ -162,7 +163,10 @@ handling。若 LPU 的 issue cycle/slot 具有 correctness 含义,它在 targe ## 10. 代码与仓库规则 -- 新形式化表示、推导与分析代码在 package 重命名 ADR 通过前进入 `src/blueprinting/compiler/` 对应边界;不得新建平行表示栈。 +- 新 canonical 表示与推导代码进入 `src/blueprinting/synthesizer/` 对应边界,cost/evidence analysis 进入 + `src/blueprinting/analysis/`;不得新建平行表示栈。 +- `blueprinting.compiler` Python path 已硬切删除;历史 `compiler.*` canonical codec tag 作为 wire identity 保留, + 未经迁移 ADR 不得改写。 - IR 对象默认 frozen;语义字段使用 typed dataclass/enum/ID,不使用自由字典代替 contract。 - 所有公共 derivation/transformation 和 verifier 必须有 positive、negative、round-trip 与 lineage 测试。 - Python 最低版本为 3.10;不得使用只在更高版本解析的语法,除非先更新 packaging contract。 diff --git a/README.md b/README.md index a120786..2716746 100644 --- a/README.md +++ b/README.md @@ -80,17 +80,17 @@ pip install -e ".[dev,docs]" ## Build the current Transformer workload blueprint ```python -from blueprinting.compiler.lowering import ( +from blueprinting.synthesizer.lowering import ( DistributeTransformerTrainingPass, PlanTransformerTrainingPass, ) -from blueprinting.compiler.models import ( +from blueprinting.synthesizer.models import ( TransformerExecutionSpec, TransformerModelSpec, build_transformer_model_ir, - compilation_session_for, + synthesis_session_for, ) -from blueprinting.compiler.passes import PassManager, PassPipeline +from blueprinting.synthesizer.passes import PassManager, PassPipeline model = TransformerModelSpec.from_mapping("gpt3-175B", model_config) execution = TransformerExecutionSpec.from_mapping(execution_config) @@ -102,7 +102,7 @@ result = PassManager().run( PlanTransformerTrainingPass(), ), source, - session=compilation_session_for(model, execution), + session=synthesis_session_for(model, execution), ) portable_plan = result.ir @@ -152,24 +152,24 @@ Calculon remains an adjacent calibration utility and does not participate in the ## Repository layout ```text -src/blueprinting/compiler/ +src/blueprinting/synthesizer/ ├── ir/ # five canonical IR contracts ├── models/ # typed semantic frontends ├── lowering/ # staged derivation passes -├── analysis/ # exact workload and derived cost analyses ├── experiments/ # reproducible validation experiments ├── passes/ # transformation contracts and manager └── session.py # explicit bindings and typed derivation context +src/blueprinting/analysis/ # exact workload and evidence-backed cost analyses src/blueprinting/application/ # framework-neutral analysis service src/blueprinting/workbench/ # NiceGUI workbench and legacy presentation adapters -tests/compiler/ # current formal-representation and calibration tests +tests/synthesizer/ # current formal-representation and calibration tests docs/ # bilingual MkDocs design, reference, experiment, and project documentation ``` -The `compiler` package path and names such as `CompilationSession` are current implementation identifiers retained -for compatibility; they do not define the product architecture. +The `synthesizer` package owns canonical representations and verified derivation mechanics. The name describes +formal plan synthesis—not RTL synthesis, a standalone Compiler product, or Blueprinting's top-level identity. ## Development diff --git a/docs/assets/architecture/implemented-compile-path.svg b/docs/assets/architecture/implemented-derivation-path.svg similarity index 99% rename from docs/assets/architecture/implemented-compile-path.svg rename to docs/assets/architecture/implemented-derivation-path.svg index fb579a2..cc302a6 100644 --- a/docs/assets/architecture/implemented-compile-path.svg +++ b/docs/assets/architecture/implemented-derivation-path.svg @@ -48,7 +48,7 @@ build_transformer_model_ir() one target-neutral decoder_training op - CompilationSession + SynthesisSession WorkloadBinding + StrategyBinding NO target · deployment · calibration binding diff --git a/docs/assets/architecture/pass-transaction.svg b/docs/assets/architecture/pass-transaction.svg index e2a4bd8..a4e3eb7 100644 --- a/docs/assets/architecture/pass-transaction.svg +++ b/docs/assets/architecture/pass-transaction.svg @@ -105,7 +105,7 @@ Content-addressed AnalysisStore - Address = (IR digest, AnalysisKey, CompilationSession fingerprint) + Address = (IR digest, AnalysisKey, SynthesisSession fingerprint) • preserved analyses are copied only to the verified output digest • non-canonical, duplicate, or undeclared products reject the pass diff --git a/docs/contributing/documentation.en.md b/docs/contributing/documentation.en.md index 1bbfb7e..b219b59 100644 --- a/docs/contributing/documentation.en.md +++ b/docs/contributing/documentation.en.md @@ -50,10 +50,10 @@ This follows the plugin's [suffix-structure guidance](https://ultrabug.github.io `mkdocs.yml` and all internal Markdown links use the language-neutral canonical path: ```markdown -[Derivation and verification model](../design/compilation-model.md) +[Derivation and verification model](../design/synthesis-model.md) ``` -Never link to `compilation-model.en.md`, `compilation-model.zh.md`, or a generated `/zh/` URL. The i18n plugin resolves the canonical path for the active locale and keeps the language selector aligned. +Never link to `synthesis-model.en.md`, `synthesis-model.zh.md`, or a generated `/zh/` URL. The i18n plugin resolves the canonical path for the active locale and keeps the language selector aligned. External links use ordinary absolute HTTPS URLs. Shared SVGs use relative paths. A language-specific asset should use the same `.en`/`.zh` pairing convention and be justified; diagrams should prefer language-neutral labels where practical. diff --git a/docs/contributing/documentation.zh.md b/docs/contributing/documentation.zh.md index 052cad7..4ade35b 100644 --- a/docs/contributing/documentation.zh.md +++ b/docs/contributing/documentation.zh.md @@ -50,10 +50,10 @@ Landing page 从硬件决策、探索闭环、证据阶梯、能力状态与读 `mkdocs.yml` 和所有站内 Markdown link 都使用 language-neutral canonical path: ```markdown -[推导与验证模型](../design/compilation-model.md) +[推导与验证模型](../design/synthesis-model.md) ``` -禁止链接到 `compilation-model.en.md`、`compilation-model.zh.md` 或 generated `/zh/` URL。i18n plugin 会为 active locale 解析 canonical path,并保持 language selector 对齐。 +禁止链接到 `synthesis-model.en.md`、`synthesis-model.zh.md` 或 generated `/zh/` URL。i18n plugin 会为 active locale 解析 canonical path,并保持 language selector 对齐。 外部链接使用普通 absolute HTTPS URL。共享 SVG 使用相对路径。Language-specific asset 应使用相同 `.en`/`.zh` pairing convention 并说明必要性;在可行时,diagram 应偏好 language-neutral label。 diff --git a/docs/design/index.en.md b/docs/design/index.en.md index 3ec0319..53f128d 100644 --- a/docs/design/index.en.md +++ b/docs/design/index.en.md @@ -128,7 +128,7 @@ First-class architecture blueprints, target/resource binding, concrete schedulin ## Reading the formal analysis foundations -- [Derivation and verification model](compilation-model.md) formalizes state, binding, proof obligations, automated analyses, and the concrete abstract machine. +- [Derivation and verification model](synthesis-model.md) formalizes state, binding, proof obligations, automated analyses, and the concrete abstract machine. - [Timeline staging path](timeline-path.md) separates command plans, predictive timelines, prescriptive timing, and LPU backend evolution. - [Golden derivation walkthrough](walkthrough.md) follows one Transformer fragment through every representation. - [Analysis module architecture](modules.md) assigns Python ownership and extension boundaries. diff --git a/docs/design/index.zh.md b/docs/design/index.zh.md index dd55e1a..8ad2c73 100644 --- a/docs/design/index.zh.md +++ b/docs/design/index.zh.md @@ -128,7 +128,7 @@ First-class architecture blueprint、target/resource binding、concrete scheduli ## 如何阅读形式化分析基础 -- [推导与验证模型](compilation-model.md)形式化 state、binding、proof obligation、automated analysis 与 concrete abstract machine。 +- [推导与验证模型](synthesis-model.md)形式化 state、binding、proof obligation、automated analysis 与 concrete abstract machine。 - [Timeline 阶段路径](timeline-path.md)区分 command plan、预测时间线、强制时序与 LPU backend 演进。 - [完整推导示例](walkthrough.md)展示一个 Transformer fragment 穿过所有 representation。 - [分析模块架构](modules.md)定义 Python ownership 与 extension boundary。 diff --git a/docs/design/modules.en.md b/docs/design/modules.en.md index 8d1d8aa..1cda4dd 100644 --- a/docs/design/modules.en.md +++ b/docs/design/modules.en.md @@ -24,7 +24,7 @@ result.bottlenecks result.sensitivity ``` -Internally, each candidate creates an immutable typed derivation context for workload mapping, architecture binding, and analysis addressing. The current implementation names this object `CompilationSession`; that class and its workload/strategy bindings are implemented, while `ExplorationSession` and the end-to-end product facade are planned. Global mutable configuration is forbidden because it would invalidate experiment reproducibility. +Internally, each candidate creates an immutable typed derivation context for workload mapping, architecture binding, and analysis addressing. The current implementation names this object `SynthesisSession`; that class and its workload/strategy bindings are implemented, while `ExplorationSession` and the end-to-end product facade are planned. Global mutable configuration is forbidden because it would invalidate experiment reproducibility. ## Frontends @@ -123,18 +123,18 @@ bindings/session ───┘ │ observation / calibration ``` -The canonical IR, binding, pass, and lowering infrastructure lives under `src/blueprinting/compiler/`. The analytical subsystem is a sibling package at `src/blueprinting/analysis/`: the compiler materializes explicit workload and plan facts, while analysis evaluates those facts against analytical models and external evidence. Analysis may depend on canonical compiler contracts; callers must not treat cost evidence as an implicit lowering decision. +The canonical IR, binding, pass, and lowering infrastructure lives under `src/blueprinting/synthesizer/`. The analytical subsystem is a sibling package at `src/blueprinting/analysis/`: the synthesizer materializes explicit workload and plan facts, while analysis evaluates those facts against analytical models and external evidence. Analysis may depend on canonical synthesis contracts; callers must not treat cost evidence as an implicit lowering decision. ## Current source map | Concern | Source | Status | |---|---|---| -| IDs, expressions, codec, frozen values | `compiler/{ids,expr,codec,frozen}.py` | Implemented | -| Canonical formal representations (`*IR`) | `compiler/ir/` | Implemented contracts | -| Bindings and sessions | `compiler/{bindings,session}.py` | Implemented | -| Analysis/transformation transactions | `compiler/passes/base.py` | Implemented | -| Transformer frontend | `compiler/models/` | Implemented slice | +| IDs, expressions, codec, frozen values | `synthesizer/{ids,expr,codec,frozen}.py` | Implemented | +| Canonical formal representations (`*IR`) | `synthesizer/ir/` | Implemented contracts | +| Bindings and sessions | `synthesizer/{bindings,session}.py` | Implemented | +| Analysis/transformation transactions | `synthesizer/passes/base.py` | Implemented | +| Transformer frontend | `synthesizer/models/` | Implemented slice | | Workload and cost analysis | `analysis/` | Implemented slice | -| Transformer derivation passes | `compiler/lowering/transformer.py` | Implemented through portable plan | +| Transformer derivation passes | `synthesizer/lowering/transformer.py` | Implemented through portable plan | | Current hardware evidence adapter | `analysis/cost_model.py`, `analysis/cost/` | Implemented slice | | Architecture model/search, evidence service, simulation, emission | Accepted boundaries | Planned | diff --git a/docs/design/modules.zh.md b/docs/design/modules.zh.md index aa5e4a9..9947d74 100644 --- a/docs/design/modules.zh.md +++ b/docs/design/modules.zh.md @@ -24,7 +24,7 @@ result.bottlenecks result.sensitivity ``` -内部会为每个 candidate 创建 immutable typed derivation context,用于 workload mapping、architecture binding 与 analysis addressing。当前实现把这个对象命名为 `CompilationSession`;该 class 及其 workload/strategy binding 已实现,而 `ExplorationSession` 与 end-to-end product facade 仍为 planned。Global mutable configuration 被禁止,因为它会破坏 experiment reproducibility。 +内部会为每个 candidate 创建 immutable typed derivation context,用于 workload mapping、architecture binding 与 analysis addressing。当前实现把这个对象命名为 `SynthesisSession`;该 class 及其 workload/strategy binding 已实现,而 `ExplorationSession` 与 end-to-end product facade 仍为 planned。Global mutable configuration 被禁止,因为它会破坏 experiment reproducibility。 ## Frontend @@ -123,18 +123,18 @@ bindings/session ───┘ │ observation / calibration ``` -Canonical IR、binding、pass 与 lowering 基础设施位于 `src/blueprinting/compiler/`。分析子系统则是同级的 `src/blueprinting/analysis/`:compiler 产出显式 workload 与 plan facts,analysis 再用解析模型和外部证据评估这些事实。Analysis 可以依赖 canonical compiler contract,但调用方不能把 cost evidence 当作隐式 lowering 决策。 +Canonical IR、binding、pass 与 lowering 基础设施位于 `src/blueprinting/synthesizer/`。分析子系统则是同级的 `src/blueprinting/analysis/`:synthesizer 产出显式 workload 与 plan facts,analysis 再用解析模型和外部证据评估这些事实。Analysis 可以依赖 canonical synthesis contract,但调用方不能把 cost evidence 当作隐式 lowering 决策。 ## 当前源码映射 | 关注点 | 源码 | 状态 | |---|---|---| -| ID、expression、codec、frozen value | `compiler/{ids,expr,codec,frozen}.py` | Implemented | -| Canonical 形式化表示(`*IR`) | `compiler/ir/` | Implemented contracts | -| Binding 与 session | `compiler/{bindings,session}.py` | Implemented | -| Analysis/transformation transaction | `compiler/passes/base.py` | Implemented | -| Transformer frontend | `compiler/models/` | Implemented slice | +| ID、expression、codec、frozen value | `synthesizer/{ids,expr,codec,frozen}.py` | Implemented | +| Canonical 形式化表示(`*IR`) | `synthesizer/ir/` | Implemented contracts | +| Binding 与 session | `synthesizer/{bindings,session}.py` | Implemented | +| Analysis/transformation transaction | `synthesizer/passes/base.py` | Implemented | +| Transformer frontend | `synthesizer/models/` | Implemented slice | | Workload 与 cost analysis | `analysis/` | Implemented slice | -| Transformer derivation pass | `compiler/lowering/transformer.py` | Implemented through portable plan | +| Transformer derivation pass | `synthesizer/lowering/transformer.py` | Implemented through portable plan | | 当前 hardware evidence adapter | `analysis/cost_model.py`、`analysis/cost/` | Implemented slice | | Architecture model/search、evidence service、simulation、emission | Accepted boundary | Planned | diff --git a/docs/design/passes/index.en.md b/docs/design/passes/index.en.md index 7a26a32..0c6ae04 100644 --- a/docs/design/passes/index.en.md +++ b/docs/design/passes/index.en.md @@ -47,7 +47,7 @@ An exception, verifier failure, observer rejection, or undeclared analysis produ Analyses are addressed by: ```text -(IR digest, AnalysisKey, CompilationSession fingerprint) +(IR digest, AnalysisKey, SynthesisSession fingerprint) ``` A pass lists every required, preserved, and produced analysis. Preserved products are copied only to the verified output digest. Undeclared products and noncanonical addresses fail the transaction. @@ -86,4 +86,4 @@ Every production analysis or transformation design must include: ## Current implementation -The repository implements `SchemaRange`, `PassContract`, `PassPipeline`, `PassManager`, content-addressed `AnalysisStore`, pass records, checkpoints, and observers in `src/blueprinting/compiler/passes/base.py`. Contract and failure behavior are covered by `tests/compiler/test_pass_manager.py`. +The repository implements `SchemaRange`, `PassContract`, `PassPipeline`, `PassManager`, content-addressed `AnalysisStore`, pass records, checkpoints, and observers in `src/blueprinting/synthesizer/passes/base.py`. Contract and failure behavior are covered by `tests/synthesizer/test_pass_manager.py`. diff --git a/docs/design/passes/index.zh.md b/docs/design/passes/index.zh.md index c7dae92..3a68f4c 100644 --- a/docs/design/passes/index.zh.md +++ b/docs/design/passes/index.zh.md @@ -47,7 +47,7 @@ Exception、verifier failure、observer rejection 或 undeclared analysis produc Analysis 地址为: ```text -(IR digest, AnalysisKey, CompilationSession fingerprint) +(IR digest, AnalysisKey, SynthesisSession fingerprint) ``` Pass 列出所有 required、preserved 和 produced analysis。Preserved product 只复制到 verified output digest。Undeclared product 和 noncanonical address 会使 transaction 失败。 @@ -86,4 +86,4 @@ Search pass 可以具有 seed 和 budget。Candidate order、pruning 和 rejecti ## 当前实现 -仓库在 `src/blueprinting/compiler/passes/base.py` 中实现了 `SchemaRange`、`PassContract`、`PassPipeline`、`PassManager`、content-addressed `AnalysisStore`、pass record、checkpoint 和 observer。Contract 与 failure behavior 由 `tests/compiler/test_pass_manager.py` 覆盖。 +仓库在 `src/blueprinting/synthesizer/passes/base.py` 中实现了 `SchemaRange`、`PassContract`、`PassPipeline`、`PassManager`、content-addressed `AnalysisStore`、pass record、checkpoint 和 observer。Contract 与 failure behavior 由 `tests/synthesizer/test_pass_manager.py` 覆盖。 diff --git a/docs/design/passes/transformer.en.md b/docs/design/passes/transformer.en.md index 24a4aad..5bd1793 100644 --- a/docs/design/passes/transformer.en.md +++ b/docs/design/passes/transformer.en.md @@ -2,7 +2,7 @@ The implemented Transformer slice is deliberately narrow and auditable: it imports one typed decoder-training workload, formally derives one local tensor-parallel block, and preserves exact work in a target-neutral portable plan. It validates the first half of the analysis architecture without pretending that target scheduling already exists. -![Implemented Transformer workload-derivation path](../../assets/architecture/implemented-compile-path.svg) +![Implemented Transformer workload-derivation path](../../assets/architecture/implemented-derivation-path.svg) ## Scope and boundary @@ -23,13 +23,13 @@ This slice currently models decoder-only training at block scope. Full-model PP/ ## Typed semantic import -`TransformerModelSpec` owns dimensions and model semantics. `TransformerExecutionSpec` owns micro-batching, TP/PP/DP, recomputation, datatype, and tensor-parallel communication mode. `compilation_session_for()` turns those execution choices into explicit workload and strategy bindings. +`TransformerModelSpec` owns dimensions and model semantics. `TransformerExecutionSpec` owns micro-batching, TP/PP/DP, recomputation, datatype, and tensor-parallel communication mode. `synthesis_session_for()` turns those execution choices into explicit workload and strategy bindings. The importer rejects invalid dimensions, head divisibility, parallel topology, and inconsistent workload facts before a pass runs. `build_transformer_model_ir()` then creates a coarse, target-neutral `transformer.decoder_training` operation. No target name, peak rate, kernel ID, or latency enters this snapshot. ## Static workload derivation -`compile_transformer_block()` decomposes a block into typed `PrimitiveInvocation` records. Each invocation has a phase, engine class, exact operations, exact read/write bytes, and—when applicable—collective kind and logical message bytes. +`derive_transformer_block()` decomposes a block into typed `PrimitiveInvocation` records. Each invocation has a phase, engine class, exact operations, exact read/write bytes, and—when applicable—collective kind and logical message bytes. The analysis follows data dependencies rather than fitted ratios. For a linear layer `Y[M,K] = X[M,N] x W[N,K]`, forward, activation-gradient, and weight-gradient work are three explicit matrix multiplications. Attention, normalization, activation, dropout, residual, and optimizer work are represented separately. @@ -86,10 +86,10 @@ The derivation does not compensate for a discrepancy by reading a reference late | Concern | Source | Tests | |---|---|---| -| Typed Transformer specifications | `src/blueprinting/compiler/models/transformer.py` | binding and calibration tests | -| Workload algebra | `src/blueprinting/analysis/transformer_workload.py` | `tests/compiler/test_calculon_calibration.py` | -| Two derivation passes | `src/blueprinting/compiler/lowering/transformer.py` | canonical representation and calibration tests | -| Transaction/checkpoints | `src/blueprinting/compiler/passes/base.py` | `tests/compiler/test_pass_manager.py` | +| Typed Transformer specifications | `src/blueprinting/synthesizer/models/transformer.py` | binding and calibration tests | +| Workload algebra | `src/blueprinting/analysis/transformer_workload.py` | `tests/synthesizer/test_calculon_calibration.py` | +| Two derivation passes | `src/blueprinting/synthesizer/lowering/transformer.py` | canonical representation and calibration tests | +| Transaction/checkpoints | `src/blueprinting/synthesizer/passes/base.py` | `tests/synthesizer/test_pass_manager.py` | | Evidence-derived estimates | `src/blueprinting/analysis/cost_model.py` | calibration tests | The [Calculon calibration experiment](../../experiments/calculon-calibration.md) is the end-to-end audit of this implemented slice. diff --git a/docs/design/passes/transformer.zh.md b/docs/design/passes/transformer.zh.md index 7876935..3d4a26f 100644 --- a/docs/design/passes/transformer.zh.md +++ b/docs/design/passes/transformer.zh.md @@ -2,7 +2,7 @@ 当前已经实现的 Transformer 纵向切片刻意保持窄而可审计:它导入一个强类型 decoder training 工作负载,形式化推导一个本地 tensor-parallel block,并把精确工作量保存在 target-neutral portable plan 中。这条路径验证了分析架构的前半段,但不会把尚未完成的 target scheduling 描述成已实现能力。 -![已经实现的 Transformer 工作负载推导路径](../../assets/architecture/implemented-compile-path.svg) +![已经实现的 Transformer 工作负载推导路径](../../assets/architecture/implemented-derivation-path.svg) ## 范围与边界 @@ -23,13 +23,13 @@ TransformerModelSpec + TransformerExecutionSpec ## 强类型语义导入 -`TransformerModelSpec` 拥有模型维度与语义,`TransformerExecutionSpec` 拥有 micro-batching、TP/PP/DP、重计算、数据类型和 tensor-parallel 通信模式。`compilation_session_for()` 把这些执行选择转换成显式 workload 与 strategy binding。 +`TransformerModelSpec` 拥有模型维度与语义,`TransformerExecutionSpec` 拥有 micro-batching、TP/PP/DP、重计算、数据类型和 tensor-parallel 通信模式。`synthesis_session_for()` 把这些执行选择转换成显式 workload 与 strategy binding。 Importer 会在 pass 运行前拒绝非法维度、head 不可整除、错误并行拓扑以及互相矛盾的 workload facts。随后 `build_transformer_model_ir()` 创建一个粗粒度、target-neutral 的 `transformer.decoder_training` operation。这个 snapshot 中不存在 target 名称、峰值性能、kernel ID 或 latency。 ## 静态工作量推导 -`compile_transformer_block()` 把一个 block 分解为强类型 `PrimitiveInvocation`。每个 invocation 都带有 phase、engine class、精确 operations、精确 read/write bytes;如果它是 collective,还会带有 collective kind 和逻辑 message bytes。 +`derive_transformer_block()` 把一个 block 分解为强类型 `PrimitiveInvocation`。每个 invocation 都带有 phase、engine class、精确 operations、精确 read/write bytes;如果它是 collective,还会带有 collective kind 和逻辑 message bytes。 分析遵循数据依赖,而不是拟合比例。对于线性层 `Y[M,K] = X[M,N] x W[N,K]`,forward、activation-gradient 和 weight-gradient 是三个显式矩阵乘。Attention、normalization、activation、dropout、residual 与 optimizer work 也分别表示。 @@ -86,10 +86,10 @@ Observer 可以把这些 facts 与 framework trace 或 reference model 对比并 | 关注点 | 源码 | 测试 | |---|---|---| -| 强类型 Transformer specification | `src/blueprinting/compiler/models/transformer.py` | binding 与 calibration tests | -| 工作量代数 | `src/blueprinting/analysis/transformer_workload.py` | `tests/compiler/test_calculon_calibration.py` | -| 两个 derivation pass | `src/blueprinting/compiler/lowering/transformer.py` | canonical representation 与 calibration tests | -| 事务与 checkpoint | `src/blueprinting/compiler/passes/base.py` | `tests/compiler/test_pass_manager.py` | +| 强类型 Transformer specification | `src/blueprinting/synthesizer/models/transformer.py` | binding 与 calibration tests | +| 工作量代数 | `src/blueprinting/analysis/transformer_workload.py` | `tests/synthesizer/test_calculon_calibration.py` | +| 两个 derivation pass | `src/blueprinting/synthesizer/lowering/transformer.py` | canonical representation 与 calibration tests | +| 事务与 checkpoint | `src/blueprinting/synthesizer/passes/base.py` | `tests/synthesizer/test_pass_manager.py` | | Evidence-derived estimate | `src/blueprinting/analysis/cost_model.py` | calibration tests | [Calculon 校准实验](../../experiments/calculon-calibration.md)是这条已实现纵向切片的端到端审计。 diff --git a/docs/design/performance/providers.en.md b/docs/design/performance/providers.en.md index 24a794d..bbfd981 100644 --- a/docs/design/performance/providers.en.md +++ b/docs/design/performance/providers.en.md @@ -75,7 +75,7 @@ from blueprinting.analysis import ( SimulatorPerformanceImporter, TabularImportSpec, ) -from blueprinting.compiler.frozen import FrozenDict +from blueprinting.synthesizer.frozen import FrozenDict spec = TabularImportSpec( name="noc-sim-r7", diff --git a/docs/design/performance/providers.zh.md b/docs/design/performance/providers.zh.md index eda8ca1..e4d7716 100644 --- a/docs/design/performance/providers.zh.md +++ b/docs/design/performance/providers.zh.md @@ -75,7 +75,7 @@ from blueprinting.analysis import ( SimulatorPerformanceImporter, TabularImportSpec, ) -from blueprinting.compiler.frozen import FrozenDict +from blueprinting.synthesizer.frozen import FrozenDict spec = TabularImportSpec( name="noc-sim-r7", diff --git a/docs/design/compilation-model.en.md b/docs/design/synthesis-model.en.md similarity index 96% rename from docs/design/compilation-model.en.md rename to docs/design/synthesis-model.en.md index 874f042..32d980a 100644 --- a/docs/design/compilation-model.en.md +++ b/docs/design/synthesis-model.en.md @@ -34,7 +34,7 @@ DerivationState_i = ( ) ``` -The current implementation calls its typed derivation context `CompilationSession`. It identifies workload, strategy, target requirements, deployment, calibration revision, feature set, and deterministic seed; its fingerprint participates in every analysis address. This is an implementation identifier inherited from compiler engineering, not a conceptual Compiler component. +The implementation names its typed derivation context `SynthesisSession`. It identifies workload, strategy, target requirements, deployment, calibration revision, feature set, and deterministic seed; its fingerprint participates in every analysis address. The term belongs to the `blueprinting.synthesizer` implementation boundary and does not introduce a conceptual Compiler component. ## Derivation contract diff --git a/docs/design/compilation-model.zh.md b/docs/design/synthesis-model.zh.md similarity index 96% rename from docs/design/compilation-model.zh.md rename to docs/design/synthesis-model.zh.md index d16db4e..8ff4dc8 100644 --- a/docs/design/compilation-model.zh.md +++ b/docs/design/synthesis-model.zh.md @@ -34,7 +34,7 @@ DerivationState_i = ( ) ``` -当前实现把 typed derivation context 命名为 `CompilationSession`。它标识 workload、strategy、target requirement、deployment、calibration revision、feature set 与 deterministic seed;session fingerprint 参与每个 analysis address。这个名称是从编译工程沿用的实现标识,不代表概念架构中存在一个 Compiler 组件。 +实现将 typed derivation context 命名为 `SynthesisSession`。它标识 workload、strategy、target requirement、deployment、calibration revision、feature set 与 deterministic seed;session fingerprint 参与每个 analysis address。这个术语属于 `blueprinting.synthesizer` 实现边界,不代表概念架构中存在一个 Compiler 组件。 ## 推导 Contract diff --git a/docs/experiments/calculon-calibration.en.md b/docs/experiments/calculon-calibration.en.md index fa04b39..5457ad2 100644 --- a/docs/experiments/calculon-calibration.en.md +++ b/docs/experiments/calculon-calibration.en.md @@ -144,15 +144,15 @@ uv run python examples/calculon_calibration.py \ uv run pytest -m baseline_regression tests/regression ``` -The original eight parametrized training regressions remain in `tests/compiler/test_calculon_calibration.py`. The repository-level gate additionally runs all eight cases as one experiment and evaluates `data/validation/baseline_regression_contract.json`: workload and Calculon equivalence, memory, paper-error budgets, evidence revision, case identity, aggregate goldens, and every `PortablePlanIR` digest are frozen together. Updating a golden is a reviewed contract change; the gate has no automatic accept-current-output mode. +The original eight parametrized training regressions remain in `tests/synthesizer/test_calculon_calibration.py`. The repository-level gate additionally runs all eight cases as one experiment and evaluates `data/validation/baseline_regression_contract.json`: workload and Calculon equivalence, memory, paper-error budgets, evidence revision, case identity, aggregate goldens, and every `PortablePlanIR` digest are frozen together. Updating a golden is a reviewed contract change; the gate has no automatic accept-current-output mode. Implementation map: -- `compiler/models/transformer.py`: typed frontend and execution facts; +- `synthesizer/models/transformer.py`: typed frontend and execution facts; - `analysis/transformer_workload.py`: static operation/byte analysis; -- `compiler/lowering/transformer.py`: the two canonical derivation passes; +- `synthesizer/lowering/transformer.py`: the two canonical derivation passes; - `analysis/cost_model.py`: peak-only and evidence-backed views; -- `compiler/experiments/calculon.py`: oracle adapter, audit, and report. -- `compiler/experiments/regression.py`: strict cross-domain baseline gate and diagnostics. +- `synthesizer/experiments/calculon.py`: oracle adapter, audit, and report. +- `synthesizer/experiments/regression.py`: strict cross-domain baseline gate and diagnostics. This is the repository's single Blueprinting/Calculon calibration path. Future comparisons must keep oracle data unavailable until workload construction and estimation complete. See [Transformer workload derivation](../design/passes/transformer.md) for the internal transformation contracts and [performance evidence](../design/performance/index.md) for the intended provider migration. diff --git a/docs/experiments/calculon-calibration.zh.md b/docs/experiments/calculon-calibration.zh.md index 1134dad..d3fd159 100644 --- a/docs/experiments/calculon-calibration.zh.md +++ b/docs/experiments/calculon-calibration.zh.md @@ -144,15 +144,15 @@ uv run python examples/calculon_calibration.py \ uv run pytest -m baseline_regression tests/regression ``` -原有的 8 组参数化训练回归仍保留在 `tests/compiler/test_calculon_calibration.py`。仓库级 gate 还会把 8 个 case 作为一个完整实验运行,并检查 `data/validation/baseline_regression_contract.json`:workload/Calculon 等价性、memory、论文误差预算、evidence revision、case identity、aggregate golden 与每个 `PortablePlanIR` digest 被一起冻结。更新 golden 是必须经过 review 的 contract 变更;gate 不提供自动“接受当前输出”的模式。 +原有的 8 组参数化训练回归仍保留在 `tests/synthesizer/test_calculon_calibration.py`。仓库级 gate 还会把 8 个 case 作为一个完整实验运行,并检查 `data/validation/baseline_regression_contract.json`:workload/Calculon 等价性、memory、论文误差预算、evidence revision、case identity、aggregate golden 与每个 `PortablePlanIR` digest 被一起冻结。更新 golden 是必须经过 review 的 contract 变更;gate 不提供自动“接受当前输出”的模式。 实现映射: -- `compiler/models/transformer.py`:typed frontend 与 execution facts; +- `synthesizer/models/transformer.py`:typed frontend 与 execution facts; - `analysis/transformer_workload.py`:静态 operation/byte analysis; -- `compiler/lowering/transformer.py`:两个 canonical derivation pass; +- `synthesizer/lowering/transformer.py`:两个 canonical derivation pass; - `analysis/cost_model.py`:peak-only 与 evidence-backed view; -- `compiler/experiments/calculon.py`:oracle adapter、audit 与 report。 -- `compiler/experiments/regression.py`:严格的跨域 baseline gate 与诊断。 +- `synthesizer/experiments/calculon.py`:oracle adapter、audit 与 report。 +- `synthesizer/experiments/regression.py`:严格的跨域 baseline gate 与诊断。 这是仓库唯一的 Blueprinting/Calculon calibration path。未来对比仍必须保证 workload construction 与 estimation 完成前无法访问 oracle data。内部 transformation contract 参见 [Transformer 工作负载推导](../design/passes/transformer.md),未来 provider 迁移参见 [performance evidence](../design/performance/index.md)。 diff --git a/docs/experiments/vidur-baseline.en.md b/docs/experiments/vidur-baseline.en.md index c449626..308282d 100644 --- a/docs/experiments/vidur-baseline.en.md +++ b/docs/experiments/vidur-baseline.en.md @@ -12,7 +12,7 @@ Transformer semantics + mapping + phase context -> Blueprinting DistributedTaskIR -> Blueprinting PortablePlanIR -> Blueprinting peak-only and system-evidence costs - -> freeze plan digests and compiled estimates + -> freeze plan digests and Blueprinting estimates -> exact Vidur baseline lookup -> coverage and error report ``` @@ -43,7 +43,7 @@ Each phase report exposes: - Blueprinting's complete block cost; - Blueprinting's subtotal over only matched components; - Vidur's subtotal over the same component intersection; -- compiled cost excluded from comparison; +- Blueprinting-estimated cost excluded from comparison; - signed absolute and relative errors at component and comparable-subtotal levels; - non-cancelling component MAPE and maximum component error; - model, distributed-plan, portable-plan, hardware-evidence, and baseline revisions. @@ -73,7 +73,7 @@ The current inference dialect models one dense-MHA, non-gated-MLP decoder templa The pinned Phi-2 rows are raw component profiles, not proof that Blueprinting reproduces Phi-2's full decoder topology. Norm placement, parallel-residual structure, fusion/layout choices, and embedding/LM-head work are not represented in the current model schema. The experiment policy therefore records `topology_equivalence = not-claimed-by-raw-component-profile-alignment`. -Vidur's public block aggregation contributes one `add_time`, while Blueprinting retains the attention residual and MLP residual as two explicit operations. Only the final MLP residual has a direct Vidur component peer; the other remains visible as excluded compiled work. This is reported as a semantic coverage gap instead of being hidden by double-counting the same baseline value. +Vidur's public block aggregation contributes one `add_time`, while Blueprinting retains the attention residual and MLP residual as two explicit operations. Only the final MLP residual has a direct Vidur component peer; the other remains visible as excluded estimated work. This is reported as a semantic coverage gap instead of being hidden by double-counting the same baseline value. ## How alignment should improve diff --git a/docs/experiments/vidur-baseline.zh.md b/docs/experiments/vidur-baseline.zh.md index bcf18fd..2a0049d 100644 --- a/docs/experiments/vidur-baseline.zh.md +++ b/docs/experiments/vidur-baseline.zh.md @@ -12,7 +12,7 @@ Transformer semantics + mapping + phase context -> Blueprinting DistributedTaskIR -> Blueprinting PortablePlanIR -> Blueprinting peak-only and system-evidence costs - -> freeze plan digests and compiled estimates + -> freeze plan digests and Blueprinting estimates -> exact Vidur baseline lookup -> coverage and error report ``` @@ -43,7 +43,7 @@ vidur.kv_cache_size = blueprinting.context_tokens - 1 - Blueprinting 完整 block cost; - Blueprinting 在 matched component 上的 subtotal; - Vidur 在相同 component intersection 上的 subtotal; -- 未进入比较的 compiled cost; +- 未进入比较的 Blueprinting estimated cost; - component 与 comparable-subtotal 层的 signed absolute/relative error; - 不可相互抵消的 component MAPE 与最大 component error; - model、distributed plan、portable plan、hardware evidence 与 baseline revision。 @@ -73,7 +73,7 @@ uv run pytest -m baseline_regression tests/regression 固定的 Phi-2 数据是 raw component profile,并不能证明 Blueprinting 已复现 Phi-2 的完整 decoder topology。当前 model schema 还没有表达 norm placement、parallel-residual structure、fusion/layout choice 与 embedding/LM-head work。因此 experiment policy 显式记录 `topology_equivalence = not-claimed-by-raw-component-profile-alignment`。 -Vidur 公开的 block aggregation 只贡献一个 `add_time`,而 Blueprinting 将 attention residual 与 MLP residual 保留为两个显式 operation。只有最终 MLP residual 拥有直接的 Vidur component peer;另一个仍作为 excluded compiled work 可见。系统把它报告为 semantic coverage gap,而不会通过重复使用同一个 baseline value 来掩盖差异。 +Vidur 公开的 block aggregation 只贡献一个 `add_time`,而 Blueprinting 将 attention residual 与 MLP residual 保留为两个显式 operation。只有最终 MLP residual 拥有直接的 Vidur component peer;另一个仍作为 excluded estimated work 可见。系统把它报告为 semantic coverage gap,而不会通过重复使用同一个 baseline value 来掩盖差异。 ## 如何改进对齐 diff --git a/docs/modeling/inference.en.md b/docs/modeling/inference.en.md index 28a25a9..9d33145 100644 --- a/docs/modeling/inference.en.md +++ b/docs/modeling/inference.en.md @@ -71,9 +71,9 @@ It is multiplied by the number of blocks in one pipeline stage. Weight storage i ```python from blueprinting.analysis import HardwareProfile, VidurProfileBaseline -from blueprinting.compiler.bindings import InferencePhase -from blueprinting.compiler.experiments import VidurExperimentCase, run_vidur_experiment -from blueprinting.compiler.models import TransformerInferenceExecutionSpec, TransformerModelSpec +from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.synthesizer.experiments import VidurExperimentCase, run_vidur_experiment +from blueprinting.synthesizer.models import TransformerInferenceExecutionSpec, TransformerModelSpec baseline = VidurProfileBaseline.from_csv( attention_csv="/profiles/attention.csv", @@ -96,7 +96,7 @@ case = VidurExperimentCase( report = run_vidur_experiment((case,), baseline) ``` -The API boundary is intentional: an admissible internal `InferenceCostProvider` exposes `resolve()`, while an external `InferenceBaseline` exposes `lookup()`. `run_vidur_experiment()` completes lowering and both Blueprinting cost modes before calling `lookup()`. Vidur therefore cannot alter operations, bytes, dependencies, the plan digest, or the compiled latency. +The API boundary is intentional: an admissible internal `InferenceCostProvider` exposes `resolve()`, while an external `InferenceBaseline` exposes `lookup()`. `run_vidur_experiment()` completes lowering and both Blueprinting cost modes before calling `lookup()`. Vidur therefore cannot alter operations, bytes, dependencies, the plan digest, or the estimated latency. Comparison is over an explicit semantic intersection. The report contains matched component count, coverage, Blueprinting's comparable subtotal, Vidur's comparable subtotal, excluded Blueprinting work, signed comparable-subtotal error, and non-cancelling component MAPE/max error. Missing records remain `not-covered`; they are never converted to zero. This matters because Vidur's public block aggregation has one `add_time`, whereas Blueprinting deliberately keeps both residual additions explicit, and the current CSV adapter does not yet ingest collective profiles. @@ -119,4 +119,4 @@ The scheduler produces a concrete batch context and asks the cost resolver for t ## Current limitations -The implemented dialect covers one dense-MHA, non-gated-MLP decoder template. Embedding, LM head, sampler, explicit norm/residual topology, GQA/MQA, gated MLP, MoE, prefix caching, paged allocation, chunked prefill, speculative decoding, disaggregated prefill/decode, scheduler overhead, and resource contention are not modeled yet. PP and replica structure are not fully materialized in `DistributedTaskIR`; PP latency/memory composition is currently analytical after the local-TP block plan. Each decode context is recompiled rather than algebraically specialized from a parametric plan. `replicas` currently participates only in mapping validation and world-size accounting; reported latency and static model token rate remain single-replica views, not multi-replica serving capacity. Consequently, the current output is suitable for inspecting derivation, analytical memory fit, and first-order hardware sensitivity—not for claiming production serving SLO accuracy. +The implemented dialect covers one dense-MHA, non-gated-MLP decoder template. Embedding, LM head, sampler, explicit norm/residual topology, GQA/MQA, gated MLP, MoE, prefix caching, paged allocation, chunked prefill, speculative decoding, disaggregated prefill/decode, scheduler overhead, and resource contention are not modeled yet. PP and replica structure are not fully materialized in `DistributedTaskIR`; PP latency/memory composition is currently analytical after the local-TP block plan. Each decode context is derived independently rather than algebraically specialized from a parametric plan. `replicas` currently participates only in mapping validation and world-size accounting; reported latency and static model token rate remain single-replica views, not multi-replica serving capacity. Consequently, the current output is suitable for inspecting derivation, analytical memory fit, and first-order hardware sensitivity—not for claiming production serving SLO accuracy. diff --git a/docs/modeling/inference.zh.md b/docs/modeling/inference.zh.md index ef2b611..4af743d 100644 --- a/docs/modeling/inference.zh.md +++ b/docs/modeling/inference.zh.md @@ -71,9 +71,9 @@ mean decode-step model time = decode total / (O-1), when O > 1 ```python from blueprinting.analysis import HardwareProfile, VidurProfileBaseline -from blueprinting.compiler.bindings import InferencePhase -from blueprinting.compiler.experiments import VidurExperimentCase, run_vidur_experiment -from blueprinting.compiler.models import TransformerInferenceExecutionSpec, TransformerModelSpec +from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.synthesizer.experiments import VidurExperimentCase, run_vidur_experiment +from blueprinting.synthesizer.models import TransformerInferenceExecutionSpec, TransformerModelSpec baseline = VidurProfileBaseline.from_csv( attention_csv="/profiles/attention.csv", @@ -96,7 +96,7 @@ case = VidurExperimentCase( report = run_vidur_experiment((case,), baseline) ``` -这个 API 边界是刻意设计的:Blueprinting 内部可接受的 `InferenceCostProvider` 暴露 `resolve()`,外部 `InferenceBaseline` 只暴露 `lookup()`。`run_vidur_experiment()` 会先完成 lowering 和两种 Blueprinting cost mode,再调用 `lookup()`;因此 Vidur 无法改变 operations、bytes、dependency、plan digest 或 compiled latency。 +这个 API 边界是刻意设计的:Blueprinting 内部可接受的 `InferenceCostProvider` 暴露 `resolve()`,外部 `InferenceBaseline` 只暴露 `lookup()`。`run_vidur_experiment()` 会先完成 lowering 和两种 Blueprinting cost mode,再调用 `lookup()`;因此 Vidur 无法改变 operations、bytes、dependency、plan digest 或 estimated latency。 Comparison 只发生在显式 semantic intersection 上。Report 给出 matched component count、coverage、Blueprinting comparable subtotal、Vidur comparable subtotal、被排除的 Blueprinting work、signed comparable-subtotal error,以及不可相互抵消的 component MAPE/max error。缺失 record 保持 `not-covered`,绝不会被当作零。这个区别很重要:Vidur 公开的 block aggregation 只有一个 `add_time`,而 Blueprinting 刻意保留两个 residual addition;当前 CSV adapter 也尚未读取 collective profile。 @@ -119,4 +119,4 @@ Scheduler 产生 concrete batch context,再使用该 context 查询 cost resol ## 当前限制 -已实现 dialect 覆盖一个 dense-MHA、non-gated-MLP decoder template。Embedding、LM head、sampler、显式 norm/residual topology、GQA/MQA、gated MLP、MoE、prefix caching、paged allocation、chunked prefill、speculative decoding、prefill/decode disaggregation、scheduler overhead 与 resource contention 尚未建模。PP 与 replica structure 还没有完整物化到 `DistributedTaskIR`;PP latency/memory 当前是在 local-TP block plan 之后做解析式组合。每个 decode context 仍会重新编译,而不是从 parametric plan 做代数特化。`replicas` 当前只参与 mapping 合法性与 world-size 记账;报告的 latency 与 static model token rate 仍是单 replica 视角,不代表多 replica serving capacity。因此当前输出适合检查推导、解析显存 fit 和一阶硬件敏感性,不能作为 production serving SLO accuracy 的声明。 +已实现 dialect 覆盖一个 dense-MHA、non-gated-MLP decoder template。Embedding、LM head、sampler、显式 norm/residual topology、GQA/MQA、gated MLP、MoE、prefix caching、paged allocation、chunked prefill、speculative decoding、prefill/decode disaggregation、scheduler overhead 与 resource contention 尚未建模。PP 与 replica structure 还没有完整物化到 `DistributedTaskIR`;PP latency/memory 当前是在 local-TP block plan 之后做解析式组合。每个 decode context 仍会独立推导,而不是从 parametric plan 做代数特化。`replicas` 当前只参与 mapping 合法性与 world-size 记账;报告的 latency 与 static model token rate 仍是单 replica 视角,不代表多 replica serving capacity。因此当前输出适合检查推导、解析显存 fit 和一阶硬件敏感性,不能作为 production serving SLO accuracy 的声明。 diff --git a/docs/project/adr/0001-synthesizer-package.en.md b/docs/project/adr/0001-synthesizer-package.en.md new file mode 100644 index 0000000..d92d111 --- /dev/null +++ b/docs/project/adr/0001-synthesizer-package.en.md @@ -0,0 +1,86 @@ +# ADR-0001: Name the Formal Derivation Package Synthesizer + +- Date: 2026-08-09 +- Status: Accepted +- Scope: Python package identity, public symbols, report vocabulary, and one serialized field name + +## Context + +Blueprinting explores hardware architectures by turning workload semantics and strategy choices into progressively more specific, verified plans. The implementation borrows IR, lowering, pass, and verifier techniques from compiler engineering, but the product is not a general-purpose compiler and has no conceptual Compiler component. + +The historical `blueprinting.compiler` package contradicted that boundary. It owned canonical representations, bindings, derivation transactions, model frontends, and validation experiments, while evidence-backed analysis had already become the sibling `blueprinting.analysis` package. Keeping the old name would continue to make an implementation toolbox look like the product architecture. + +Here, **synthesis** means formal plan synthesis: deriving a more specific, verified representation from explicit inputs and obligations. It does not mean RTL synthesis, hardware implementation, code generation, or Blueprinting's product identity. + +## Decision drivers + +- Make package ownership match the formal modeling and verified-derivation architecture. +- Keep analysis and performance evidence visibly separate from canonical state transformation. +- Remove ambiguous public vocabulary before target plugins and external consumers depend on it. +- Preserve stable serialized identities wherever the semantic contract has not changed. +- Prefer one decisive migration over a long-lived parallel API hierarchy. + +## Considered alternatives + +**Keep `compiler`.** This minimizes import churn but preserves the product-framing error and makes future module boundaries harder to explain. + +**Use `formal_analysis`.** This overlaps the existing `blueprinting.analysis` package and blurs authoritative transformations with rebuildable analyses. + +**Use `planner`.** Planning is only part of the path; the package also owns semantic frontends, canonical representations, verification, lineage, and machine-level realization contracts. + +**Rename to `synthesizer` with a compatibility shim.** A shim lowers immediate migration cost but keeps two discoverable public hierarchies, weakens boundary tests, and encourages indefinite use of the deprecated identity. + +## Decision + +Rename `blueprinting.compiler` to `blueprinting.synthesizer` as a hard Python API cut. Do not provide a `blueprinting.compiler` import shim. `blueprinting.analysis` remains a sibling package and may consume canonical synthesis contracts without mutating them. + +Use synthesis vocabulary for public implementation symbols: + +| Previous | Current | +|---|---| +| `CompilationSession` | `SynthesisSession` | +| `CompilerError` | `SynthesisError` | +| `CompilerPass` | `DerivationPass` | +| `compilation_session_for()` | `synthesis_session_for()` | +| `inference_compilation_session_for()` | `inference_synthesis_session_for()` | +| `compile_transformer_block()` | `derive_transformer_block()` | +| `compile_transformer_inference_block()` | `derive_transformer_inference_block()` | + +Keep `Pass`, `PassManager`, `lowering`, and `IR` where they describe precise borrowed mechanisms. External APIs such as Calculon's `model.compile()` and Python's `compile()` retain their names. + +Preserve all existing `compiler.*` canonical codec tags and the `compilation-session` digest domain as stable wire identities. They are historical opaque identifiers, not current Python package names. Renaming those tags would invalidate otherwise unchanged snapshots and digests without adding semantic value. + +Rename `TargetProfile.compiler_abi` to `target_abi` because the ABI belongs to a bound target, not to a Blueprinting Compiler component. The decoder accepts the legacy field as an alias, rejects payloads containing both spellings, and the encoder emits only `target_abi`. This intentional field-level schema change can alter digests of values containing a `TargetProfile`; target-neutral representation digests must remain unchanged. + +Report vocabulary distinguishes facts from predictions: exact work uses `derived_*`, timing and memory predictions use `estimated_*`, and the Blueprinting side of comparisons uses `blueprinting`. Calculon and Vidur report schemas advance to v2 because their emitted field names change. + +## Consequences + +- Imports from `blueprinting.compiler` fail immediately and downstream Python callers must migrate atomically. +- Source navigation now exposes the intended split: `synthesizer` owns canonical derivation; `analysis` owns rebuildable evaluation and evidence resolution. +- Canonical snapshots using historical `compiler.*` tags remain readable. +- Legacy canonical JSON containing `compiler_abi` remains readable, but newly serialized target profiles and target-bound session fingerprints change. +- Pickle/module-path compatibility is not provided. Canonical JSON is the supported persistence boundary. +- Existing v1 experiment report consumers must migrate to the v2 field names. + +## Migration + +1. Replace Python imports from `blueprinting.compiler` with `blueprinting.synthesizer`. +2. Replace the public symbols using the table above. +3. Replace `compiler_abi=` constructor arguments and attribute reads with `target_abi=` and `.target_abi`. +4. Update Calculon consumers from `compiled` to `blueprinting`, `compiled_breakdown_seconds` to `estimated_breakdown_seconds`, and `compiled_explicit_operations` to `derived_explicit_operations`. +5. Update Vidur consumers from `compiled_*` to the corresponding `estimated_*` fields. +6. Regenerate v2 experiment artifacts; do not rewrite old canonical input snapshots merely to replace their opaque codec tags. + +## Validation + +- A package-boundary test requires `blueprinting.synthesizer` to exist and `blueprinting.compiler` to be absent. +- Public API tests require the new symbols and reject legacy re-exports. +- Canonical round-trip tests decode the legacy `compiler_abi` payload and verify that new output contains only `target_abi`. +- Golden target-neutral IR snapshots and baseline regression digests guard the preserved wire tags and digest domain. +- Calculon and Vidur tests guard the v2 report vocabulary and numerical equivalence. +- Ruff, the full pytest suite, bilingual documentation parity, and strict MkDocs builds are release gates. + +## Status + +Accepted on 2026-08-09. This ADR governs the hard-cut migration delivered with the package rename. A future change to the package boundary, preserved codec tags, or alias policy requires a superseding ADR. diff --git a/docs/project/adr/0001-synthesizer-package.zh.md b/docs/project/adr/0001-synthesizer-package.zh.md new file mode 100644 index 0000000..b6164f9 --- /dev/null +++ b/docs/project/adr/0001-synthesizer-package.zh.md @@ -0,0 +1,86 @@ +# ADR-0001:将形式化推导包命名为 Synthesizer + +- 日期:2026-08-09 +- 状态:Accepted +- 范围:Python package identity、public symbol、report vocabulary 与一个 serialized field name + +## 背景 + +Blueprinting 通过把 workload semantic 与 strategy choice 逐步转化为更具体、经过验证的计划来探索硬件架构。实现借用了编译工程中的 IR、lowering、pass 与 verifier 技术,但产品不是通用编译器,概念架构中也没有独立 Compiler 组件。 + +历史上的 `blueprinting.compiler` package 与这个边界冲突。它同时承载 canonical representation、binding、derivation transaction、model frontend 与 validation experiment,而 evidence-backed analysis 已经提升为同级的 `blueprinting.analysis` package。继续保留旧名称,会持续把实现工具箱误解为产品架构。 + +这里的 **synthesis** 指 formal plan synthesis:根据显式输入与 obligation,推导出更具体且经过验证的 representation。它不表示 RTL synthesis、硬件实现、code generation,也不是 Blueprinting 的产品身份。 + +## 决策驱动因素 + +- 让 package ownership 与形式化建模、verified derivation 架构一致。 +- 让 analysis/performance evidence 与 canonical state transformation 保持清晰分离。 +- 在 target plugin 和外部 consumer 形成依赖前,消除有歧义的 public vocabulary。 +- 在 semantic contract 未变化时保留稳定 serialized identity。 +- 采用一次明确迁移,避免长期维护平行 API hierarchy。 + +## 备选方案 + +**保留 `compiler`。** Import churn 最小,但会保留产品口径错误,也让后续 module boundary 更难解释。 + +**使用 `formal_analysis`。** 它会与现有 `blueprinting.analysis` package 重叠,并混淆 authoritative transformation 与 rebuildable analysis。 + +**使用 `planner`。** Planning 只是路径的一部分;该 package 还承载 semantic frontend、canonical representation、verification、lineage 与 machine-level realization contract。 + +**重命名为 `synthesizer` 并提供兼容 shim。** Shim 能降低即时迁移成本,却会保留两套可发现 public hierarchy、削弱 boundary test,并鼓励已弃用身份长期存在。 + +## 决策 + +将 `blueprinting.compiler` 硬切重命名为 `blueprinting.synthesizer`,不提供 `blueprinting.compiler` import shim。`blueprinting.analysis` 保持同级 package,可以消费 canonical synthesis contract,但不得修改它们。 + +Public implementation symbol 使用 synthesis vocabulary: + +| 旧名称 | 新名称 | +|---|---| +| `CompilationSession` | `SynthesisSession` | +| `CompilerError` | `SynthesisError` | +| `CompilerPass` | `DerivationPass` | +| `compilation_session_for()` | `synthesis_session_for()` | +| `inference_compilation_session_for()` | `inference_synthesis_session_for()` | +| `compile_transformer_block()` | `derive_transformer_block()` | +| `compile_transformer_inference_block()` | `derive_transformer_inference_block()` | + +当 `Pass`、`PassManager`、`lowering` 与 `IR` 精确描述借用机制时继续保留。Calculon 的 `model.compile()`、Python 的 `compile()` 等外部 API 也保持原名。 + +保留所有既有 `compiler.*` canonical codec tag 与 `compilation-session` digest domain,把它们视为稳定 wire identity。它们是历史 opaque identifier,不是当前 Python package name。改写这些 tag 会在语义未变化时破坏 snapshot/digest,没有额外价值。 + +将 `TargetProfile.compiler_abi` 重命名为 `target_abi`,因为 ABI 属于绑定后的 target,而不属于 Blueprinting Compiler 组件。Decoder 将旧 field 作为 alias 接受,遇到两种拼写同时出现时拒绝 payload;encoder 只输出 `target_abi`。这个有意的 field-level schema change 会改变包含 `TargetProfile` 的 value digest;target-neutral representation digest 必须保持不变。 + +Report vocabulary 区分事实与预测:exact work 使用 `derived_*`,timing/memory prediction 使用 `estimated_*`,comparison 中 Blueprinting 一侧使用 `blueprinting`。Calculon 与 Vidur report 因输出 field name 改变升级为 v2。 + +## 影响 + +- `blueprinting.compiler` import 立即失败,下游 Python caller 必须原子迁移。 +- 源码结构明确表达目标边界:`synthesizer` 负责 canonical derivation,`analysis` 负责 rebuildable evaluation 与 evidence resolution。 +- 使用历史 `compiler.*` tag 的 canonical snapshot 仍可读取。 +- 包含 `compiler_abi` 的旧 canonical JSON 仍可读取,但新序列化 target profile 与 target-bound session fingerprint 会改变。 +- 不提供 pickle/module-path compatibility;canonical JSON 是受支持的 persistence boundary。 +- 既有 v1 experiment report consumer 必须迁移到 v2 field name。 + +## 迁移 + +1. 将 Python import 从 `blueprinting.compiler` 替换为 `blueprinting.synthesizer`。 +2. 按上表替换 public symbol。 +3. 将 `compiler_abi=` constructor argument 与 attribute read 改为 `target_abi=` 和 `.target_abi`。 +4. Calculon consumer 将 `compiled` 改为 `blueprinting`、`compiled_breakdown_seconds` 改为 `estimated_breakdown_seconds`、`compiled_explicit_operations` 改为 `derived_explicit_operations`。 +5. Vidur consumer 将 `compiled_*` 改为对应的 `estimated_*` field。 +6. 重新生成 v2 experiment artifact;不要仅为了替换 opaque codec tag 而改写旧 canonical input snapshot。 + +## 验证 + +- Package-boundary test 要求 `blueprinting.synthesizer` 存在且 `blueprinting.compiler` 不存在。 +- Public API test 要求新 symbol 存在,并拒绝 legacy re-export。 +- Canonical round-trip test 解码旧 `compiler_abi` payload,并验证新输出只包含 `target_abi`。 +- Golden target-neutral IR snapshot 与 baseline regression digest 守护被保留的 wire tag 与 digest domain。 +- Calculon/Vidur test 守护 v2 report vocabulary 与数值等价性。 +- Ruff、完整 pytest、双语文档一致性与 strict MkDocs build 是 release gate。 + +## 状态 + +本 ADR 于 2026-08-09 被接受,约束随 package rename 一起交付的 hard-cut migration。未来若修改 package boundary、保留的 codec tag 或 alias policy,必须创建 superseding ADR。 diff --git a/docs/project/decisions.en.md b/docs/project/decisions.en.md index da3884b..3c019f4 100644 --- a/docs/project/decisions.en.md +++ b/docs/project/decisions.en.md @@ -19,6 +19,7 @@ This page is the compact index of architecture commitments, rejected alternative | Runtime | Honor a verified plan while retaining bounded mechanism decisions allowed by the contract | Runtime does not repeat unbounded global search or pretend backpressure and failure do not exist | | Schema maturity | Internal schema versions are not automatically public compatibility promises | A contract graduates only after producer, independent consumer, migration, and conformance gates pass | | Documentation | Colocated suffix-based bilingual sources | Navigation and language switching remain page-aligned | +| Formal derivation package | Hard-cut Python rename to `blueprinting.synthesizer`; preserve historical codec tags | Source ownership matches formal plan synthesis without invalidating unchanged canonical snapshots; see [ADR-0001](adr/0001-synthesizer-package.md) | ## Rejected alternatives @@ -74,7 +75,7 @@ Accepted ADRs are immutable except for status links and typo fixes. A supersedin | Formal representation | An authoritative typed model with a versioned contract and verifier; current canonical types use the `*IR` suffix | | Derived view | A rebuildable projection that cannot change source representation semantics | | Artifact | A published report, trace, executable, or replayable package | -| Binding | An explicit specialization fact supplied through a typed derivation context; current code calls it `CompilationSession` | +| Binding | An explicit specialization fact supplied through a typed derivation context; current code calls it `SynthesisSession` | | Derivation | A verified rule that resolves decisions, discharges obligations, and preserves required semantics | | Lowering | A compiler-engineering implementation technique used for a staged derivation | | Revision | An immutable identity for evidence, schema, analysis engine, plugin, or product state | diff --git a/docs/project/decisions.zh.md b/docs/project/decisions.zh.md index 16539c6..d25d1fb 100644 --- a/docs/project/decisions.zh.md +++ b/docs/project/decisions.zh.md @@ -19,6 +19,7 @@ | Runtime | 遵守 verified plan,并保留 contract 允许的 bounded mechanism decision | Runtime 不重复无界 global search,也不假装 backpressure/failure 不存在 | | Schema maturity | Internal schema version 不自动构成 public compatibility promise | Producer、独立 consumer、migration 与 conformance Gate 通过后才毕业为 stable contract | | Documentation | 同目录 suffix-based 双语 source | Navigation 与 language switching 始终按页面对齐 | +| 形式化推导 package | Python path 硬切为 `blueprinting.synthesizer`;保留历史 codec tag | Source ownership 对齐 formal plan synthesis,同时不破坏未变化的 canonical snapshot;见 [ADR-0001](adr/0001-synthesizer-package.md) | ## 被拒绝方案 @@ -74,7 +75,7 @@ Accepted ADR 除 status link 和 typo 外保持不可变。取代旧决策时创 | Formal representation | 具有版本化 contract 与 verifier 的权威 typed model;当前 canonical type 使用 `*IR` 后缀 | | Derived view | 不改变 source representation semantic 的可重建 projection | | Artifact | 已发布 report、trace、executable 或 replayable package | -| Binding | 通过 typed derivation context 显式提供的 specialization fact;当前代码名为 `CompilationSession` | +| Binding | 通过 typed derivation context 显式提供的 specialization fact;当前代码名为 `SynthesisSession` | | Derivation | 解析决策、消解 obligation 并保持所需语义的 verified rule | | Lowering | 用于实现 staged derivation 的编译工程技术 | | Revision | Evidence、schema、analysis engine、plugin 或 product state 的 immutable identity | diff --git a/docs/project/status.en.md b/docs/project/status.en.md index 7de0dbc..3fdefec 100644 --- a/docs/project/status.en.md +++ b/docs/project/status.en.md @@ -71,7 +71,7 @@ TransformerModelSpec + inference mapping + request cohort ## What the current result can claim -The repository can claim that selected Transformer training workloads are decomposed into auditable target-neutral work and compared against one versioned system evidence profile without case-specific timing coefficients. It can also compile dense-MHA inference prefill and decode phase points, derive KV capacity, and compose a homogeneous request cohort with explicit evidence provenance. It provides the stable identities, verifier gates, and pass-level checkpoint hooks needed for later simulation correlation. +The repository can claim that selected Transformer training workloads are decomposed into auditable target-neutral work and compared against one versioned system evidence profile without case-specific timing coefficients. It can also derive dense-MHA inference prefill and decode phase points, derive KV capacity, and compose a homogeneous request cohort with explicit evidence provenance. It provides the stable identities, verifier gates, and pass-level checkpoint hooks needed for later simulation correlation. It cannot yet claim serving-system SLO accuracy: arrivals, queueing, continuous batching, scheduler overhead, contention, and tail distributions are absent. Nor can it yet claim that Blueprinting explores compute/memory/interconnect parameters, predicts NoC behavior, models energy/area/cost, constructs a legal concrete hardware schedule, produces sensitivity/Pareto results, or closes a calibration loop on real GPU/LPU observations. @@ -79,8 +79,8 @@ It cannot yet claim serving-system SLO accuracy: arrivals, queueing, continuous | Foundation | Status | Source of truth | |---|---|---| -| Immutable values, stable IDs, lineage, codec, digests | **Implemented** | `src/blueprinting/compiler/{frozen,ids,codec}.py` | -| Five progressive formal-representation schemas (`*IR`) and verifiers | **Experimental Contract** | `src/blueprinting/compiler/ir/`; only the first three have a production derivation slice | +| Immutable values, stable IDs, lineage, codec, digests | **Implemented** | `src/blueprinting/synthesizer/{frozen,ids,codec}.py` | +| Five progressive formal-representation schemas (`*IR`) and verifiers | **Experimental Contract** | `src/blueprinting/synthesizer/ir/`; only the first three have a production derivation slice | | Typed workload/strategy/target/deployment bindings | **Implemented** | `bindings.py`, `session.py` | | Transactional analyses/transformations, checkpoints, observers | **Implemented** | `passes/base.py` | | Transformer semantic frontend and workload algebra | **Implemented slice** | `models/transformer.py`, `analysis/transformer_workload.py` | diff --git a/docs/project/status.zh.md b/docs/project/status.zh.md index be5284f..e645233 100644 --- a/docs/project/status.zh.md +++ b/docs/project/status.zh.md @@ -71,7 +71,7 @@ TransformerModelSpec + inference mapping + request cohort ## 当前结果可以声称什么 -仓库可以声称:选定 Transformer training workload 被分解为可审计 target-neutral work,并在不使用 case-specific timing coefficient 的前提下与一个版本化 system evidence profile 比较;系统也可以编译 dense-MHA inference 的 prefill/decode phase point、推导 KV 容量,并以显式 evidence provenance 组合 homogeneous request cohort。系统提供了后续 simulation correlation 所需的 stable identity、verifier gate 与 pass-level checkpoint hook。 +仓库可以声称:选定 Transformer training workload 被分解为可审计 target-neutral work,并在不使用 case-specific timing coefficient 的前提下与一个版本化 system evidence profile 比较;系统也可以推导 dense-MHA inference 的 prefill/decode phase point、推导 KV 容量,并以显式 evidence provenance 组合 homogeneous request cohort。系统提供了后续 simulation correlation 所需的 stable identity、verifier gate 与 pass-level checkpoint hook。 当前还不能声称 serving-system SLO accuracy:arrival、queueing、continuous batching、scheduler overhead、contention 与 tail distribution 均未实现。也不能声称 Blueprinting 已经探索 compute/memory/interconnect parameter、预测 NoC 行为、建模 energy/area/cost、构造合法 concrete hardware schedule、产生 sensitivity/Pareto result,或用真实 GPU/LPU observation 闭合 calibration loop。 @@ -79,8 +79,8 @@ TransformerModelSpec + inference mapping + request cohort | 基础 | 状态 | Source of truth | |---|---|---| -| Immutable value、stable ID、lineage、codec、digest | **Implemented** | `src/blueprinting/compiler/{frozen,ids,codec}.py` | -| 五层 progressive formal-representation schema(`*IR`)与 verifier | **Experimental Contract** | `src/blueprinting/compiler/ir/`;只有前三层存在 production derivation slice | +| Immutable value、stable ID、lineage、codec、digest | **Implemented** | `src/blueprinting/synthesizer/{frozen,ids,codec}.py` | +| 五层 progressive formal-representation schema(`*IR`)与 verifier | **Experimental Contract** | `src/blueprinting/synthesizer/ir/`;只有前三层存在 production derivation slice | | Typed workload/strategy/target/deployment binding | **Implemented** | `bindings.py`、`session.py` | | Transactional analysis/transformation、checkpoint、observer | **Implemented** | `passes/base.py` | | Transformer semantic frontend 与 workload algebra | **Implemented slice** | `models/transformer.py`、`analysis/transformer_workload.py` | diff --git a/examples/calculon_calibration.py b/examples/calculon_calibration.py index 8fbe80f..8a543d0 100644 --- a/examples/calculon_calibration.py +++ b/examples/calculon_calibration.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Run the canonical compiler/Calculon calibration experiment. +"""Run the canonical Blueprinting/Calculon calibration experiment. Examples: uv run python examples/calculon_calibration.py @@ -15,14 +15,14 @@ ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) -from blueprinting.compiler.experiments import ( # noqa: E402 +from blueprinting.synthesizer.experiments import ( # noqa: E402 discover_seqsel_tab5_cases, run_calculon_experiment, ) def main() -> int: - parser = argparse.ArgumentParser(description="Compile and calibrate the SeqSel/Calculon comparison cases") + parser = argparse.ArgumentParser(description="Derive and calibrate the SeqSel/Calculon comparison cases") parser.add_argument("--output", type=Path, help="optional JSON report path") arguments = parser.parse_args() @@ -32,7 +32,7 @@ def main() -> int: arguments.output.parent.mkdir(parents=True, exist_ok=True) arguments.output.write_text(payload, encoding="utf-8") - print("Blueprinting compiler ↔ Calculon calibration") + print("Blueprinting synthesis ↔ Calculon calibration") print(f"hardware evidence: {report.hardware_name} / {report.evidence_revision}") print( "mean absolute error: " diff --git a/examples/calculon_calibration_result.json b/examples/calculon_calibration_result.json index 7f49044..a1a86a5 100644 --- a/examples/calculon_calibration_result.json +++ b/examples/calculon_calibration_result.json @@ -31,7 +31,7 @@ "tensor_parallel": 0.1974261886030769 }, "case": "seqsel-tab5/megatron-22B/full", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 0.5154692240714278, "data_parallel": 0.0, "forward": 0.2861413155860729, @@ -61,12 +61,12 @@ }, "memory_bytes": { "calculon": 51705331712.0, - "compiled": 51705331712, + "estimated": 51705331712, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 7626047881216.0, - "compiled_explicit_operations": 981366472704, + "derived_explicit_operations": 981366472704, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -82,97 +82,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 100663296, + "blueprinting": 100663296, "reference": 100663296, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 1728053248, + "blueprinting": 1728053248, "reference": 1728053248, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 5683331072, + "blueprinting": 5683331072, "reference": 5683331072, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 1034478944256, + "blueprinting": 1034478944256, "reference": 1034478944256.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 1526726656, + "blueprinting": 1526726656, "reference": 1526726656, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 201326592, + "blueprinting": 201326592, "reference": 201326592, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 4676698112, + "blueprinting": 4676698112, "reference": 4676698112, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 981454553088, + "blueprinting": 981454553088, "reference": 981454553088.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 201326592, + "blueprinting": 201326592, "reference": 201326592, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 679772160, + "blueprinting": 679772160, "reference": 679772160.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 679772160, + "blueprinting": 679772160, "reference": 679772160.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 623124480, + "blueprinting": 623124480, "reference": 623124480.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 201326592, + "blueprinting": 201326592, "reference": 201326592, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 113295360, + "blueprinting": 113295360, "reference": 113295360, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 113295360, + "blueprinting": 113295360, "reference": 113295360.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 1270923264, + "blueprinting": 1270923264, "reference": 1270923264, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 928417579008, + "blueprinting": 928417579008, "reference": 928417579008, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 226590720, + "blueprinting": 226590720, "reference": 226590720, "relative_error_percent": 0.0 } @@ -191,7 +191,7 @@ "tensor_parallel": 0.30704300977230775 }, "case": "seqsel-tab5/megatron-22B/seqsel", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 0.4456712746314279, "data_parallel": 0.0, "forward": 0.24675584713068827, @@ -221,12 +221,12 @@ }, "memory_bytes": { "calculon": 55920607232.0, - "compiled": 55920607232, + "estimated": 55920607232, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 774797000704, - "compiled_explicit_operations": 26575110144, + "derived_explicit_operations": 26575110144, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -242,97 +242,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 1136656384, + "blueprinting": 1136656384, "reference": 1136656384.0, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 4236296192, + "blueprinting": 4236296192, "reference": 4236296192, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 1032805416960, + "blueprinting": 1032805416960, "reference": 1032805416960.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 213909504, + "blueprinting": 213909504, "reference": 213909504.0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 1111490560, + "blueprinting": 1111490560, "reference": 1111490560.0, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 402653184, + "blueprinting": 402653184, "reference": 402653184, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 3758145536, + "blueprinting": 3758145536, "reference": 3758145536, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 980485668864, + "blueprinting": 980485668864, "reference": 980485668864.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 402653184, + "blueprinting": 402653184, "reference": 402653184, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 679772160, + "blueprinting": 679772160, "reference": 679772160.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 679772160, + "blueprinting": 679772160, "reference": 679772160.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 623124480, + "blueprinting": 623124480, "reference": 623124480.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 201326592, + "blueprinting": 201326592, "reference": 201326592, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 113295360, + "blueprinting": 113295360, "reference": 113295360, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 113295360, + "blueprinting": 113295360, "reference": 113295360.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 918601728, + "blueprinting": 918601728, "reference": 918601728, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 927801016320, + "blueprinting": 927801016320, "reference": 927801016320, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 226590720, + "blueprinting": 226590720, "reference": 226590720, "relative_error_percent": 0.0 } @@ -351,7 +351,7 @@ "tensor_parallel": 1.5947695088246152 }, "case": "seqsel-tab5/gpt3-175B/full", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 6.906075908214845, "data_parallel": 0.0, "forward": 3.648568878349474, @@ -381,12 +381,12 @@ }, "memory_bytes": { "calculon": 51649806336.0, - "compiled": 51649806336, + "estimated": 51649806336, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 7290655604736.0, - "compiled_explicit_operations": 954439041024, + "derived_explicit_operations": 954439041024, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -402,97 +402,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 50331648, + "blueprinting": 50331648, "reference": 50331648, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 780140544, + "blueprinting": 780140544, "reference": 780140544, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 3019997184, + "blueprinting": 3019997184, "reference": 3019997184, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 980944945152, + "blueprinting": 980944945152, "reference": 980944945152.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 679477248, + "blueprinting": 679477248, "reference": 679477248, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 100663296, + "blueprinting": 100663296, "reference": 100663296, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 2516680704, + "blueprinting": 2516680704, "reference": 2516680704, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 954483081216, + "blueprinting": 954483081216, "reference": 954483081216.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 100663296, + "blueprinting": 100663296, "reference": 100663296, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 2718498816, + "blueprinting": 2718498816, "reference": 2718498816.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 2718498816, + "blueprinting": 2718498816, "reference": 2718498816.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 2491957248, + "blueprinting": 2491957248, "reference": 2491957248.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 100663296, + "blueprinting": 100663296, "reference": 100663296, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 453083136, + "blueprinting": 453083136, "reference": 453083136, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 453083136, + "blueprinting": 453083136, "reference": 453083136.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 1031897088, + "blueprinting": 1031897088, "reference": 1031897088, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 928065257472, + "blueprinting": 928065257472, "reference": 928065257472, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 906166272, + "blueprinting": 906166272, "reference": 906166272, "relative_error_percent": 0.0 } @@ -511,7 +511,7 @@ "tensor_parallel": 2.487064078178462 }, "case": "seqsel-tab5/gpt3-175B/seqsel", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 6.347704600694843, "data_parallel": 0.0, "forward": 3.3334912747063967, @@ -541,12 +541,12 @@ }, "memory_bytes": { "calculon": 58060800000.0, - "compiled": 58060800000, + "estimated": 58060800000, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 735106301952, - "compiled_explicit_operations": 13186891776, + "derived_explicit_operations": 13186891776, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -562,97 +562,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 484442112, + "blueprinting": 484442112, "reference": 484442112.0, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 2296479744, + "blueprinting": 2296479744, "reference": 2296479744, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 980108181504, + "blueprinting": 980108181504, "reference": 980108181504.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 106954752, + "blueprinting": 106954752, "reference": 106954752.0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 471859200, + "blueprinting": 471859200, "reference": 471859200.0, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 201326592, + "blueprinting": 201326592, "reference": 201326592, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 2057404416, + "blueprinting": 2057404416, "reference": 2057404416, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 953998639104, + "blueprinting": 953998639104, "reference": 953998639104.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 201326592, + "blueprinting": 201326592, "reference": 201326592, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 2718498816, + "blueprinting": 2718498816, "reference": 2718498816.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 2718498816, + "blueprinting": 2718498816, "reference": 2718498816.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 2491957248, + "blueprinting": 2491957248, "reference": 2491957248.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 100663296, + "blueprinting": 100663296, "reference": 100663296, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 453083136, + "blueprinting": 453083136, "reference": 453083136, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 453083136, + "blueprinting": 453083136, "reference": 453083136.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 855736320, + "blueprinting": 855736320, "reference": 855736320, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 927756976128, + "blueprinting": 927756976128, "reference": 927756976128, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 906166272, + "blueprinting": 906166272, "reference": 906166272, "relative_error_percent": 0.0 } @@ -671,7 +671,7 @@ "tensor_parallel": 2.8847319171282053 }, "case": "seqsel-tab5/turing-530B/full", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 18.49786125754674, "data_parallel": 0.0, "forward": 9.488717946599008, @@ -701,12 +701,12 @@ }, "memory_bytes": { "calculon": 45386465280.0, - "compiled": 45386465280, + "estimated": 45386465280, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 19880741961728.0, - "compiled_explicit_operations": 2621423222784, + "derived_explicit_operations": 2621423222784, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -722,97 +722,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 83886080, + "blueprinting": 83886080, "reference": 83886080, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 1216348160, + "blueprinting": 1216348160, "reference": 1216348160, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 5318541312, + "blueprinting": 5318541312, "reference": 5318541312, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 2665549398016, + "blueprinting": 2665549398016, "reference": 2665549398016.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 1048576000, + "blueprinting": 1048576000, "reference": 1048576000, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 167772160, + "blueprinting": 167772160, "reference": 167772160, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 4479680512, + "blueprinting": 4479680512, "reference": 4479680512, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 2621496623104, + "blueprinting": 2621496623104, "reference": 2621496623104.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 167772160, + "blueprinting": 167772160, "reference": 167772160, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 7550730240, + "blueprinting": 7550730240, "reference": 7550730240.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 7550730240, + "blueprinting": 7550730240, "reference": 7550730240.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 6921502720, + "blueprinting": 6921502720, "reference": 6921502720.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 167772160, + "blueprinting": 167772160, "reference": 167772160, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 1258455040, + "blueprinting": 1258455040, "reference": 1258455040, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 1258455040, + "blueprinting": 1258455040, "reference": 1258455040.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 2223144960, + "blueprinting": 2223144960, "reference": 2223144960, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 2577567580160, + "blueprinting": 2577567580160, "reference": 2577567580160, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 2516910080, + "blueprinting": 2516910080, "reference": 2516910080, "relative_error_percent": 0.0 } @@ -831,7 +831,7 @@ "tensor_parallel": 4.488910559179487 }, "case": "seqsel-tab5/turing-530B/seqsel", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 17.47999689488007, "data_parallel": 0.0, "forward": 8.914357731624653, @@ -861,12 +861,12 @@ }, "memory_bytes": { "calculon": 57487032320.0, - "compiled": 57487032320, + "estimated": 57487032320, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 1998086733824, - "compiled_explicit_operations": 21877489664, + "derived_explicit_operations": 21877489664, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -882,97 +882,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 723517440, + "blueprinting": 723517440, "reference": 723517440.0, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 4112678912, + "blueprinting": 4112678912, "reference": 4112678912, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 2664154791936, + "blueprinting": 2664154791936, "reference": 2664154791936.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 178257920, + "blueprinting": 178257920, "reference": 178257920.0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 702545920, + "blueprinting": 702545920, "reference": 702545920.0, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 335544320, + "blueprinting": 335544320, "reference": 335544320, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 3714220032, + "blueprinting": 3714220032, "reference": 3714220032, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 2620689219584, + "blueprinting": 2620689219584, "reference": 2620689219584.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 335544320, + "blueprinting": 335544320, "reference": 335544320, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 7550730240, + "blueprinting": 7550730240, "reference": 7550730240.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 7550730240, + "blueprinting": 7550730240, "reference": 7550730240.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 6921502720, + "blueprinting": 6921502720, "reference": 6921502720.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 167772160, + "blueprinting": 167772160, "reference": 167772160, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 1258455040, + "blueprinting": 1258455040, "reference": 1258455040, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 1258455040, + "blueprinting": 1258455040, "reference": 1258455040.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 1929543680, + "blueprinting": 1929543680, "reference": 1929543680, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 2577053777920, + "blueprinting": 2577053777920, "reference": 2577053777920, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 2516910080, + "blueprinting": 2516910080, "reference": 2516910080, "relative_error_percent": 0.0 } @@ -991,7 +991,7 @@ "tensor_parallel": 4.3855419689572654 }, "case": "seqsel-tab5/megatron-1T/full", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 34.17680307666013, "data_parallel": 0.0, "forward": 17.447966314699297, @@ -1021,12 +1021,12 @@ }, "memory_bytes": { "calculon": 49679769600.0, - "compiled": 49679769600, + "estimated": 49679769600, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 30890725212160.0, - "compiled_explicit_operations": 4082085396480, + "derived_explicit_operations": 4082085396480, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -1042,97 +1042,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 104857600, + "blueprinting": 104857600, "reference": 104857600, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 1520435200, + "blueprinting": 1520435200, "reference": 1520435200, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 7041392640, + "blueprinting": 7041392640, "reference": 7041392640, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 4137243115520, + "blueprinting": 4137243115520, "reference": 4137243115520.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 1310720000, + "blueprinting": 1310720000, "reference": 1310720000, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 209715200, + "blueprinting": 209715200, "reference": 209715200, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 5992816640, + "blueprinting": 5992816640, "reference": 5992816640, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 4082177146880, + "blueprinting": 4082177146880, "reference": 4082177146880.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 209715200, + "blueprinting": 209715200, "reference": 209715200, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 11797708800, + "blueprinting": 11797708800, "reference": 11797708800.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 11797708800, + "blueprinting": 11797708800, "reference": 11797708800.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 10814566400, + "blueprinting": 10814566400, "reference": 10814566400.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 209715200, + "blueprinting": 209715200, "reference": 209715200, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 1966284800, + "blueprinting": 1966284800, "reference": 1966284800, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 1966284800, + "blueprinting": 1966284800, "reference": 1966284800.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 3172147200, + "blueprinting": 3172147200, "reference": 3172147200, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 4027265843200, + "blueprinting": 4027265843200, "reference": 4027265843200, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 3932569600, + "blueprinting": 3932569600, "reference": 3932569600, "relative_error_percent": 0.0 } @@ -1151,7 +1151,7 @@ "tensor_parallel": 6.819764661606838 }, "case": "seqsel-tab5/megatron-1T/seqsel", - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "backward": 32.62577166688235, "data_parallel": 0.0, "forward": 16.572750749024085, @@ -1181,12 +1181,12 @@ }, "memory_bytes": { "calculon": 63507865600.0, - "compiled": 63507865600, + "estimated": 63507865600, "relative_error_percent": 0.0 }, "recompute_counter_audit": { "calculon_block_re_flops": 3101588193280, - "compiled_explicit_operations": 27346862080, + "derived_explicit_operations": 27346862080, "note": "Calculon accumulates a running forward prefix in block_re_flops; block_re_time sums selected operations and is the comparable metric." }, "timing_error_percent": { @@ -1202,97 +1202,97 @@ }, "workload_audit": { "block_activation_checkpoint_bytes": { - "compiled": 0, + "blueprinting": 0, "reference": 0, "relative_error_percent": 0.0 }, "block_activation_gradient_bytes": { - "compiled": 904396800, + "blueprinting": 904396800, "reference": 904396800.0, "relative_error_percent": 0.0 }, "block_activation_gradient_memory_bytes": { - "compiled": 5534064640, + "blueprinting": 5534064640, "reference": 5534064640, "relative_error_percent": 0.0 }, "block_activation_gradient_operations": { - "compiled": 4135499857920, + "blueprinting": 4135499857920, "reference": 4135499857920.0, "relative_error_percent": 0.0 }, "block_activation_storage_bytes": { - "compiled": 222822400, + "blueprinting": 222822400, "reference": 222822400.0, "relative_error_percent": 0.0 }, "block_activation_working_bytes": { - "compiled": 878182400, + "blueprinting": 878182400, "reference": 878182400.0, "relative_error_percent": 0.0 }, "block_backward_tp_message_bytes": { - "compiled": 419430400, + "blueprinting": 419430400, "reference": 419430400, "relative_error_percent": 0.0 }, "block_forward_memory_bytes": { - "compiled": 5035991040, + "blueprinting": 5035991040, "reference": 5035991040, "relative_error_percent": 0.0 }, "block_forward_operations": { - "compiled": 4081167892480, + "blueprinting": 4081167892480, "reference": 4081167892480.0, "relative_error_percent": 0.0 }, "block_forward_tp_message_bytes": { - "compiled": 419430400, + "blueprinting": 419430400, "reference": 419430400, "relative_error_percent": 0.0 }, "block_optimizer_bytes": { - "compiled": 11797708800, + "blueprinting": 11797708800, "reference": 11797708800.0, "relative_error_percent": 0.0 }, "block_optimizer_memory_bytes": { - "compiled": 11797708800, + "blueprinting": 11797708800, "reference": 11797708800.0, "relative_error_percent": 0.0 }, "block_optimizer_operations": { - "compiled": 10814566400, + "blueprinting": 10814566400, "reference": 10814566400.0, "relative_error_percent": 0.0 }, "block_recommunication_message_bytes": { - "compiled": 209715200, + "blueprinting": 209715200, "reference": 209715200, "relative_error_percent": 0.0 }, "block_weight_bytes": { - "compiled": 1966284800, + "blueprinting": 1966284800, "reference": 1966284800, "relative_error_percent": 0.0 }, "block_weight_gradient_bytes": { - "compiled": 1966284800, + "blueprinting": 1966284800, "reference": 1966284800.0, "relative_error_percent": 0.0 }, "block_weight_gradient_memory_bytes": { - "compiled": 2805145600, + "blueprinting": 2805145600, "reference": 2805145600, "relative_error_percent": 0.0 }, "block_weight_gradient_operations": { - "compiled": 4026623590400, + "blueprinting": 4026623590400, "reference": 4026623590400, "relative_error_percent": 0.0 }, "block_weight_gradient_unsharded_bytes": { - "compiled": 3932569600, + "blueprinting": 3932569600, "reference": 3932569600, "relative_error_percent": 0.0 } @@ -1303,7 +1303,7 @@ "evidence_revision": "eb1eb9fcc4a6e414e85b0252c23ea9ad2730aae2", "name": "a100_80g" }, - "schema": "blueprinting.calculon-calibration-experiment.v1", + "schema": "blueprinting.calculon-calibration-experiment.v2", "summary": { "case_count": 8, "peak_only_mean_absolute_error_percent": 12.993831306582473, diff --git a/mkdocs.yml b/mkdocs.yml index dfcae82..1ad7668 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -62,7 +62,7 @@ nav: - Vidur Raw Component-Profile Alignment: experiments/vidur-baseline.md - Formal Analysis Foundations: - Formal Analysis Architecture: design/index.md - - Derivation and Verification Model: design/compilation-model.md + - Derivation and Verification Model: design/synthesis-model.md - Timeline Staging Path: design/timeline-path.md - Golden Derivation Walkthrough: design/walkthrough.md - Analysis Module Architecture: design/modules.md @@ -78,6 +78,7 @@ nav: - Implementation Status: project/status.md - Roadmap: project/roadmap.md - Decisions and Terminology: project/decisions.md + - ADR-0001 — Synthesizer Package: project/adr/0001-synthesizer-package.md - Architecture Risk Register: project/risks.md - Documentation Guide: contributing/documentation.md @@ -136,6 +137,7 @@ plugins: Implementation Status: 实现状态 Roadmap: 路线图 Decisions and Terminology: 设计决策与术语 + ADR-0001 — Synthesizer Package: ADR-0001 — Synthesizer 包命名 Architecture Risk Register: 架构风险登记表 Documentation Guide: 文档维护指南 diff --git a/src/blueprinting/analysis/__init__.py b/src/blueprinting/analysis/__init__.py index ee7f5bd..2e34190 100644 --- a/src/blueprinting/analysis/__init__.py +++ b/src/blueprinting/analysis/__init__.py @@ -49,7 +49,7 @@ from .transformer_inference import ( InferenceBlockMemoryFacts, InferenceInvocation, - compile_transformer_inference_block, + derive_transformer_inference_block, ) from .transformer_workload import ( BlockMemoryFacts, @@ -57,7 +57,7 @@ PhaseWork, PrimitiveInvocation, TrainingPhase, - compile_transformer_block, + derive_transformer_block, ) from .vidur import VidurProfileBaseline, VidurProfileImporter @@ -102,8 +102,8 @@ "TabularImportSpec", "TabularPerformanceImporter", "TrainingPhase", - "compile_transformer_block", - "compile_transformer_inference_block", + "derive_transformer_block", + "derive_transformer_inference_block", "cost_query_for_inference_task", "estimate_block", "estimate_inference_phase", diff --git a/src/blueprinting/analysis/cost/aiconfigurator.py b/src/blueprinting/analysis/cost/aiconfigurator.py index 2f9ce16..0c303a0 100644 --- a/src/blueprinting/analysis/cost/aiconfigurator.py +++ b/src/blueprinting/analysis/cost/aiconfigurator.py @@ -13,8 +13,8 @@ from pathlib import Path from typing import Any -from ...compiler.codec import content_digest -from ...compiler.frozen import FrozenDict +from ...synthesizer.codec import content_digest +from ...synthesizer.frozen import FrozenDict from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .importers import read_tabular_rows from .protocol import CostSubject, EstimateMethod diff --git a/src/blueprinting/analysis/cost/database.py b/src/blueprinting/analysis/cost/database.py index 3df6945..8826840 100644 --- a/src/blueprinting/analysis/cost/database.py +++ b/src/blueprinting/analysis/cost/database.py @@ -8,8 +8,8 @@ from dataclasses import dataclass, field from functools import cached_property -from ...compiler.codec import canonical_dumps, canonical_loads, content_digest, record_type -from ...compiler.frozen import FrozenDict +from ...synthesizer.codec import canonical_dumps, canonical_loads, content_digest, record_type +from ...synthesizer.frozen import FrozenDict from .protocol import ( CostEstimate, CostProvider, diff --git a/src/blueprinting/analysis/cost/importers.py b/src/blueprinting/analysis/cost/importers.py index cc9b5a6..85c3823 100644 --- a/src/blueprinting/analysis/cost/importers.py +++ b/src/blueprinting/analysis/cost/importers.py @@ -11,8 +11,8 @@ from pathlib import Path from typing import Any -from ...compiler.codec import content_digest -from ...compiler.frozen import FrozenDict +from ...synthesizer.codec import content_digest +from ...synthesizer.frozen import FrozenDict from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .protocol import CostSubject, EstimateMethod diff --git a/src/blueprinting/analysis/cost/protocol.py b/src/blueprinting/analysis/cost/protocol.py index a92db9b..2e11d46 100644 --- a/src/blueprinting/analysis/cost/protocol.py +++ b/src/blueprinting/analysis/cost/protocol.py @@ -13,8 +13,8 @@ from enum import Enum from typing import Protocol, runtime_checkable -from ...compiler.codec import content_digest, enum_type, record_type -from ...compiler.frozen import FrozenDict +from ...synthesizer.codec import content_digest, enum_type, record_type +from ...synthesizer.frozen import FrozenDict # Keep the legacy codec namespace as a stable serialized identity. diff --git a/src/blueprinting/analysis/cost/roofline.py b/src/blueprinting/analysis/cost/roofline.py index 9a774d8..f10a69c 100644 --- a/src/blueprinting/analysis/cost/roofline.py +++ b/src/blueprinting/analysis/cost/roofline.py @@ -2,8 +2,8 @@ from __future__ import annotations -from ...compiler.codec import content_digest -from ...compiler.frozen import FrozenDict +from ...synthesizer.codec import content_digest +from ...synthesizer.frozen import FrozenDict from ..cost_model import CalibrationMode, HardwareProfile from .protocol import ( CostEstimate, diff --git a/src/blueprinting/analysis/cost_model.py b/src/blueprinting/analysis/cost_model.py index 3d74242..6809cb8 100644 --- a/src/blueprinting/analysis/cost_model.py +++ b/src/blueprinting/analysis/cost_model.py @@ -20,10 +20,10 @@ from enum import Enum from typing import Any -from ..compiler.codec import content_digest, enum_type, record_type -from ..compiler.frozen import FrozenDict -from ..compiler.ir import CollectiveKind, PortablePlanIR -from ..compiler.models.transformer import ( +from ..synthesizer.codec import content_digest, enum_type, record_type +from ..synthesizer.frozen import FrozenDict +from ..synthesizer.ir import CollectiveKind, PortablePlanIR +from ..synthesizer.models.transformer import ( RecomputePolicy, TensorParallelCommunication, TransformerExecutionSpec, @@ -398,7 +398,7 @@ def estimate_iteration( hardware: HardwareProfile, mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, ) -> IterationEstimate: - """Apply an explicit 1F1B/interleaved schedule to a compiled block plan.""" + """Apply an explicit 1F1B/interleaved schedule to a derived block plan.""" model = plan.attributes.get("model_spec") execution = plan.attributes.get("execution_spec") diff --git a/src/blueprinting/analysis/inference_cost.py b/src/blueprinting/analysis/inference_cost.py index e3328e0..e8699b9 100644 --- a/src/blueprinting/analysis/inference_cost.py +++ b/src/blueprinting/analysis/inference_cost.py @@ -4,11 +4,11 @@ from dataclasses import dataclass -from ..compiler.bindings import InferencePhase -from ..compiler.frozen import FrozenDict -from ..compiler.ir import CollectiveKind, PlanBuffer, PlanTask, PortablePlanIR -from ..compiler.models.transformer import TransformerModelSpec -from ..compiler.models.transformer_inference import TransformerInferenceExecutionSpec +from ..synthesizer.bindings import InferencePhase +from ..synthesizer.frozen import FrozenDict +from ..synthesizer.ir import CollectiveKind, PlanBuffer, PlanTask, PortablePlanIR +from ..synthesizer.models.transformer import TransformerModelSpec +from ..synthesizer.models.transformer_inference import TransformerInferenceExecutionSpec from .cost import CostQuery, CostQueryContext, CostResolver, CostSubject from .cost_model import CalibrationMode, HardwareProfile from .inference_evidence import InferenceCostProvider, InferenceEvidenceQuery diff --git a/src/blueprinting/analysis/inference_evidence.py b/src/blueprinting/analysis/inference_evidence.py index 3086dd3..e32b902 100644 --- a/src/blueprinting/analysis/inference_evidence.py +++ b/src/blueprinting/analysis/inference_evidence.py @@ -11,7 +11,7 @@ from dataclasses import dataclass from typing import Protocol, runtime_checkable -from ..compiler.bindings import InferencePhase +from ..synthesizer.bindings import InferencePhase @dataclass(frozen=True) diff --git a/src/blueprinting/analysis/transformer_inference.py b/src/blueprinting/analysis/transformer_inference.py index b37b873..68354e5 100644 --- a/src/blueprinting/analysis/transformer_inference.py +++ b/src/blueprinting/analysis/transformer_inference.py @@ -10,11 +10,11 @@ from dataclasses import dataclass -from ..compiler.bindings import InferencePhase -from ..compiler.codec import record_type -from ..compiler.ir import CollectiveKind -from ..compiler.models.transformer import TransformerModelSpec -from ..compiler.models.transformer_inference import TransformerInferenceExecutionSpec +from ..synthesizer.bindings import InferencePhase +from ..synthesizer.codec import record_type +from ..synthesizer.ir import CollectiveKind +from ..synthesizer.models.transformer import TransformerModelSpec +from ..synthesizer.models.transformer_inference import TransformerInferenceExecutionSpec from .transformer_workload import EngineKind, PhaseWork # Keep the legacy codec namespace as a stable serialized identity. @@ -73,7 +73,7 @@ def _work(*, operations: int = 0, read: int = 0, write: int = 0, message: int = return PhaseWork(operations=operations, read_bytes=read, write_bytes=write, message_bytes=message) -def compile_transformer_inference_block( +def derive_transformer_inference_block( model: TransformerModelSpec, execution: TransformerInferenceExecutionSpec, *, diff --git a/src/blueprinting/analysis/transformer_workload.py b/src/blueprinting/analysis/transformer_workload.py index 4522fb6..3752fdf 100644 --- a/src/blueprinting/analysis/transformer_workload.py +++ b/src/blueprinting/analysis/transformer_workload.py @@ -11,9 +11,9 @@ from dataclasses import dataclass, replace from enum import Enum -from ..compiler.codec import enum_type, record_type -from ..compiler.ir import CollectiveKind -from ..compiler.models.transformer import ( +from ..synthesizer.codec import enum_type, record_type +from ..synthesizer.ir import CollectiveKind +from ..synthesizer.models.transformer import ( RecomputePolicy, TensorParallelCommunication, TransformerExecutionSpec, @@ -739,7 +739,7 @@ def _communication_invocation( ) -def compile_transformer_block( +def derive_transformer_block( model: TransformerModelSpec, execution: TransformerExecutionSpec, ) -> tuple[tuple[PrimitiveInvocation, ...], BlockMemoryFacts]: diff --git a/src/blueprinting/analysis/vidur.py b/src/blueprinting/analysis/vidur.py index 1ae4a18..20816d5 100644 --- a/src/blueprinting/analysis/vidur.py +++ b/src/blueprinting/analysis/vidur.py @@ -13,9 +13,9 @@ import statistics from pathlib import Path -from ..compiler.bindings import InferencePhase -from ..compiler.codec import content_digest -from ..compiler.frozen import FrozenDict +from ..synthesizer.bindings import InferencePhase +from ..synthesizer.codec import content_digest +from ..synthesizer.frozen import FrozenDict from .cost.database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .cost.protocol import CostSubject, EstimateMethod from .inference_evidence import InferenceEvidenceQuery, InferenceEvidenceResult diff --git a/src/blueprinting/application/analysis.py b/src/blueprinting/application/analysis.py index e99ba1c..6cf69e7 100644 --- a/src/blueprinting/application/analysis.py +++ b/src/blueprinting/application/analysis.py @@ -17,22 +17,22 @@ from typing import TYPE_CHECKING, Any from blueprinting.analysis import CalibrationMode, HardwareProfile, estimate_iteration -from blueprinting.compiler.codec import content_digest -from blueprinting.compiler.errors import ( - CompilerError, +from blueprinting.synthesizer.codec import content_digest +from blueprinting.synthesizer.errors import ( IRVerificationError, PassExecutionError, + SynthesisError, ) -from blueprinting.compiler.frozen import FrozenDict, freeze, thaw -from blueprinting.compiler.ir import DistributedTaskIR, ModelIR, PortablePlanIR -from blueprinting.compiler.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass -from blueprinting.compiler.models import ( +from blueprinting.synthesizer.frozen import FrozenDict, freeze, thaw +from blueprinting.synthesizer.ir import DistributedTaskIR, ModelIR, PortablePlanIR +from blueprinting.synthesizer.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass +from blueprinting.synthesizer.models import ( TransformerExecutionSpec, TransformerModelSpec, build_transformer_model_ir, - compilation_session_for, + synthesis_session_for, ) -from blueprinting.compiler.passes import AnalysisStore, PassManager, PassPipeline +from blueprinting.synthesizer.passes import AnalysisStore, PassManager, PassPipeline LOGGER = logging.getLogger(__name__) @@ -427,9 +427,9 @@ def analyze(self, draft: AnalysisDraft) -> AnalysisOutcome: hint="检查模型维度、批量整除关系、并行度和网络层级。", ) return AnalysisOutcome(draft.fingerprint, (diagnostic,)) - except CompilerError as error: + except SynthesisError as error: diagnostic = AnalysisDiagnostic( - code="analysis.compiler_failure", + code="analysis.synthesis_failure", message=str(error), hint="查看 IR 推导页中的阶段信息和 digest。", ) @@ -459,7 +459,7 @@ def _analyze(self, draft: AnalysisDraft) -> AnalysisOutcome: frontend_started = time.perf_counter_ns() source = build_transformer_model_ir(model, datatype=execution.datatype) frontend_duration = time.perf_counter_ns() - frontend_started - session = replace(compilation_session_for(model, execution), seed=draft.seed) + session = replace(synthesis_session_for(model, execution), seed=draft.seed) pipeline = self._manager.run(self._pipeline, source, session=session) plan = pipeline.ir if not isinstance(plan, PortablePlanIR): diff --git a/src/blueprinting/application/inference.py b/src/blueprinting/application/inference.py index 5ac76e7..be43335 100644 --- a/src/blueprinting/application/inference.py +++ b/src/blueprinting/application/inference.py @@ -1,6 +1,6 @@ """Application service for static decoder inference planning. -This layer composes independently compiled prefill and decode phase points +This layer composes independently derived prefill and decode phase points into one homogeneous request-cohort report. It deliberately excludes request arrival, queueing, continuous batching and scheduler policy; those belong to the future serving-simulation layer. @@ -21,20 +21,20 @@ InferencePhaseEstimate, estimate_inference_phase, ) -from blueprinting.compiler.bindings import InferencePhase -from blueprinting.compiler.codec import content_digest -from blueprinting.compiler.errors import CompilerError, IRVerificationError, PassExecutionError -from blueprinting.compiler.frozen import FrozenDict, freeze, thaw -from blueprinting.compiler.ir import ModelIR, PortablePlanIR -from blueprinting.compiler.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from blueprinting.compiler.models import ( +from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.synthesizer.codec import content_digest +from blueprinting.synthesizer.errors import IRVerificationError, PassExecutionError, SynthesisError +from blueprinting.synthesizer.frozen import FrozenDict, freeze, thaw +from blueprinting.synthesizer.ir import ModelIR, PortablePlanIR +from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass +from blueprinting.synthesizer.models import ( TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, TransformerModelSpec, build_transformer_inference_model_ir, - inference_compilation_session_for, + inference_synthesis_session_for, ) -from blueprinting.compiler.passes import AnalysisStore, PassCheckpoint, PassManager, PassPipeline +from blueprinting.synthesizer.passes import AnalysisStore, PassCheckpoint, PassManager, PassPipeline from .analysis import ( AnalysisDiagnostic, @@ -179,7 +179,7 @@ def ok(self) -> bool: @dataclass(frozen=True) -class _CompiledPhase: +class _DerivedPhase: session_fingerprint: str plan: PortablePlanIR estimate: InferencePhaseEstimate @@ -215,7 +215,7 @@ def _task_reports(plan: PortablePlanIR, estimate: InferencePhaseEstimate) -> tup class InferenceAnalysisService: - """Compile and compose static inference phase points.""" + """Derive and compose static inference phase points.""" def __init__( self, @@ -279,12 +279,12 @@ def analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: ), ), ) - except CompilerError as error: + except SynthesisError as error: return InferenceAnalysisOutcome( draft.fingerprint, ( AnalysisDiagnostic( - code="inference.analysis.compiler_failure", + code="inference.analysis.synthesis_failure", message=str(error), ), ), @@ -301,7 +301,7 @@ def analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: ), ) - def _compile_phase( + def _derive_phase( self, source: ModelIR, model: TransformerModelSpec, @@ -312,9 +312,9 @@ def _compile_phase( phase: InferencePhase, batch_size: int, context_tokens: int, - ) -> _CompiledPhase: + ) -> _DerivedPhase: session = replace( - inference_compilation_session_for( + inference_synthesis_session_for( model, execution, phase=phase, @@ -333,7 +333,7 @@ def _compile_phase( draft.calibration_mode, cost_provider=self._cost_provider, ) - return _CompiledPhase(session.fingerprint, plan, estimate, pipeline.checkpoints) + return _DerivedPhase(session.fingerprint, plan, estimate, pipeline.checkpoints) def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: model_data = thaw(draft.model_data) @@ -354,7 +354,7 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: frontend_started = time.perf_counter_ns() source = build_transformer_inference_model_ir(model, datatype=execution.datatype) frontend_duration = time.perf_counter_ns() - frontend_started - prefill = self._compile_phase( + prefill = self._derive_phase( source, model, execution, @@ -365,7 +365,7 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: context_tokens=request.prompt_tokens, ) decode = tuple( - self._compile_phase( + self._derive_phase( source, model, execution, diff --git a/src/blueprinting/compiler/__init__.py b/src/blueprinting/synthesizer/__init__.py similarity index 90% rename from src/blueprinting/compiler/__init__.py rename to src/blueprinting/synthesizer/__init__.py index 42f9dc5..2466f7f 100644 --- a/src/blueprinting/compiler/__init__.py +++ b/src/blueprinting/synthesizer/__init__.py @@ -1,4 +1,4 @@ -"""Blueprinting canonical compiler infrastructure.""" +"""Canonical formal-synthesis infrastructure for Blueprinting.""" from .axes import BindingAxis from .bindings import ( @@ -15,13 +15,13 @@ from .codec import canonical_dumps, canonical_loads, content_digest from .errors import ( BindingError, - CompilerError, Diagnostic, IRVerificationError, MissingAnalysisError, MissingBindingError, PassContractError, SerializationError, + SynthesisError, VerificationReport, ) from .expr import ExprOp, ScalarExpr, Symbol, ceil_div, free_symbols, maximum, minimum, substitute @@ -39,7 +39,7 @@ TokenId, ValueId, ) -from .session import CompilationSession +from .session import SynthesisSession __all__ = [ "BindingAxis", @@ -48,8 +48,8 @@ "BufferId", "CalibrationBinding", "CommandId", - "CompilationSession", - "CompilerError", + "SynthesisSession", + "SynthesisError", "DeploymentProfile", "DeviceId", "Diagnostic", diff --git a/src/blueprinting/compiler/axes.py b/src/blueprinting/synthesizer/axes.py similarity index 100% rename from src/blueprinting/compiler/axes.py rename to src/blueprinting/synthesizer/axes.py diff --git a/src/blueprinting/compiler/bindings.py b/src/blueprinting/synthesizer/bindings.py similarity index 97% rename from src/blueprinting/compiler/bindings.py rename to src/blueprinting/synthesizer/bindings.py index 8d50a52..6c40f70 100644 --- a/src/blueprinting/compiler/bindings.py +++ b/src/blueprinting/synthesizer/bindings.py @@ -1,4 +1,4 @@ -"""Typed binding objects for progressive compiler specialization.""" +"""Typed binding objects for progressive formal synthesis.""" from __future__ import annotations @@ -151,7 +151,7 @@ def __post_init__(self) -> None: object.__setattr__(self, "attributes", _frozen_map(self.attributes)) -@record_type("compiler.binding.target") +@record_type("compiler.binding.target", field_aliases={"compiler_abi": "target_abi"}) @dataclass(frozen=True) class TargetProfile: name: str @@ -159,7 +159,7 @@ class TargetProfile: architecture_revision: str runtime_stack: str runtime_revision: str - compiler_abi: str + target_abi: str supported_dtypes: frozenset[str] = frozenset() supported_operations: frozenset[str] = frozenset() memory_spaces: frozenset[str] = frozenset() @@ -177,7 +177,7 @@ def __post_init__(self) -> None: "architecture_revision", "runtime_stack", "runtime_revision", - "compiler_abi", + "target_abi", ) if any(not isinstance(getattr(self, name), str) or not getattr(self, name) for name in required): raise BindingError("target identity fields must not be empty") @@ -301,7 +301,7 @@ def require(self, *axes: BindingAxis) -> None: missing = tuple(axis for axis in axes if not self.has(axis)) if missing: names = ", ".join(axis.value for axis in missing) - raise MissingBindingError(f"missing required compiler bindings: {names}") + raise MissingBindingError(f"missing required synthesis bindings: {names}") def with_binding(self, binding: BindingValue) -> BindingSet: if isinstance(binding, WorkloadBinding): diff --git a/src/blueprinting/compiler/codec.py b/src/blueprinting/synthesizer/codec.py similarity index 80% rename from src/blueprinting/compiler/codec.py rename to src/blueprinting/synthesizer/codec.py index 100e7aa..9645ce1 100644 --- a/src/blueprinting/compiler/codec.py +++ b/src/blueprinting/synthesizer/codec.py @@ -1,4 +1,4 @@ -"""Closed-world canonical codec used by compiler IR snapshots. +"""Closed-world canonical codec used by formal synthesis snapshots. The decoder only constructs explicitly registered record and enum types. It never imports a class named by an input payload, which keeps IR loading @@ -23,15 +23,28 @@ _RECORD_TYPES: dict[str, type[Any]] = {} _RECORD_TAGS: dict[type[Any], str] = {} +_RECORD_FIELD_ALIASES: dict[str, dict[str, str]] = {} _ENUM_TYPES: dict[str, type[Enum]] = {} _ENUM_TAGS: dict[type[Enum], str] = {} -def record_type(tag: str) -> Callable[[type[T]], type[T]]: - """Register a frozen dataclass for canonical round-trip serialization.""" +def record_type( + tag: str, + *, + field_aliases: Mapping[str, str] | None = None, +) -> Callable[[type[T]], type[T]]: + """Register a frozen dataclass and optional legacy field aliases.""" if not isinstance(tag, str) or not tag: raise TypeError("canonical record tag must be a non-empty string") + aliases = dict(field_aliases or {}) + if any( + not isinstance(legacy, str) or not legacy or not isinstance(current, str) or not current or legacy == current + for legacy, current in aliases.items() + ): + raise TypeError("canonical field aliases must map distinct non-empty strings") + if len(set(aliases.values())) != len(aliases): + raise TypeError("canonical field aliases must have unique destinations") def decorate(cls: type[T]) -> type[T]: if not is_dataclass(cls): @@ -39,11 +52,24 @@ def decorate(cls: type[T]) -> type[T]: parameters = getattr(cls, "__dataclass_params__", None) if parameters is None or not parameters.frozen: raise TypeError(f"canonical record {cls.__name__} must be frozen") + init_fields = {item.name for item in fields(cls) if item.init} + unknown_destinations = set(aliases.values()) - init_fields + if unknown_destinations: + rendered = ", ".join(sorted(unknown_destinations)) + raise TypeError(f"canonical field aliases target unknown fields: {rendered}") + conflicting_sources = set(aliases) & init_fields + if conflicting_sources: + rendered = ", ".join(sorted(conflicting_sources)) + raise TypeError(f"canonical field aliases shadow current fields: {rendered}") previous = _RECORD_TYPES.get(tag) if previous is not None and previous is not cls: raise RuntimeError(f"canonical record tag {tag!r} is already registered") + previous_aliases = _RECORD_FIELD_ALIASES.get(tag) + if previous_aliases is not None and previous_aliases != aliases: + raise RuntimeError(f"canonical record tag {tag!r} has conflicting field aliases") _RECORD_TYPES[tag] = cls _RECORD_TAGS[cls] = tag + _RECORD_FIELD_ALIASES[tag] = aliases return cls return decorate @@ -184,7 +210,15 @@ def _decode(value: Any) -> Any: if not isinstance(payload, dict): raise SerializationError(f"fields for canonical record {tag!r} must be an object") try: - decoded = {name: _decode(item) for name, item in payload.items()} + aliases = _RECORD_FIELD_ALIASES.get(tag, {}) + decoded = {} + for name, item in payload.items(): + current_name = aliases.get(name, name) + if current_name in decoded: + raise SerializationError( + f"canonical record {tag!r} supplies both a current field and its legacy alias: {current_name!r}" + ) + decoded[current_name] = _decode(item) return record_cls(**decoded) except SerializationError: raise diff --git a/src/blueprinting/compiler/errors.py b/src/blueprinting/synthesizer/errors.py similarity index 83% rename from src/blueprinting/compiler/errors.py rename to src/blueprinting/synthesizer/errors.py index ce9da4b..d2add59 100644 --- a/src/blueprinting/compiler/errors.py +++ b/src/blueprinting/synthesizer/errors.py @@ -1,6 +1,6 @@ -"""Error and diagnostic types for the canonical compiler. +"""Error and diagnostic types for formal synthesis. -The compiler distinguishes user-facing contract failures from implementation +The synthesizer distinguishes user-facing contract failures from implementation errors. Verification collects diagnostics first and raises them as one error, which is substantially more useful than failing on the first malformed edge. """ @@ -12,19 +12,19 @@ from enum import Enum -class CompilerError(Exception): - """Base class for canonical compiler failures.""" +class SynthesisError(Exception): + """Base class for formal-synthesis failures.""" -class SerializationError(CompilerError): +class SerializationError(SynthesisError): """Raised when canonical serialization or deserialization fails.""" -class InvalidIdError(CompilerError, ValueError): - """Raised when a stable compiler identifier is malformed.""" +class InvalidIdError(SynthesisError, ValueError): + """Raised when a stable synthesis identifier is malformed.""" -class BindingError(CompilerError, ValueError): +class BindingError(SynthesisError, ValueError): """Raised when a typed binding is invalid or incomplete.""" @@ -32,16 +32,16 @@ class MissingBindingError(BindingError): """Raised when a lowering pass is missing a required binding axis.""" -class MissingAnalysisError(CompilerError, LookupError): +class MissingAnalysisError(SynthesisError, LookupError): """Raised when a pass requires an analysis absent from the current snapshot.""" -class PassContractError(CompilerError): +class PassContractError(SynthesisError): """Raised when a pass violates its declared contract.""" -class PassExecutionError(CompilerError): - """Wraps an unexpected exception raised by a compiler pass.""" +class PassExecutionError(SynthesisError): + """Wraps an unexpected exception raised by a derivation pass.""" def __init__(self, pass_name: str, cause: BaseException): self.pass_name = pass_name @@ -98,7 +98,7 @@ def require_ok(self, subject: str = "IR") -> None: raise IRVerificationError(subject, self.errors) -class IRVerificationError(CompilerError, ValueError): +class IRVerificationError(SynthesisError, ValueError): """Raised when a canonical IR snapshot violates its contract.""" def __init__(self, subject: str, diagnostics: Iterable[Diagnostic]): diff --git a/src/blueprinting/compiler/experiments/__init__.py b/src/blueprinting/synthesizer/experiments/__init__.py similarity index 94% rename from src/blueprinting/compiler/experiments/__init__.py rename to src/blueprinting/synthesizer/experiments/__init__.py index 793cf27..54c8f16 100644 --- a/src/blueprinting/compiler/experiments/__init__.py +++ b/src/blueprinting/synthesizer/experiments/__init__.py @@ -1,4 +1,4 @@ -"""Reproducible compiler validation experiments.""" +"""Reproducible formal-synthesis validation experiments.""" from .calculon import ( CalculonCase, diff --git a/src/blueprinting/compiler/experiments/calculon.py b/src/blueprinting/synthesizer/experiments/calculon.py similarity index 95% rename from src/blueprinting/compiler/experiments/calculon.py rename to src/blueprinting/synthesizer/experiments/calculon.py index b723459..c58125f 100644 --- a/src/blueprinting/compiler/experiments/calculon.py +++ b/src/blueprinting/synthesizer/experiments/calculon.py @@ -2,7 +2,7 @@ The experiment uses Calculon in two roles only: -* an oracle for independently checking compiled work and schedule metrics; +* an oracle for independently checking derived work and schedule metrics; * a source of historical SeqSel paper values for held-out validation. Calculon results are never read while constructing IR, workload facts, or the @@ -34,7 +34,7 @@ TransformerExecutionSpec, TransformerModelSpec, build_transformer_model_ir, - compilation_session_for, + synthesis_session_for, ) from ..passes import PassManager, PassPipeline @@ -57,18 +57,18 @@ class CalculonCase: @dataclass(frozen=True) class MetricComparison: - compiled: float + blueprinting: float reference: float @property def relative_error_percent(self) -> float: if self.reference == 0: - return 0.0 if self.compiled == 0 else float("inf") - return (self.compiled - self.reference) / self.reference * 100 + return 0.0 if self.blueprinting == 0 else float("inf") + return (self.blueprinting - self.reference) / self.reference * 100 def to_dict(self) -> dict[str, float]: return { - "compiled": self.compiled, + "blueprinting": self.blueprinting, "reference": self.reference, "relative_error_percent": self.relative_error_percent, } @@ -126,7 +126,7 @@ def to_dict(self) -> dict[str, Any]: "system_evidence_vs_calculon": self.calibrated_error_percent, "system_evidence_vs_paper": self.paper_error_percent, }, - "compiled_breakdown_seconds": { + "estimated_breakdown_seconds": { "forward": self.calibrated.forward, "backward": self.calibrated.backward, "optimizer": self.calibrated.optimizer, @@ -149,7 +149,7 @@ def to_dict(self) -> dict[str, Any]: "pipeline_bubble": self.calculon_stats["bubble_time"], }, "memory_bytes": { - "compiled": self.calibrated.memory.total, + "estimated": self.calibrated.memory.total, "calculon": self.calculon_stats["proc_mem_tier1_cap_req"], "relative_error_percent": ( (self.calibrated.memory.total - self.calculon_stats["proc_mem_tier1_cap_req"]) @@ -158,7 +158,7 @@ def to_dict(self) -> dict[str, Any]: ), }, "recompute_counter_audit": { - "compiled_explicit_operations": _phase_operations_from_estimate( + "derived_explicit_operations": _phase_operations_from_estimate( self.calibrated, TrainingPhase.RECOMPUTE, local_only=True ), "calculon_block_re_flops": self.calculon_stats["block_re_flops"], @@ -254,7 +254,7 @@ def _run_calculon( execution_data: dict[str, Any], system_data: dict[str, Any], ) -> dict[str, Any]: - logger = logging.getLogger("blueprinting.compiler.experiments.calculon") + logger = logging.getLogger("blueprinting.synthesizer.experiments.calculon") application = Llm.Application(model_data) execution_fields = {field: execution_data[field] for field in Llm.Execution.fields()} execution = Llm.Execution.from_json(execution_fields) @@ -265,12 +265,12 @@ def _run_calculon( return model.get_stats_json(False) -def _compile_plan( +def _derive_plan( model: TransformerModelSpec, execution: TransformerExecutionSpec, ) -> tuple[PortablePlanIR, tuple[dict[str, Any], ...], str, str]: source = build_transformer_model_ir(model) - session = compilation_session_for(model, execution) + session = synthesis_session_for(model, execution) result = PassManager().run( PassPipeline.of(DistributeTransformerTrainingPass(), PlanTransformerTrainingPass()), source, @@ -367,7 +367,7 @@ def run_calculon_experiment(cases: tuple[CalculonCase, ...]) -> CalculonExperime system_data = _read_json(case.system_path) model = TransformerModelSpec.from_mapping(case.model_path.stem, model_data) execution = TransformerExecutionSpec.from_mapping(execution_data) - plan, checkpoints, model_digest, distributed_digest = _compile_plan(model, execution) + plan, checkpoints, model_digest, distributed_digest = _derive_plan(model, execution) hardware = HardwareProfile.from_mapping(case.system_path.stem, system_data, datatype=execution.datatype) if not evidence_revision: evidence_revision = hardware.evidence_revision @@ -389,7 +389,7 @@ def run_calculon_experiment(cases: tuple[CalculonCase, ...]) -> CalculonExperime ) ) return CalculonExperimentReport( - schema="blueprinting.calculon-calibration-experiment.v1", + schema="blueprinting.calculon-calibration-experiment.v2", hardware_name=hardware_name, evidence_revision=evidence_revision, calibration_policy={ diff --git a/src/blueprinting/compiler/experiments/regression.py b/src/blueprinting/synthesizer/experiments/regression.py similarity index 99% rename from src/blueprinting/compiler/experiments/regression.py rename to src/blueprinting/synthesizer/experiments/regression.py index 66865be..9680537 100644 --- a/src/blueprinting/compiler/experiments/regression.py +++ b/src/blueprinting/synthesizer/experiments/regression.py @@ -428,7 +428,7 @@ def _inference_checks( ), _close( f"inference.{case_name}.system_evidence_comparable_block_seconds", - comparison.compiled_comparable_block_seconds, + comparison.estimated_comparable_block_seconds, expected["system_evidence_comparable_block_seconds"], absolute_tolerance=1e-15, ), diff --git a/src/blueprinting/compiler/experiments/vidur.py b/src/blueprinting/synthesizer/experiments/vidur.py similarity index 91% rename from src/blueprinting/compiler/experiments/vidur.py rename to src/blueprinting/synthesizer/experiments/vidur.py index 595ae85..6de84f7 100644 --- a/src/blueprinting/compiler/experiments/vidur.py +++ b/src/blueprinting/synthesizer/experiments/vidur.py @@ -2,7 +2,7 @@ Vidur data is read only after ModelIR -> DistributedTaskIR -> PortablePlanIR lowering and Blueprinting cost evaluation have completed. The comparison -therefore measures agreement; it cannot make the compiled result agree by +therefore measures agreement; it cannot make the estimated result agree by construction. """ @@ -27,7 +27,7 @@ TransformerInferenceExecutionSpec, TransformerModelSpec, build_transformer_inference_model_ir, - inference_compilation_session_for, + inference_synthesis_session_for, ) from ..passes import PassManager, PassPipeline @@ -39,7 +39,7 @@ class VidurComponentComparison: task_name: str source_layer: str primitive: str - compiled_seconds: float + estimated_seconds: float baseline_seconds: float | None baseline_match: str | None @@ -51,20 +51,20 @@ def comparable(self) -> bool: def absolute_error_seconds(self) -> float | None: if self.baseline_seconds is None: return None - return self.compiled_seconds - self.baseline_seconds + return self.estimated_seconds - self.baseline_seconds @property def relative_error_percent(self) -> float | None: if self.baseline_seconds is None or self.baseline_seconds == 0: return None - return (self.compiled_seconds - self.baseline_seconds) / self.baseline_seconds * 100 + return (self.estimated_seconds - self.baseline_seconds) / self.baseline_seconds * 100 def to_dict(self) -> dict[str, Any]: return { "task": self.task_name, "source_layer": self.source_layer, "primitive": self.primitive, - "compiled_seconds": self.compiled_seconds, + "estimated_seconds": self.estimated_seconds, "baseline_seconds": self.baseline_seconds, "baseline_match": self.baseline_match, "absolute_error_seconds": self.absolute_error_seconds, @@ -85,7 +85,7 @@ class VidurPhaseComparison: plan_digest: str hardware_revision: str baseline_revision: str - compiled_block_seconds: float + estimated_block_seconds: float components: tuple[VidurComponentComparison, ...] @property @@ -93,16 +93,16 @@ def matched_components(self) -> tuple[VidurComponentComparison, ...]: return tuple(component for component in self.components if component.comparable) @property - def compiled_comparable_block_seconds(self) -> float: - return sum(component.compiled_seconds for component in self.matched_components) + def estimated_comparable_block_seconds(self) -> float: + return sum(component.estimated_seconds for component in self.matched_components) @property def baseline_comparable_block_seconds(self) -> float: return sum(component.baseline_seconds or 0.0 for component in self.matched_components) @property - def excluded_compiled_block_seconds(self) -> float: - return self.compiled_block_seconds - self.compiled_comparable_block_seconds + def excluded_estimated_block_seconds(self) -> float: + return self.estimated_block_seconds - self.estimated_comparable_block_seconds @property def component_coverage(self) -> float: @@ -113,7 +113,7 @@ def comparable_subtotal_relative_error_percent(self) -> float | None: reference = self.baseline_comparable_block_seconds if reference == 0: return None - return (self.compiled_comparable_block_seconds - reference) / reference * 100 + return (self.estimated_comparable_block_seconds - reference) / reference * 100 @property def component_absolute_percentage_errors(self) -> tuple[float, ...]: @@ -148,10 +148,10 @@ def to_dict(self) -> dict[str, Any]: "component_coverage": self.component_coverage, "matched_component_count": len(self.matched_components), "component_count": len(self.components), - "compiled_block_seconds": self.compiled_block_seconds, - "compiled_comparable_block_seconds": self.compiled_comparable_block_seconds, + "estimated_block_seconds": self.estimated_block_seconds, + "estimated_comparable_block_seconds": self.estimated_comparable_block_seconds, "baseline_comparable_block_seconds": self.baseline_comparable_block_seconds, - "excluded_compiled_block_seconds": self.excluded_compiled_block_seconds, + "excluded_estimated_block_seconds": self.excluded_estimated_block_seconds, "comparable_subtotal_relative_error_percent": self.comparable_subtotal_relative_error_percent, "component_mean_absolute_error_percent": self.component_mean_absolute_error_percent, "component_max_absolute_error_percent": self.component_max_absolute_error_percent, @@ -161,7 +161,7 @@ def to_dict(self) -> dict[str, Any]: @dataclass(frozen=True) class VidurExperimentCase: - """One static phase point compiled by Blueprinting before comparison.""" + """One static phase point synthesized by Blueprinting before comparison.""" name: str model: TransformerModelSpec @@ -329,7 +329,7 @@ def compare_inference_phase_to_vidur( task_name=invocation.name, source_layer=invocation.source_layer, primitive=invocation.primitive, - compiled_seconds=task_estimate.total_seconds, + estimated_seconds=task_estimate.total_seconds, baseline_seconds=reference.seconds if reference is not None else None, baseline_match=reference.match if reference is not None else None, ) @@ -343,7 +343,7 @@ def compare_inference_phase_to_vidur( plan_digest=plan.digest, hardware_revision=hardware.evidence_revision, baseline_revision=baseline.revision, - compiled_block_seconds=estimate.block_seconds, + estimated_block_seconds=estimate.block_seconds, components=tuple(components), ) @@ -352,7 +352,7 @@ def run_vidur_experiment( cases: tuple[VidurExperimentCase, ...], baseline: InferenceBaseline, ) -> VidurExperimentReport: - """Compile each phase independently, then compare both analytical modes.""" + """Synthesize each phase independently, then compare both analytical modes.""" if not cases: raise ValueError("Vidur experiment requires at least one case") @@ -364,7 +364,7 @@ def run_vidur_experiment( result = manager.run( pipeline, source, - session=inference_compilation_session_for( + session=inference_synthesis_session_for( case.model, case.execution, phase=case.phase, @@ -397,7 +397,7 @@ def run_vidur_experiment( ) ) return VidurExperimentReport( - schema="blueprinting.vidur-baseline-experiment.v1", + schema="blueprinting.vidur-baseline-experiment.v2", baseline_revision=baseline.revision, policy={ "baseline_role": "post-hoc-comparison-only", diff --git a/src/blueprinting/compiler/expr.py b/src/blueprinting/synthesizer/expr.py similarity index 100% rename from src/blueprinting/compiler/expr.py rename to src/blueprinting/synthesizer/expr.py diff --git a/src/blueprinting/compiler/frozen.py b/src/blueprinting/synthesizer/frozen.py similarity index 96% rename from src/blueprinting/compiler/frozen.py rename to src/blueprinting/synthesizer/frozen.py index e10c951..285f665 100644 --- a/src/blueprinting/compiler/frozen.py +++ b/src/blueprinting/synthesizer/frozen.py @@ -9,9 +9,9 @@ def freeze(value: Any) -> Any: """Recursively freeze JSON-like extension data. - Registered immutable compiler records pass through unchanged. Mutable + Registered immutable synthesis records pass through unchanged. Mutable mappings and sequences are copied so callers cannot mutate an IR snapshot - through an alias retained outside the compiler. + through an alias retained outside the synthesizer. """ if isinstance(value, FrozenDict): diff --git a/src/blueprinting/compiler/ids.py b/src/blueprinting/synthesizer/ids.py similarity index 96% rename from src/blueprinting/compiler/ids.py rename to src/blueprinting/synthesizer/ids.py index 96656c4..e4dbc73 100644 --- a/src/blueprinting/compiler/ids.py +++ b/src/blueprinting/synthesizer/ids.py @@ -19,7 +19,7 @@ @dataclass(frozen=True, order=True) class StableId: - """Base implementation for a typed 128-bit compiler identifier.""" + """Base implementation for a typed 128-bit synthesis identifier.""" value: str PREFIX: ClassVar[str] = "id" @@ -130,7 +130,7 @@ def __post_init__(self) -> None: if not isinstance(self.kind, LineageKind): raise TypeError("lineage kind must be LineageKind") if any(not isinstance(source, StableId) for source in self.sources): - raise TypeError("lineage sources must be stable compiler IDs") + raise TypeError("lineage sources must be stable synthesis IDs") if not self.transform: raise ValueError("lineage transform must not be empty") if len(set(self.sources)) != len(self.sources): diff --git a/src/blueprinting/compiler/ir/__init__.py b/src/blueprinting/synthesizer/ir/__init__.py similarity index 100% rename from src/blueprinting/compiler/ir/__init__.py rename to src/blueprinting/synthesizer/ir/__init__.py diff --git a/src/blueprinting/compiler/ir/common.py b/src/blueprinting/synthesizer/ir/common.py similarity index 99% rename from src/blueprinting/compiler/ir/common.py rename to src/blueprinting/synthesizer/ir/common.py index e1d5c77..0b97b11 100644 --- a/src/blueprinting/compiler/ir/common.py +++ b/src/blueprinting/synthesizer/ir/common.py @@ -1,4 +1,4 @@ -"""Shared contracts for the canonical compiler IRs. +"""Shared contracts for the canonical formal representations. The classes in this module intentionally keep serialization, schema identity, diagnostics, and content addressing out of individual dialect implementations. @@ -32,7 +32,7 @@ def is_content_digest(value: str) -> bool: def frozen_map(value: Any) -> FrozenDict: - """Copy an extension mapping into the compiler's immutable value domain.""" + """Copy an extension mapping into the synthesizer's immutable value domain.""" result = freeze(value) if not isinstance(result, FrozenDict): diff --git a/src/blueprinting/compiler/ir/concrete_plan.py b/src/blueprinting/synthesizer/ir/concrete_plan.py similarity index 100% rename from src/blueprinting/compiler/ir/concrete_plan.py rename to src/blueprinting/synthesizer/ir/concrete_plan.py diff --git a/src/blueprinting/compiler/ir/distributed.py b/src/blueprinting/synthesizer/ir/distributed.py similarity index 100% rename from src/blueprinting/compiler/ir/distributed.py rename to src/blueprinting/synthesizer/ir/distributed.py diff --git a/src/blueprinting/compiler/ir/machine.py b/src/blueprinting/synthesizer/ir/machine.py similarity index 100% rename from src/blueprinting/compiler/ir/machine.py rename to src/blueprinting/synthesizer/ir/machine.py diff --git a/src/blueprinting/compiler/ir/model.py b/src/blueprinting/synthesizer/ir/model.py similarity index 100% rename from src/blueprinting/compiler/ir/model.py rename to src/blueprinting/synthesizer/ir/model.py diff --git a/src/blueprinting/compiler/ir/portable_plan.py b/src/blueprinting/synthesizer/ir/portable_plan.py similarity index 100% rename from src/blueprinting/compiler/ir/portable_plan.py rename to src/blueprinting/synthesizer/ir/portable_plan.py diff --git a/src/blueprinting/compiler/lowering/__init__.py b/src/blueprinting/synthesizer/lowering/__init__.py similarity index 100% rename from src/blueprinting/compiler/lowering/__init__.py rename to src/blueprinting/synthesizer/lowering/__init__.py diff --git a/src/blueprinting/compiler/lowering/transformer.py b/src/blueprinting/synthesizer/lowering/transformer.py similarity index 97% rename from src/blueprinting/compiler/lowering/transformer.py rename to src/blueprinting/synthesizer/lowering/transformer.py index 7d0b37c..94be355 100644 --- a/src/blueprinting/compiler/lowering/transformer.py +++ b/src/blueprinting/synthesizer/lowering/transformer.py @@ -5,7 +5,7 @@ from ...analysis.transformer_workload import ( EngineKind, PrimitiveInvocation, - compile_transformer_block, + derive_transformer_block, ) from ..axes import BindingAxis from ..frozen import FrozenDict @@ -45,7 +45,7 @@ TransformerExecutionSpec, TransformerModelSpec, ) -from ..passes import CompilerPass, PassContext, PassContract +from ..passes import DerivationPass, PassContext, PassContract def _semantic_specs(ir: ModelIR, context: PassContext) -> tuple[TransformerModelSpec, TransformerExecutionSpec]: @@ -76,7 +76,7 @@ def _semantic_specs(ir: ModelIR, context: PassContext) -> tuple[TransformerModel return model, execution -class DistributeTransformerTrainingPass(CompilerPass[ModelIR, DistributedTaskIR]): +class DistributeTransformerTrainingPass(DerivationPass[ModelIR, DistributedTaskIR]): """Expand one semantic block into explicit local and collective tasks.""" contract = PassContract.create( @@ -88,7 +88,7 @@ class DistributeTransformerTrainingPass(CompilerPass[ModelIR, DistributedTaskIR] def run(self, ir: ModelIR, context: PassContext) -> DistributedTaskIR: model, execution = _semantic_specs(ir, context) - invocations, block_memory = compile_transformer_block(model, execution) + invocations, block_memory = derive_transformer_block(model, execution) ranks = tuple(range(execution.tensor_parallel)) mesh = LogicalMesh("local-tensor-parallel-group", (MeshAxis("tp", execution.tensor_parallel),)) source_input = ir.inputs[0] @@ -214,7 +214,7 @@ def _plan_resources(invocation: PrimitiveInvocation) -> tuple[ResourceRequiremen return tuple(resources) -class PlanTransformerTrainingPass(CompilerPass[DistributedTaskIR, PortablePlanIR]): +class PlanTransformerTrainingPass(DerivationPass[DistributedTaskIR, PortablePlanIR]): """Materialize exact WorkloadFacts without choosing a hardware target.""" contract = PassContract.create( diff --git a/src/blueprinting/compiler/lowering/transformer_inference.py b/src/blueprinting/synthesizer/lowering/transformer_inference.py similarity index 98% rename from src/blueprinting/compiler/lowering/transformer_inference.py rename to src/blueprinting/synthesizer/lowering/transformer_inference.py index c3f8c2e..380b608 100644 --- a/src/blueprinting/compiler/lowering/transformer_inference.py +++ b/src/blueprinting/synthesizer/lowering/transformer_inference.py @@ -5,7 +5,7 @@ from ...analysis.transformer_inference import ( InferenceBlockMemoryFacts, InferenceInvocation, - compile_transformer_inference_block, + derive_transformer_inference_block, ) from ...analysis.transformer_workload import EngineKind from ..axes import BindingAxis @@ -46,7 +46,7 @@ ) from ..models.transformer import TransformerModelSpec from ..models.transformer_inference import TransformerInferenceExecutionSpec -from ..passes import CompilerPass, PassContext, PassContract +from ..passes import DerivationPass, PassContext, PassContract def _semantic_specs( @@ -89,7 +89,7 @@ def _semantic_specs( return model, execution, workload.inference_phase, batch_size, query_tokens, context_tokens -class DistributeTransformerInferencePass(CompilerPass[ModelIR, DistributedTaskIR]): +class DistributeTransformerInferencePass(DerivationPass[ModelIR, DistributedTaskIR]): """Expand one phase into observable local and collective components.""" contract = PassContract.create( @@ -101,7 +101,7 @@ class DistributeTransformerInferencePass(CompilerPass[ModelIR, DistributedTaskIR def run(self, ir: ModelIR, context: PassContext) -> DistributedTaskIR: model, execution, phase, batch_size, query_tokens, context_tokens = _semantic_specs(ir, context) - invocations, block_memory = compile_transformer_inference_block( + invocations, block_memory = derive_transformer_inference_block( model, execution, phase=phase, @@ -269,7 +269,7 @@ def _implementation(invocation: InferenceInvocation) -> ImplementationRequiremen return ImplementationRequirement(invocation.primitive, alternatives=("vector-engine",)) -class PlanTransformerInferencePass(CompilerPass[DistributedTaskIR, PortablePlanIR]): +class PlanTransformerInferencePass(DerivationPass[DistributedTaskIR, PortablePlanIR]): """Materialize a phase plan without target placement or measured time.""" contract = PassContract.create( diff --git a/src/blueprinting/compiler/models/__init__.py b/src/blueprinting/synthesizer/models/__init__.py similarity index 75% rename from src/blueprinting/compiler/models/__init__.py rename to src/blueprinting/synthesizer/models/__init__.py index 8ff0c81..7f9d855 100644 --- a/src/blueprinting/compiler/models/__init__.py +++ b/src/blueprinting/synthesizer/models/__init__.py @@ -1,4 +1,4 @@ -"""Semantic model frontends for the canonical compiler.""" +"""Semantic model frontends for formal plan synthesis.""" from .transformer import ( RecomputePolicy, @@ -6,13 +6,13 @@ TransformerExecutionSpec, TransformerModelSpec, build_transformer_model_ir, - compilation_session_for, + synthesis_session_for, ) from .transformer_inference import ( TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, build_transformer_inference_model_ir, - inference_compilation_session_for, + inference_synthesis_session_for, ) __all__ = [ @@ -24,6 +24,6 @@ "TransformerModelSpec", "build_transformer_inference_model_ir", "build_transformer_model_ir", - "compilation_session_for", - "inference_compilation_session_for", + "synthesis_session_for", + "inference_synthesis_session_for", ] diff --git a/src/blueprinting/compiler/models/transformer.py b/src/blueprinting/synthesizer/models/transformer.py similarity index 98% rename from src/blueprinting/compiler/models/transformer.py rename to src/blueprinting/synthesizer/models/transformer.py index c600dc1..fba39fe 100644 --- a/src/blueprinting/compiler/models/transformer.py +++ b/src/blueprinting/synthesizer/models/transformer.py @@ -21,7 +21,7 @@ from ..frozen import FrozenDict from ..ids import Lineage, NodeId, ValueId from ..ir import ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole -from ..session import CompilationSession +from ..session import SynthesisSession def _positive_integer(value: Any, name: str) -> int: @@ -87,7 +87,7 @@ def from_mapping(cls, name: str, data: Mapping[str, Any]) -> TransformerModelSpe @record_type("compiler.transformer.execution_spec.v1") @dataclass(frozen=True) class TransformerExecutionSpec: - """Compile-time training strategy used by the comparison experiment. + """Structurally relevant training strategy used by the comparison experiment. Every field changes structure, multiplicity, storage, or communication. There are intentionally no efficiency or correction-factor fields here. @@ -241,10 +241,10 @@ def build_transformer_model_ir(model: TransformerModelSpec, *, datatype: str = " ) -def compilation_session_for( +def synthesis_session_for( model: TransformerModelSpec, execution: TransformerExecutionSpec, -) -> CompilationSession: +) -> SynthesisSession: """Create the explicit session consumed by Transformer lowering passes.""" workload = WorkloadBinding( @@ -261,7 +261,7 @@ def compilation_session_for( pipeline_policy=f"1f1b-interleaved-{execution.pipeline_interleaving}", attributes=FrozenDict({"execution_spec": execution}), ) - return CompilationSession( + return SynthesisSession( bindings=BindingSet(workload=workload, strategy=strategy), features=frozenset({"transformer-training-analysis-v1"}), ) diff --git a/src/blueprinting/compiler/models/transformer_inference.py b/src/blueprinting/synthesizer/models/transformer_inference.py similarity index 98% rename from src/blueprinting/compiler/models/transformer_inference.py rename to src/blueprinting/synthesizer/models/transformer_inference.py index e85ec44..6a6bee8 100644 --- a/src/blueprinting/compiler/models/transformer_inference.py +++ b/src/blueprinting/synthesizer/models/transformer_inference.py @@ -30,7 +30,7 @@ from ..frozen import FrozenDict from ..ids import Lineage, NodeId, ValueId from ..ir import Effect, EffectKind, ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole -from ..session import CompilationSession +from ..session import SynthesisSession from .transformer import TransformerModelSpec _SUPPORTED_DATATYPES = frozenset({"float8", "float16", "bfloat16", "float32"}) @@ -205,14 +205,14 @@ def build_transformer_inference_model_ir( ) -def inference_compilation_session_for( +def inference_synthesis_session_for( model: TransformerModelSpec, execution: TransformerInferenceExecutionSpec, *, phase: InferencePhase, batch_size: int, context_tokens: int, -) -> CompilationSession: +) -> SynthesisSession: """Create an explicit phase binding for static inference specialization.""" execution.validate_model(model) @@ -241,7 +241,7 @@ def inference_compilation_session_for( pipeline_policy="static-inference", attributes=FrozenDict({"inference_execution_spec": execution}), ) - return CompilationSession( + return SynthesisSession( bindings=BindingSet(workload=workload, strategy=strategy), features=frozenset({"transformer-inference-analysis-v1", f"inference-{phase.value}"}), ) diff --git a/src/blueprinting/compiler/passes/__init__.py b/src/blueprinting/synthesizer/passes/__init__.py similarity index 95% rename from src/blueprinting/compiler/passes/__init__.py rename to src/blueprinting/synthesizer/passes/__init__.py index 461a015..ba59cab 100644 --- a/src/blueprinting/compiler/passes/__init__.py +++ b/src/blueprinting/synthesizer/passes/__init__.py @@ -6,7 +6,7 @@ AnalysisKey, AnalysisProduct, AnalysisStore, - CompilerPass, + DerivationPass, FunctionPass, MutationModel, PassCheckpoint, @@ -28,7 +28,7 @@ "AnalysisKey", "AnalysisProduct", "AnalysisStore", - "CompilerPass", + "DerivationPass", "FunctionPass", "MutationModel", "PassContext", diff --git a/src/blueprinting/compiler/passes/base.py b/src/blueprinting/synthesizer/passes/base.py similarity index 94% rename from src/blueprinting/compiler/passes/base.py rename to src/blueprinting/synthesizer/passes/base.py index 1ea83ed..d0e90d6 100644 --- a/src/blueprinting/compiler/passes/base.py +++ b/src/blueprinting/synthesizer/passes/base.py @@ -1,4 +1,4 @@ -"""Declarative, immutable compiler pass infrastructure. +"""Declarative, immutable derivation-pass infrastructure. The pass manager treats lowering as a sequence of typed snapshot transitions. It validates schemas, bindings, analyses, mutation behavior, verification @@ -20,15 +20,15 @@ from ..axes import BindingAxis from ..codec import content_digest from ..errors import ( - CompilerError, MissingAnalysisError, PassContractError, PassExecutionError, SerializationError, + SynthesisError, ) from ..frozen import freeze from ..ir.common import CanonicalIRMixin, SchemaVersion -from ..session import CompilationSession +from ..session import SynthesisSession InputIR = TypeVar("InputIR", bound=CanonicalIRMixin) OutputIR = TypeVar("OutputIR", bound=CanonicalIRMixin) @@ -273,7 +273,7 @@ def create( @dataclass(frozen=True) class PassContext: - session: CompilationSession + session: SynthesisSession analyses: AnalysisStore def analysis(self, ir: CanonicalIRMixin, key: AnalysisKey) -> Any: @@ -289,7 +289,7 @@ def __post_init__(self) -> None: object.__setattr__(self, "analyses", tuple(self.analyses)) -class CompilerPass(ABC, Generic[InputIR, OutputIR]): +class DerivationPass(ABC, Generic[InputIR, OutputIR]): """Base class for one declaratively contracted lowering or analysis pass.""" contract: PassContract @@ -300,7 +300,7 @@ def run(self, ir: InputIR, context: PassContext) -> OutputIR | PassResult[Output @dataclass(frozen=True) -class FunctionPass(CompilerPass[InputIR, OutputIR]): +class FunctionPass(DerivationPass[InputIR, OutputIR]): """Small adapter for pure functions; production passes may use named classes.""" contract: PassContract @@ -314,7 +314,7 @@ def run(self, ir: InputIR, context: PassContext) -> OutputIR | PassResult[Output class PassPipeline: """Immutable, type-checked pass composition.""" - passes: tuple[CompilerPass[Any, Any], ...] = () + passes: tuple[DerivationPass[Any, Any], ...] = () def __post_init__(self) -> None: object.__setattr__(self, "passes", tuple(self.passes)) @@ -335,15 +335,15 @@ def _verify_link(previous: PassContract, following: PassContract) -> None: ) @classmethod - def of(cls, *passes: CompilerPass[Any, Any]) -> PassPipeline: + def of(cls, *passes: DerivationPass[Any, Any]) -> PassPipeline: return cls(tuple(passes)) - def then(self, compiler_pass: CompilerPass[Any, Any]) -> PassPipeline: + def then(self, derivation_pass: DerivationPass[Any, Any]) -> PassPipeline: if self.passes: - self._verify_link(self.passes[-1].contract, compiler_pass.contract) - return PassPipeline(self.passes + (compiler_pass,)) + self._verify_link(self.passes[-1].contract, derivation_pass.contract) + return PassPipeline(self.passes + (derivation_pass,)) - def __iter__(self) -> Iterator[CompilerPass[Any, Any]]: + def __iter__(self) -> Iterator[DerivationPass[Any, Any]]: return iter(self.passes) def __len__(self) -> int: @@ -374,7 +374,7 @@ class PassObserver(ABC): """Synchronous read-only hook invoked before a pass transition is committed.""" @abstractmethod - def inspect(self, checkpoint: PassCheckpoint, session: CompilationSession) -> None: + def inspect(self, checkpoint: PassCheckpoint, session: SynthesisSession) -> None: raise NotImplementedError @@ -402,15 +402,15 @@ def run( pipeline: PassPipeline, ir: InputIR, *, - session: CompilationSession, + session: SynthesisSession, ) -> PipelineResult[Any]: current: CanonicalIRMixin = ir records = [] checkpoints = [] context = PassContext(session=session, analyses=self.analyses) - for compiler_pass in pipeline: - contract = compiler_pass.contract + for derivation_pass in pipeline: + contract = derivation_pass.contract if type(current) is not contract.input_type: raise PassContractError( f"pass {contract.name!r} expects {contract.input_type.__name__}, got {type(current).__name__}" @@ -440,8 +440,8 @@ def run( started = time.perf_counter_ns() try: - raw_result = compiler_pass.run(working, context) - except CompilerError: + raw_result = derivation_pass.run(working, context) + except SynthesisError: raise except Exception as error: raise PassExecutionError(contract.name, error) from error @@ -451,7 +451,7 @@ def run( if contract.mutation_model is MutationModel.IMMUTABLE: try: post_pass_input_digest = current.digest - except CompilerError as error: + except SynthesisError as error: raise PassContractError( f"immutable pass {contract.name!r} corrupted its input snapshot: {error}" ) from error @@ -502,7 +502,7 @@ def run( for observer in self.observers: try: observer.inspect(checkpoint, session) - except CompilerError: + except SynthesisError: raise except Exception as error: observer_name = f"{contract.name}:observer:{type(observer).__name__}" diff --git a/src/blueprinting/compiler/session.py b/src/blueprinting/synthesizer/session.py similarity index 81% rename from src/blueprinting/compiler/session.py rename to src/blueprinting/synthesizer/session.py index aa6556d..4877bf7 100644 --- a/src/blueprinting/compiler/session.py +++ b/src/blueprinting/synthesizer/session.py @@ -1,4 +1,4 @@ -"""Explicit immutable compilation session.""" +"""Explicit immutable formal-synthesis session.""" from __future__ import annotations @@ -14,7 +14,7 @@ @record_type("compiler.session") @dataclass(frozen=True) -class CompilationSession: +class SynthesisSession: bindings: BindingSet = field(default_factory=BindingSet) target_requirements: TargetRequirements = field(default_factory=TargetRequirements) evidence_snapshot: str = "none" @@ -28,22 +28,22 @@ def __post_init__(self) -> None: if not isinstance(self.target_requirements, TargetRequirements): raise TypeError("target_requirements must be TargetRequirements") if isinstance(self.seed, bool) or not isinstance(self.seed, int) or self.seed < 0: - raise ValueError("compilation seed must be a non-negative integer") + raise ValueError("synthesis seed must be a non-negative integer") if not isinstance(self.evidence_snapshot, str) or not self.evidence_snapshot: raise ValueError("evidence_snapshot must not be empty") features = frozenset(self.features) if any(not isinstance(feature, str) or not feature for feature in features): - raise ValueError("compilation features must be non-empty strings") + raise ValueError("synthesis features must be non-empty strings") object.__setattr__(self, "features", features) options = freeze(self.options) if not isinstance(options, FrozenDict): raise TypeError("session options must be a mapping") object.__setattr__(self, "options", options) - def with_binding(self, binding: BindingValue) -> CompilationSession: + def with_binding(self, binding: BindingValue) -> SynthesisSession: return replace(self, bindings=self.bindings.with_binding(binding)) - def with_options(self, **changes: Any) -> CompilationSession: + def with_options(self, **changes: Any) -> SynthesisSession: return replace(self, options=self.options.evolve(**changes)) def require(self, *axes: BindingAxis) -> None: @@ -53,12 +53,14 @@ def require(self, *axes: BindingAxis) -> None: if BindingAxis.TARGET in axes: target = self.bindings.target if target is not None and not target.satisfies(self.target_requirements): - raise BindingError(f"target {target.name!r} does not satisfy compilation requirements") + raise BindingError(f"target {target.name!r} does not satisfy synthesis requirements") if BindingAxis.DEPLOYMENT in axes: deployment = self.bindings.deployment if deployment is not None and not deployment.satisfies(self.target_requirements): - raise BindingError(f"deployment {deployment.name!r} does not satisfy compilation requirements") + raise BindingError(f"deployment {deployment.name!r} does not satisfy synthesis requirements") @property def fingerprint(self) -> str: + # The digest domain is a stable wire identity retained across the + # Python package and public class rename. return content_digest(self, "compilation-session") diff --git a/tests/analysis/test_cost_model_providers.py b/tests/analysis/test_cost_model_providers.py index b81e24e..abd4562 100644 --- a/tests/analysis/test_cost_model_providers.py +++ b/tests/analysis/test_cost_model_providers.py @@ -27,16 +27,16 @@ estimate_inference_phase, ) from blueprinting.analysis.cost import InvalidCostEvidenceError -from blueprinting.compiler.bindings import InferencePhase -from blueprinting.compiler.frozen import FrozenDict -from blueprinting.compiler.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from blueprinting.compiler.models import ( +from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.synthesizer.frozen import FrozenDict +from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass +from blueprinting.synthesizer.models import ( TransformerInferenceExecutionSpec, TransformerModelSpec, build_transformer_inference_model_ir, - inference_compilation_session_for, + inference_synthesis_session_for, ) -from blueprinting.compiler.passes import PassManager, PassPipeline +from blueprinting.synthesizer.passes import PassManager, PassPipeline ROOT = Path(__file__).resolve().parents[2] @@ -410,7 +410,7 @@ def _inference_fixture(): .run( PassPipeline.of(DistributeTransformerInferencePass(), PlanTransformerInferencePass()), build_transformer_inference_model_ir(model), - session=inference_compilation_session_for( + session=inference_synthesis_session_for( model, execution, phase=InferencePhase.DECODE, diff --git a/tests/analysis/test_package_boundary.py b/tests/analysis/test_package_boundary.py index 53e34bc..6107fd8 100644 --- a/tests/analysis/test_package_boundary.py +++ b/tests/analysis/test_package_boundary.py @@ -2,10 +2,26 @@ import importlib.util +import pytest + import blueprinting.analysis as analysis +import blueprinting.synthesizer as synthesizer def test_analysis_is_a_top_level_blueprinting_package() -> None: assert analysis.__name__ == "blueprinting.analysis" assert importlib.util.find_spec("blueprinting.analysis") is not None - assert importlib.util.find_spec("blueprinting.compiler.analysis") is None + assert importlib.util.find_spec("blueprinting.synthesizer.analysis") is None + + +def test_synthesizer_is_the_only_formal_synthesis_package() -> None: + assert synthesizer.__name__ == "blueprinting.synthesizer" + assert importlib.util.find_spec("blueprinting.synthesizer") is not None + assert importlib.util.find_spec("blueprinting.compiler") is None + with pytest.raises(ModuleNotFoundError): + __import__("blueprinting.compiler") + + +def test_legacy_public_symbols_are_not_reexported() -> None: + assert not hasattr(synthesizer, "CompilationSession") + assert not hasattr(synthesizer, "CompilerError") diff --git a/tests/application/test_analysis_service.py b/tests/application/test_analysis_service.py index 4a4c1a9..8dc5d1c 100644 --- a/tests/application/test_analysis_service.py +++ b/tests/application/test_analysis_service.py @@ -4,8 +4,8 @@ from blueprinting.analysis import CalibrationMode from blueprinting.application import AnalysisDraft, BlueprintingService, SweepRequest -from blueprinting.compiler.frozen import FrozenDict -from blueprinting.compiler.models import TransformerModelSpec, build_transformer_model_ir +from blueprinting.synthesizer.frozen import FrozenDict +from blueprinting.synthesizer.models import TransformerModelSpec, build_transformer_model_ir from blueprinting.workbench import default_catalog diff --git a/tests/regression/test_baseline_quality_gate.py b/tests/regression/test_baseline_quality_gate.py index 56f4888..481dce0 100644 --- a/tests/regression/test_baseline_quality_gate.py +++ b/tests/regression/test_baseline_quality_gate.py @@ -4,7 +4,7 @@ import pytest -from blueprinting.compiler.experiments import ( +from blueprinting.synthesizer.experiments import ( BaselineRegressionGate, RegressionCheck, run_inference_baseline_regression, diff --git a/tests/compiler/conftest.py b/tests/synthesizer/conftest.py similarity index 99% rename from tests/compiler/conftest.py rename to tests/synthesizer/conftest.py index 3bc88c8..4e94ad0 100644 --- a/tests/compiler/conftest.py +++ b/tests/synthesizer/conftest.py @@ -2,8 +2,8 @@ import pytest -from blueprinting.compiler import FrozenDict -from blueprinting.compiler.ids import ( +from blueprinting.synthesizer import FrozenDict +from blueprinting.synthesizer.ids import ( BufferId, CommandId, DeviceId, @@ -15,7 +15,7 @@ TokenId, ValueId, ) -from blueprinting.compiler.ir import ( +from blueprinting.synthesizer.ir import ( AbstractStorageClass, AccessMode, BufferBinding, diff --git a/tests/compiler/test_bindings.py b/tests/synthesizer/test_bindings.py similarity index 57% rename from tests/compiler/test_bindings.py rename to tests/synthesizer/test_bindings.py index 3e24c00..92e48bd 100644 --- a/tests/compiler/test_bindings.py +++ b/tests/synthesizer/test_bindings.py @@ -2,19 +2,22 @@ import pytest -from blueprinting.compiler import ( +from blueprinting.synthesizer import ( BindingAxis, BindingError, - CompilationSession, DeploymentProfile, FrozenDict, + SerializationError, Symbol, + SynthesisSession, TargetProfile, TargetRequirements, WorkloadBinding, WorkloadMode, + canonical_dumps, + canonical_loads, ) -from blueprinting.compiler.ir import PortablePlanIR +from blueprinting.synthesizer.ir import PortablePlanIR def _target(name: str, architecture: str) -> TargetProfile: @@ -24,7 +27,7 @@ def _target(name: str, architecture: str) -> TargetProfile: architecture_revision="1", runtime_stack="fixture-runtime", runtime_revision="1", - compiler_abi="fixture-abi-v1", + target_abi="fixture-abi-v1", supported_dtypes=frozenset({"f16"}), supported_collectives=frozenset({"all_reduce"}), capabilities=frozenset({"matrix_multiply"}), @@ -75,9 +78,43 @@ def test_target_and_deployment_requirements_are_checked() -> None: def test_target_binding_changes_session_not_portable_plan( portable_ir: PortablePlanIR, ) -> None: - first = CompilationSession().with_binding(_target("virtual-a", "virtual-v1")) - second = CompilationSession().with_binding(_target("virtual-b", "virtual-v2")) + first = SynthesisSession().with_binding(_target("virtual-a", "virtual-v1")) + second = SynthesisSession().with_binding(_target("virtual-b", "virtual-v2")) portable_digest = portable_ir.digest assert first.fingerprint != second.fingerprint assert portable_ir.digest == portable_digest + + +LEGACY_TARGET_PROFILE_JSON = ( + '{"$type":"compiler.binding.target","fields":{' + '"architecture":"virtual","architecture_revision":"1",' + '"attributes":{"$map":[]},"capabilities":{"$frozenset":[]},' + '"collective_library":"none","compiler_abi":"abi-v1",' + '"execution_engines":{"$frozenset":[]},"kernel_library":"none",' + '"memory_spaces":{"$frozenset":[]},"name":"fixture",' + '"runtime_revision":"1","runtime_stack":"runtime",' + '"supported_collectives":{"$frozenset":[]},' + '"supported_dtypes":{"$frozenset":[]},' + '"supported_operations":{"$frozenset":[]}}}' +) + + +def test_legacy_target_profile_field_decodes_and_reencodes_canonically() -> None: + target = canonical_loads(LEGACY_TARGET_PROFILE_JSON) + + assert isinstance(target, TargetProfile) + assert target.target_abi == "abi-v1" + encoded = canonical_dumps(target) + assert '"target_abi":"abi-v1"' in encoded + assert '"compiler_abi"' not in encoded + + +def test_target_profile_rejects_legacy_and_current_field_together() -> None: + conflicting = LEGACY_TARGET_PROFILE_JSON.replace( + '"compiler_abi":"abi-v1",', + '"compiler_abi":"abi-v1","target_abi":"abi-v1",', + ) + + with pytest.raises(SerializationError, match="both a current field and its legacy alias"): + canonical_loads(conflicting) diff --git a/tests/compiler/test_calculon_calibration.py b/tests/synthesizer/test_calculon_calibration.py similarity index 85% rename from tests/compiler/test_calculon_calibration.py rename to tests/synthesizer/test_calculon_calibration.py index a2b30d8..b256b1e 100644 --- a/tests/compiler/test_calculon_calibration.py +++ b/tests/synthesizer/test_calculon_calibration.py @@ -7,15 +7,15 @@ from blueprinting.analysis.cost_model import CalibrationMode, HardwareProfile, estimate_iteration from blueprinting.analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase -from blueprinting.compiler.experiments import discover_seqsel_tab5_cases, run_calculon_experiment -from blueprinting.compiler.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass -from blueprinting.compiler.models import ( +from blueprinting.synthesizer.experiments import discover_seqsel_tab5_cases, run_calculon_experiment +from blueprinting.synthesizer.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass +from blueprinting.synthesizer.models import ( TransformerExecutionSpec, TransformerModelSpec, build_transformer_model_ir, - compilation_session_for, + synthesis_session_for, ) -from blueprinting.compiler.passes import PassManager, PassPipeline +from blueprinting.synthesizer.passes import PassManager, PassPipeline ROOT = Path(__file__).resolve().parents[2] @@ -25,7 +25,7 @@ def _json(path: Path): return json.load(stream) -def _compile(model_name: str, mode: str): +def _derive(model_name: str, mode: str): model_data = _json(ROOT / "data" / "models" / f"{model_name}.json") execution_data = _json(ROOT / "data" / "validation" / "seqsel" / "tab5" / f"{model_name}_{mode}.json") model = TransformerModelSpec.from_mapping(model_name, model_data) @@ -34,13 +34,13 @@ def _compile(model_name: str, mode: str): result = PassManager().run( PassPipeline.of(DistributeTransformerTrainingPass(), PlanTransformerTrainingPass()), source, - session=compilation_session_for(model, execution), + session=synthesis_session_for(model, execution), ) return model, execution, source, result def test_transformer_lowering_produces_auditable_ir_checkpoints(): - _, _, source, result = _compile("gpt3-175B", "seqsel") + _, _, source, result = _derive("gpt3-175B", "seqsel") assert source.require_valid() is None assert tuple(record.pass_name for record in result.records) == ( @@ -56,7 +56,7 @@ def test_transformer_lowering_produces_auditable_ir_checkpoints(): def test_selective_recompute_is_structural_and_linear_gradients_are_derived(): - _, _, _, result = _compile("gpt3-175B", "seqsel") + _, _, _, result = _derive("gpt3-175B", "seqsel") invocations = tuple(task.attributes["invocation"] for task in result.ir.tasks) recomputed_layers = { invocation.source_layer for invocation in invocations if invocation.phase is TrainingPhase.RECOMPUTE @@ -77,7 +77,7 @@ def test_selective_recompute_is_structural_and_linear_gradients_are_derived(): def test_hardware_evidence_is_shared_and_does_not_change_workload(): - _, execution, _, result = _compile("gpt3-175B", "full") + _, execution, _, result = _derive("gpt3-175B", "full") hardware = HardwareProfile.from_mapping( "a100_80g", _json(ROOT / "data" / "systems" / "a100_80g.json"), @@ -111,5 +111,5 @@ def test_explicit_recompute_does_not_copy_calculon_prefix_counter(): result = run_calculon_experiment((case,)).cases[0] audit = result.to_dict()["recompute_counter_audit"] - assert audit["compiled_explicit_operations"] < audit["calculon_block_re_flops"] + assert audit["derived_explicit_operations"] < audit["calculon_block_re_flops"] assert result.calibrated.recompute == pytest.approx(result.calculon_stats["recompute_time"], rel=1e-12) diff --git a/tests/compiler/test_canonical_ir.py b/tests/synthesizer/test_canonical_ir.py similarity index 94% rename from tests/compiler/test_canonical_ir.py rename to tests/synthesizer/test_canonical_ir.py index 4a1366e..c59beca 100644 --- a/tests/compiler/test_canonical_ir.py +++ b/tests/synthesizer/test_canonical_ir.py @@ -5,9 +5,9 @@ import pytest -from blueprinting.compiler import FrozenDict, NodeId, SerializationError -from blueprinting.compiler.codec import canonical_dumps, canonical_loads, record_type -from blueprinting.compiler.ir import ( +from blueprinting.synthesizer import FrozenDict, NodeId, SerializationError +from blueprinting.synthesizer.codec import canonical_dumps, canonical_loads, record_type +from blueprinting.synthesizer.ir import ( ConcretePlanIR, DistributedTaskIR, MachineIR, diff --git a/tests/compiler/test_pass_manager.py b/tests/synthesizer/test_pass_manager.py similarity index 77% rename from tests/compiler/test_pass_manager.py rename to tests/synthesizer/test_pass_manager.py index 962b519..ed85413 100644 --- a/tests/compiler/test_pass_manager.py +++ b/tests/synthesizer/test_pass_manager.py @@ -4,18 +4,18 @@ import pytest -from blueprinting.compiler import ( +from blueprinting.synthesizer import ( BindingAxis, - CompilationSession, FrozenDict, MissingAnalysisError, MissingBindingError, PassContractError, + SynthesisSession, ) -from blueprinting.compiler.errors import PassExecutionError -from blueprinting.compiler.ir import DistributedTaskIR, ModelIR -from blueprinting.compiler.ir.common import make_header -from blueprinting.compiler.passes import ( +from blueprinting.synthesizer.errors import PassExecutionError +from blueprinting.synthesizer.ir import DistributedTaskIR, ModelIR +from blueprinting.synthesizer.ir.common import make_header +from blueprinting.synthesizer.passes import ( AnalysisKey, AnalysisProduct, FunctionPass, @@ -47,7 +47,7 @@ def test_analysis_products_are_atomic_and_context_addressed(model_ir: ModelIR) - ), lambda ir, context: ir if context.analysis(ir, key)["rank"] == 2 else None, ) - session = CompilationSession() + session = SynthesisSession() manager = PassManager() result = manager.run(PassPipeline.of(analyze, consume), model_ir, session=session) @@ -67,12 +67,12 @@ def inspect(self, checkpoint: PassCheckpoint, _context: object) -> None: self.checkpoints.append(checkpoint) observer = CaptureObserver() - compiler_pass = FunctionPass(_model_contract("fixture.profiled"), lambda ir, _context: ir) + derivation_pass = FunctionPass(_model_contract("fixture.profiled"), lambda ir, _context: ir) result = PassManager(observers=(observer,)).run( - PassPipeline.of(compiler_pass), + PassPipeline.of(derivation_pass), model_ir, - session=CompilationSession(), + session=SynthesisSession(), ) assert observer.checkpoints == list(result.checkpoints) @@ -85,67 +85,67 @@ def inspect(self, _checkpoint: PassCheckpoint, _session: object) -> None: raise RuntimeError("profiler rejected checkpoint") key = AnalysisKey("fixture", "rejected") - compiler_pass = FunctionPass( + derivation_pass = FunctionPass( _model_contract("fixture.observed", produced_analyses=frozenset({key})), lambda ir, _context: PassResult(ir, (AnalysisProduct(key, FrozenDict({"ok": True})),)), ) - session = CompilationSession() + session = SynthesisSession() manager = PassManager(observers=(RejectObserver(),)) with pytest.raises(PassExecutionError, match="RejectObserver"): - manager.run(PassPipeline.of(compiler_pass), model_ir, session=session) + manager.run(PassPipeline.of(derivation_pass), model_ir, session=session) assert not manager.analyses.has(model_ir.digest, key, session.fingerprint) def test_missing_analysis_fails_before_pass_runs(model_ir: ModelIR) -> None: key = AnalysisKey("fixture", "missing") - compiler_pass = FunctionPass( + derivation_pass = FunctionPass( _model_contract("fixture.requires-analysis", required_analyses=frozenset({key})), lambda ir, _context: ir, ) with pytest.raises(MissingAnalysisError, match="requires missing analyses"): - PassManager().run(PassPipeline.of(compiler_pass), model_ir, session=CompilationSession()) + PassManager().run(PassPipeline.of(derivation_pass), model_ir, session=SynthesisSession()) def test_analysis_store_rejects_noncanonical_product_atomically(model_ir: ModelIR) -> None: key = AnalysisKey("fixture", "bad") - compiler_pass = FunctionPass( + derivation_pass = FunctionPass( _model_contract("fixture.bad-analysis", produced_analyses=frozenset({key})), lambda ir, _context: PassResult(ir, (AnalysisProduct(key, object()),)), ) manager = PassManager() - session = CompilationSession() + session = SynthesisSession() with pytest.raises(PassContractError, match="not a canonical immutable value"): - manager.run(PassPipeline.of(compiler_pass), model_ir, session=session) + manager.run(PassPipeline.of(derivation_pass), model_ir, session=session) assert not manager.analyses.has(model_ir.digest, key, session.fingerprint) def test_analysis_store_defensively_freezes_extension_data(model_ir: ModelIR) -> None: key = AnalysisKey("fixture", "frozen") source = {"values": [1]} - compiler_pass = FunctionPass( + derivation_pass = FunctionPass( _model_contract("fixture.freeze-analysis", produced_analyses=frozenset({key})), lambda ir, _context: PassResult(ir, (AnalysisProduct(key, source),)), ) manager = PassManager() - session = CompilationSession() + session = SynthesisSession() - manager.run(PassPipeline.of(compiler_pass), model_ir, session=session) + manager.run(PassPipeline.of(derivation_pass), model_ir, session=session) source["values"].append(2) assert manager.analyses.get(model_ir.digest, key, session.fingerprint) == FrozenDict({"values": (1,)}) def test_required_binding_is_enforced(model_ir: ModelIR) -> None: - compiler_pass = FunctionPass( + derivation_pass = FunctionPass( _model_contract("fixture.target-bound", required_bindings=frozenset({BindingAxis.TARGET})), lambda ir, _context: ir, ) with pytest.raises(MissingBindingError, match="target"): - PassManager().run(PassPipeline.of(compiler_pass), model_ir, session=CompilationSession()) + PassManager().run(PassPipeline.of(derivation_pass), model_ir, session=SynthesisSession()) def test_pipeline_rejects_declared_type_mismatch() -> None: @@ -164,10 +164,10 @@ def mutate(ir: ModelIR, _context: object) -> ModelIR: object.__setattr__(ir, "name", "illegally-mutated") return ir - compiler_pass = FunctionPass(_model_contract("fixture.illegal-mutation"), mutate) + derivation_pass = FunctionPass(_model_contract("fixture.illegal-mutation"), mutate) with pytest.raises(PassContractError, match="mutated its input"): - PassManager().run(PassPipeline.of(compiler_pass), model_ir, session=CompilationSession()) + PassManager().run(PassPipeline.of(derivation_pass), model_ir, session=SynthesisSession()) def test_transactional_pass_cannot_mutate_caller_snapshot(model_ir: ModelIR) -> None: @@ -182,12 +182,12 @@ def mutate_copy(ir: ModelIR, _context: object) -> ModelIR: ) return ir - compiler_pass = FunctionPass( + derivation_pass = FunctionPass( _model_contract("fixture.transaction", mutation_model=MutationModel.TRANSACTIONAL), mutate_copy, ) - result = PassManager().run(PassPipeline.of(compiler_pass), model_ir, session=CompilationSession()) + result = PassManager().run(PassPipeline.of(derivation_pass), model_ir, session=SynthesisSession()) assert model_ir.name == "fixture-model" assert model_ir.digest == original_digest @@ -195,20 +195,20 @@ def mutate_copy(ir: ModelIR, _context: object) -> ModelIR: def test_rewrite_must_retain_parent_digest(model_ir: ModelIR) -> None: - compiler_pass = FunctionPass( + derivation_pass = FunctionPass( _model_contract("fixture.bad-lineage"), lambda ir, _context: replace(ir, name="rewritten-without-lineage"), ) with pytest.raises(PassContractError, match="without retaining its input digest"): - PassManager().run(PassPipeline.of(compiler_pass), model_ir, session=CompilationSession()) + PassManager().run(PassPipeline.of(derivation_pass), model_ir, session=SynthesisSession()) def test_unexpected_pass_failure_is_wrapped(model_ir: ModelIR) -> None: def fail(_ir: ModelIR, _context: object) -> ModelIR: raise RuntimeError("implementation bug") - compiler_pass = FunctionPass(_model_contract("fixture.failure"), fail) + derivation_pass = FunctionPass(_model_contract("fixture.failure"), fail) with pytest.raises(PassExecutionError, match="fixture.failure"): - PassManager().run(PassPipeline.of(compiler_pass), model_ir, session=CompilationSession()) + PassManager().run(PassPipeline.of(derivation_pass), model_ir, session=SynthesisSession()) diff --git a/tests/compiler/test_transformer_inference.py b/tests/synthesizer/test_transformer_inference.py similarity index 90% rename from tests/compiler/test_transformer_inference.py rename to tests/synthesizer/test_transformer_inference.py index eddc624..08c990f 100644 --- a/tests/compiler/test_transformer_inference.py +++ b/tests/synthesizer/test_transformer_inference.py @@ -12,21 +12,21 @@ VidurProfileBaseline, estimate_inference_phase, ) -from blueprinting.compiler.bindings import InferencePhase -from blueprinting.compiler.experiments import ( +from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.synthesizer.experiments import ( VidurExperimentCase, compare_inference_phase_to_vidur, run_vidur_experiment, ) -from blueprinting.compiler.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from blueprinting.compiler.models import ( +from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass +from blueprinting.synthesizer.models import ( TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, TransformerModelSpec, build_transformer_inference_model_ir, - inference_compilation_session_for, + inference_synthesis_session_for, ) -from blueprinting.compiler.passes import PassManager, PassPipeline +from blueprinting.synthesizer.passes import PassManager, PassPipeline ROOT = Path(__file__).resolve().parents[2] @@ -55,14 +55,14 @@ def _execution() -> TransformerInferenceExecutionSpec: ) -def _compile(phase: InferencePhase, context_tokens: int): +def _derive(phase: InferencePhase, context_tokens: int): model = _model() execution = _execution() source = build_transformer_inference_model_ir(model) result = PassManager().run( PassPipeline.of(DistributeTransformerInferencePass(), PlanTransformerInferencePass()), source, - session=inference_compilation_session_for( + session=inference_synthesis_session_for( model, execution, phase=phase, @@ -79,7 +79,7 @@ def _task(plan, primitive: str): @pytest.mark.parametrize("phase", tuple(InferencePhase)) def test_inference_lowering_has_valid_auditable_phase_plans(phase: InferencePhase): - source, plan = _compile(phase, 64) + source, plan = _derive(phase, 64) assert source.verify().ok assert plan.verify().ok @@ -95,10 +95,10 @@ def test_inference_lowering_has_valid_auditable_phase_plans(phase: InferencePhas def test_prefill_attention_is_quadratic_and_decode_attention_is_linear_in_context(): - _, prefill_32 = _compile(InferencePhase.PREFILL, 32) - _, prefill_64 = _compile(InferencePhase.PREFILL, 64) - _, decode_32 = _compile(InferencePhase.DECODE, 32) - _, decode_64 = _compile(InferencePhase.DECODE, 64) + _, prefill_32 = _derive(InferencePhase.PREFILL, 32) + _, prefill_64 = _derive(InferencePhase.PREFILL, 64) + _, decode_32 = _derive(InferencePhase.DECODE, 32) + _, decode_64 = _derive(InferencePhase.DECODE, 64) assert ( _task(prefill_64, "attention_core").workload.operations @@ -111,7 +111,7 @@ def test_prefill_attention_is_quadratic_and_decode_attention_is_linear_in_contex def test_kv_cache_capacity_is_derived_from_shape_not_a_correction_factor(): - _, plan = _compile(InferencePhase.DECODE, 96) + _, plan = _derive(InferencePhase.DECODE, 96) kv_buffer = next(buffer for buffer in plan.buffers if buffer.attributes.get("semantic") == "kv_cache") workspace = next( buffer for buffer in plan.buffers if buffer.attributes.get("semantic") == "block_working_upper_bound" @@ -212,7 +212,7 @@ def test_vidur_adapter_uses_only_exact_shape_matches(tmp_path: Path): assert compute_exact is not None assert compute_exact.seconds == pytest.approx(0.00075) - _, plan = _compile(InferencePhase.DECODE, 96) + _, plan = _derive(InferencePhase.DECODE, 96) hardware = HardwareProfile.from_mapping( "fixture-hardware", json.loads((ROOT / "data" / "systems" / "a100_80g.json").read_text(encoding="utf-8")), @@ -228,7 +228,7 @@ def test_vidur_adapter_uses_only_exact_shape_matches(tmp_path: Path): assert attention_estimate.total_seconds == attention_estimate.analytical_seconds assert attention_estimate.evidence_provider == "analytical-system-profile" assert attention_comparison.baseline_seconds == pytest.approx(0.00025) - assert attention_comparison.compiled_seconds == attention_estimate.total_seconds + assert attention_comparison.estimated_seconds == attention_estimate.total_seconds assert not isinstance(baseline, InferenceCostProvider) assert plan.digest == digest_before diff --git a/tests/compiler/test_verifiers.py b/tests/synthesizer/test_verifiers.py similarity index 97% rename from tests/compiler/test_verifiers.py rename to tests/synthesizer/test_verifiers.py index 77e40be..18369c7 100644 --- a/tests/compiler/test_verifiers.py +++ b/tests/synthesizer/test_verifiers.py @@ -2,8 +2,8 @@ from dataclasses import fields, replace -from blueprinting.compiler import BufferId, FrozenDict, NodeId, ValueId -from blueprinting.compiler.ir import ( +from blueprinting.synthesizer import BufferId, FrozenDict, NodeId, ValueId +from blueprinting.synthesizer.ir import ( ConcretePlanIR, DistributedTaskIR, MachineIR, From 34ed5d50842c682ed9b9a71849ae8e00f137119f Mon Sep 17 00:00:00 2001 From: Reiase Date: Sun, 9 Aug 2026 17:02:33 +0800 Subject: [PATCH 4/6] refactor: separate workload and system domains --- AGENTS.md | 15 +- README.md | 16 +- .../implemented-derivation-path.svg | 2 +- .../architecture/inference-planning-path.svg | 2 +- docs/design/index.en.md | 2 +- docs/design/index.zh.md | 2 +- docs/design/modules.en.md | 41 ++-- docs/design/modules.zh.md | 41 ++-- docs/design/passes/target.en.md | 2 +- docs/design/passes/target.zh.md | 2 +- docs/design/passes/transformer.en.md | 5 +- docs/design/passes/transformer.zh.md | 5 +- docs/design/performance/database.en.md | 6 +- docs/design/performance/database.zh.md | 6 +- docs/design/performance/index.en.md | 2 +- docs/design/performance/index.zh.md | 2 +- docs/design/performance/providers.en.md | 2 +- docs/design/performance/providers.zh.md | 2 +- docs/experiments/calculon-calibration.en.md | 3 +- docs/experiments/calculon-calibration.zh.md | 3 +- docs/exploration/design-space.en.md | 2 +- docs/exploration/design-space.zh.md | 2 +- docs/modeling/hardware.en.md | 6 +- docs/modeling/hardware.zh.md | 6 +- docs/modeling/inference.en.md | 11 +- docs/modeling/inference.zh.md | 11 +- .../adr/0002-workload-system-domains.en.md | 76 ++++++ .../adr/0002-workload-system-domains.zh.md | 76 ++++++ docs/project/decisions.en.md | 1 + docs/project/decisions.zh.md | 1 + docs/project/roadmap.en.md | 4 +- docs/project/roadmap.zh.md | 4 +- docs/project/status.en.md | 13 +- docs/project/status.zh.md | 13 +- mkdocs.yml | 2 + src/blueprinting/analysis/__init__.py | 2 - src/blueprinting/analysis/cost/roofline.py | 29 ++- src/blueprinting/analysis/cost_model.py | 224 +++--------------- src/blueprinting/analysis/inference_cost.py | 40 ++-- .../analysis/transformer_inference.py | 3 +- .../analysis/transformer_workload.py | 2 +- src/blueprinting/application/analysis.py | 13 +- src/blueprinting/application/inference.py | 18 +- .../synthesizer/experiments/calculon.py | 14 +- .../synthesizer/experiments/regression.py | 7 +- .../synthesizer/experiments/vidur.py | 14 +- .../synthesizer/frontend/__init__.py | 14 ++ .../synthesizer/frontend/transformer.py | 84 +++++++ .../frontend/transformer_inference.py | 126 ++++++++++ .../synthesizer/lowering/transformer.py | 10 +- .../lowering/transformer_inference.py | 3 +- src/blueprinting/system/__init__.py | 15 ++ src/blueprinting/system/chip.py | 113 +++++++++ src/blueprinting/system/interconnect.py | 103 ++++++++ src/blueprinting/system/profile.py | 114 +++++++++ .../models => workload}/__init__.py | 10 +- .../models => workload}/transformer.py | 84 +------ .../transformer_inference.py | 126 +--------- tests/analysis/test_cost_model_providers.py | 16 +- tests/analysis/test_package_boundary.py | 43 ++++ tests/analysis/test_system_profile.py | 58 +++++ tests/application/test_analysis_service.py | 3 +- .../synthesizer/test_calculon_calibration.py | 13 +- .../synthesizer/test_transformer_inference.py | 14 +- 64 files changed, 1100 insertions(+), 604 deletions(-) create mode 100644 docs/project/adr/0002-workload-system-domains.en.md create mode 100644 docs/project/adr/0002-workload-system-domains.zh.md create mode 100644 src/blueprinting/synthesizer/frontend/__init__.py create mode 100644 src/blueprinting/synthesizer/frontend/transformer.py create mode 100644 src/blueprinting/synthesizer/frontend/transformer_inference.py create mode 100644 src/blueprinting/system/__init__.py create mode 100644 src/blueprinting/system/chip.py create mode 100644 src/blueprinting/system/interconnect.py create mode 100644 src/blueprinting/system/profile.py rename src/blueprinting/{synthesizer/models => workload}/__init__.py (57%) rename src/blueprinting/{synthesizer/models => workload}/transformer.py (69%) rename src/blueprinting/{synthesizer/models => workload}/transformer_inference.py (51%) create mode 100644 tests/analysis/test_system_profile.py diff --git a/AGENTS.md b/AGENTS.md index ae4f1d0..789adf9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,13 +142,15 @@ handling。若 LPU 的 issue cycle/slot 具有 correctness 含义,它在 targe ## 9. 当前实现边界 -当前 canonical 表示与形式化推导机制位于 `src/blueprinting/synthesizer/`,分析与证据评估位于同级 -`src/blueprinting/analysis/`。Synthesizer 表示 formal plan synthesis 的实现边界,不是产品身份、RTL 综合器或独立 Compiler 组件。已经实现: +Target-neutral workload contract 位于 `src/blueprinting/workload/`,芯片、memory、interconnect 与 system profile +位于 `src/blueprinting/system/`;canonical 表示与形式化推导机制位于 `src/blueprinting/synthesizer/`,分析与证据评估 +位于同级 `src/blueprinting/analysis/`。Synthesizer 表示 formal plan synthesis 的实现边界,不是产品身份、RTL 综合器或独立 Compiler 组件。已经实现: - 五层 canonical IR 的 immutable schema、serialization 和 structural verifier;其中后两层仍是 experimental contract; - stable ID、lineage、typed scalar expression、binding/session; - pass contract、analysis cache/invalidation 和 derivation checkpoint; -- decoder-only Transformer training frontend; +- decoder-only Transformer workload contract 与 training/inference frontend adapter; +- compute、memory、interconnect 和聚合 `SystemProfile` contract; - `ModelIR -> DistributedTaskIR -> PortablePlanIR` 的 TP、recompute、workload 与 buffer derivation; - peak-only / system-evidence cost view 和 Calculon/SeqSel 校准实验。 @@ -163,8 +165,11 @@ handling。若 LPU 的 issue cycle/slot 具有 correctness 含义,它在 targe ## 10. 代码与仓库规则 -- 新 canonical 表示与推导代码进入 `src/blueprinting/synthesizer/` 对应边界,cost/evidence analysis 进入 - `src/blueprinting/analysis/`;不得新建平行表示栈。 +- Workload semantic/request/mapping contract 进入 `src/blueprinting/workload/`;芯片、memory、interconnect 与 system + contract 进入 `src/blueprinting/system/`;workload-to-IR adapter、canonical 表示与推导进入 + `src/blueprinting/synthesizer/`;cost/evidence analysis 进入 `src/blueprinting/analysis/`。不得新建平行表示栈。 +- `SystemProfile` 是当前有限的 compute/memory/network evidence-bearing adapter,不得被描述成已经实现的完整 + `ArchitectureBlueprint`;`src/blueprinting/types/system/` 只服务 legacy calculator,新代码不得依赖它。 - `blueprinting.compiler` Python path 已硬切删除;历史 `compiler.*` canonical codec tag 作为 wire identity 保留, 未经迁移 ADR 不得改写。 - IR 对象默认 frozen;语义字段使用 typed dataclass/enum/ID,不使用自由字典代替 contract。 diff --git a/README.md b/README.md index 2716746..7417a68 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,9 @@ from blueprinting.synthesizer.lowering import ( DistributeTransformerTrainingPass, PlanTransformerTrainingPass, ) -from blueprinting.synthesizer.models import ( - TransformerExecutionSpec, - TransformerModelSpec, - build_transformer_model_ir, - synthesis_session_for, -) +from blueprinting.synthesizer.frontend import build_transformer_model_ir, synthesis_session_for from blueprinting.synthesizer.passes import PassManager, PassPipeline +from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec model = TransformerModelSpec.from_mapping("gpt3-175B", model_config) execution = TransformerExecutionSpec.from_mapping(execution_config) @@ -154,12 +150,14 @@ Calculon remains an adjacent calibration utility and does not participate in the ```text src/blueprinting/synthesizer/ ├── ir/ # five canonical IR contracts -├── models/ # typed semantic frontends +├── frontend/ # workload-to-IR/session adapters ├── lowering/ # staged derivation passes ├── experiments/ # reproducible validation experiments ├── passes/ # transformation contracts and manager └── session.py # explicit bindings and typed derivation context +src/blueprinting/workload/ # target-neutral workload and mapping contracts +src/blueprinting/system/ # chip, memory, interconnect, and system profiles src/blueprinting/analysis/ # exact workload and evidence-backed cost analyses src/blueprinting/application/ # framework-neutral analysis service src/blueprinting/workbench/ # NiceGUI workbench and legacy presentation adapters @@ -168,8 +166,8 @@ tests/synthesizer/ # current formal-representation and calibration tests docs/ # bilingual MkDocs design, reference, experiment, and project documentation ``` -The `synthesizer` package owns canonical representations and verified derivation mechanics. The name describes -formal plan synthesis—not RTL synthesis, a standalone Compiler product, or Blueprinting's top-level identity. +`workload` and `system` own the two domain inputs. `synthesizer` connects them through canonical representations +and verified derivation mechanics; `analysis` evaluates the resulting facts without owning either domain model. ## Development diff --git a/docs/assets/architecture/implemented-derivation-path.svg b/docs/assets/architecture/implemented-derivation-path.svg index cc302a6..f945b09 100644 --- a/docs/assets/architecture/implemented-derivation-path.svg +++ b/docs/assets/architecture/implemented-derivation-path.svg @@ -95,7 +95,7 @@ - HardwareProfile + SystemProfile peak throughput/bandwidth + versioned efficiency curves same evidence revision shared across every case diff --git a/docs/assets/architecture/inference-planning-path.svg b/docs/assets/architecture/inference-planning-path.svg index 7460442..2019a1f 100644 --- a/docs/assets/architecture/inference-planning-path.svg +++ b/docs/assets/architecture/inference-planning-path.svg @@ -45,7 +45,7 @@ Blueprinting Cost Model - HardwareProfile · internal cost provider + SystemProfile · internal cost provider no comparison-oracle input Phase Cost View diff --git a/docs/design/index.en.md b/docs/design/index.en.md index 53f128d..0691d13 100644 --- a/docs/design/index.en.md +++ b/docs/design/index.en.md @@ -122,7 +122,7 @@ Each shortcut makes architecture candidates less comparable or conclusions less ## Current versus target system -The current connected path ends at `PortablePlanIR`, followed by an analytical `HardwareProfile` estimate used for validation. Five typed representation schemas, verified transformation transactions, workload derivation, and the Calculon experiment are implemented foundations. Internal schema version numbers identify serialization contracts; they are not public compatibility promises until production producers, independent consumers, and migration policies exist. +The current connected path ends at `PortablePlanIR`, followed by an analytical `SystemProfile` estimate used for validation. Five typed representation schemas, verified transformation transactions, workload derivation, and the Calculon experiment are implemented foundations. Internal schema version numbers identify serialization contracts; they are not public compatibility promises until production producers, independent consumers, and migration policies exist. First-class architecture blueprints, target/resource binding, concrete scheduling, event simulation, simulator providers, design-space search, and optional GPU/LPU program emission remain planned or contract-only. The [status page](../project/status.md) is authoritative. diff --git a/docs/design/index.zh.md b/docs/design/index.zh.md index 8ad2c73..dcb2540 100644 --- a/docs/design/index.zh.md +++ b/docs/design/index.zh.md @@ -122,7 +122,7 @@ Search 可以昂贵,但必须有界且可复现。Runtime 或 simulation execu ## 当前系统与目标系统 -当前贯通路径结束在 `PortablePlanIR`,随后通过 analytical `HardwareProfile` estimate 做验证。五层 typed representation schema、verified transformation transaction、workload derivation 与 Calculon experiment 已经实现,是后续产品的基础。Schema 的内部版本号只标识 serialization contract;在 production producer、独立 consumer 与 migration policy 到位前,不构成 public compatibility 承诺。 +当前贯通路径结束在 `PortablePlanIR`,随后通过 analytical `SystemProfile` estimate 做验证。五层 typed representation schema、verified transformation transaction、workload derivation 与 Calculon experiment 已经实现,是后续产品的基础。Schema 的内部版本号只标识 serialization contract;在 production producer、独立 consumer 与 migration policy 到位前,不构成 public compatibility 承诺。 First-class architecture blueprint、target/resource binding、concrete scheduling、event simulation、simulator provider、design-space search 与可选 GPU/LPU program emission 仍为 planned 或 contract-only。以[状态页](../project/status.md)为准。 diff --git a/docs/design/modules.en.md b/docs/design/modules.en.md index 1cda4dd..99adb5a 100644 --- a/docs/design/modules.en.md +++ b/docs/design/modules.en.md @@ -26,13 +26,21 @@ result.sensitivity Internally, each candidate creates an immutable typed derivation context for workload mapping, architecture binding, and analysis addressing. The current implementation names this object `SynthesisSession`; that class and its workload/strategy bindings are implemented, while `ExplorationSession` and the end-to-end product facade are planned. Global mutable configuration is forbidden because it would invalidate experiment reproducibility. +## Workload and system domain models + +`blueprinting.workload` owns target-neutral model semantics, request scenarios, and logical mapping intent. A workload object cannot contain a chip name, peak rate, empirical latency, kernel identity, or physical placement. The current slice provides typed Transformer training and inference contracts. + +`blueprinting.system` owns immutable chip-local compute engines, memory capacity/bandwidth, interconnect tiers, collective volume rules, and their imported evidence revision. `SystemProfile` is the current limited compute/memory/network adapter; it is not yet the hierarchical `ArchitectureBlueprint`, physical deployment, or target binding described by the product design. Cost policy remains in `analysis`: the system contract exposes peak and evidence-bearing facts but does not choose calibration mode. + +These packages are authoritative domain inputs, not alternative IR hierarchies. Canonical derivation starts only when a synthesizer frontend imports a workload contract into `ModelIR`; a system profile remains outside canonical workload state and is consumed by explicit analysis or later target binding. + ## Frontends Frontends parse model or framework input, validate target-independent types and effects, assign stable identities, and emit `ModelIR`. They own import diagnostics and source mappings. Frontends do not read peak throughput, kernel catalogs, physical topology, or runtime observations. Framework adapters expose canonical IR rather than a parallel public IR hierarchy. -The current frontend covers typed decoder-only Transformer training. Additional training architectures, inference prefill/decode, KV-cache semantics, and framework importers are planned. +The current frontend covers typed decoder-only Transformer training plus static inference prefill/decode with explicit KV-cache semantics. Additional model families, framework importers, and online serving scenarios are planned. ## Canonical formal-representation infrastructure @@ -107,34 +115,31 @@ The same lineage supports forward and reverse queries from model operation to ru ## Dependency direction ```text -frontend ───────► ir/common - │ -lowering/passes ────┼────► planning -bindings/session ───┘ │ - ▼ - architecture binding - │ - evidence ◄───────┼──────► scheduling - │ - ▼ - simulation / emission - │ - ▼ - observation / calibration +workload ──► synthesizer/frontend ──► ModelIR + │ + lowering/passes ──► portable planning + │ │ +system ─────────────────────────► analysis ▼ + │ architecture binding +evidence ──────────────────────────────┘ │ + ▼ + simulation / emission ``` -The canonical IR, binding, pass, and lowering infrastructure lives under `src/blueprinting/synthesizer/`. The analytical subsystem is a sibling package at `src/blueprinting/analysis/`: the synthesizer materializes explicit workload and plan facts, while analysis evaluates those facts against analytical models and external evidence. Analysis may depend on canonical synthesis contracts; callers must not treat cost evidence as an implicit lowering decision. +The dependency direction is explicit: workload contracts do not depend on system descriptions; system descriptions do not depend on analysis policy; analysis does not construct canonical plans. The synthesizer materializes workload and plan facts, while analysis evaluates those facts against system descriptions and external evidence. Callers must not treat cost evidence as an implicit lowering decision. ## Current source map | Concern | Source | Status | |---|---|---| +| Workload semantics and logical mapping intent | `workload/` | Implemented Transformer slice | +| Chip, memory, interconnect, and aggregate system profile | `system/` | Implemented limited profile adapter | | IDs, expressions, codec, frozen values | `synthesizer/{ids,expr,codec,frozen}.py` | Implemented | | Canonical formal representations (`*IR`) | `synthesizer/ir/` | Implemented contracts | | Bindings and sessions | `synthesizer/{bindings,session}.py` | Implemented | | Analysis/transformation transactions | `synthesizer/passes/base.py` | Implemented | -| Transformer frontend | `synthesizer/models/` | Implemented slice | +| Workload-to-IR/session frontends | `synthesizer/frontend/` | Implemented Transformer slice | | Workload and cost analysis | `analysis/` | Implemented slice | | Transformer derivation passes | `synthesizer/lowering/transformer.py` | Implemented through portable plan | -| Current hardware evidence adapter | `analysis/cost_model.py`, `analysis/cost/` | Implemented slice | +| Current system cost adapters | `analysis/cost_model.py`, `analysis/cost/` | Implemented slice | | Architecture model/search, evidence service, simulation, emission | Accepted boundaries | Planned | diff --git a/docs/design/modules.zh.md b/docs/design/modules.zh.md index 9947d74..01de6d0 100644 --- a/docs/design/modules.zh.md +++ b/docs/design/modules.zh.md @@ -26,13 +26,21 @@ result.sensitivity 内部会为每个 candidate 创建 immutable typed derivation context,用于 workload mapping、architecture binding 与 analysis addressing。当前实现把这个对象命名为 `SynthesisSession`;该 class 及其 workload/strategy binding 已实现,而 `ExplorationSession` 与 end-to-end product facade 仍为 planned。Global mutable configuration 被禁止,因为它会破坏 experiment reproducibility。 +## Workload 与 System 领域模型 + +`blueprinting.workload` 拥有 target-neutral model semantic、request scenario 与 logical mapping intent。Workload object 不得包含 chip name、peak rate、empirical latency、kernel identity 或 physical placement。当前 slice 提供 typed Transformer training/inference contract。 + +`blueprinting.system` 拥有 immutable chip-local compute engine、memory capacity/bandwidth、interconnect tier、collective volume rule 与导入 evidence revision。`SystemProfile` 是当前有限的 compute/memory/network adapter;它还不是产品设计中的 hierarchical `ArchitectureBlueprint`、physical deployment 或 target binding。Cost policy 继续属于 `analysis`:system contract 暴露 peak 与 evidence-bearing facts,但不选择 calibration mode。 + +这两个 package 是权威 domain input,不是另一套 IR hierarchy。只有 synthesizer frontend 把 workload contract 导入 `ModelIR` 后,canonical derivation 才开始;system profile 继续位于 canonical workload state 之外,只能被显式 analysis 或后续 target binding 消费。 + ## Frontend Frontend 解析 model 或 framework input,验证 target-independent type/effect,分配 stable identity,并输出 `ModelIR`。它拥有 import diagnostic 和 source mapping。 Frontend 不读取 peak throughput、kernel catalog、physical topology 或 runtime observation。Framework adapter 暴露 canonical IR,而不是平行的 public IR hierarchy。 -当前 frontend 覆盖 typed decoder-only Transformer training。更多 training architecture、inference prefill/decode、KV-cache semantic 和 framework importer 属于后续工作。 +当前 frontend 覆盖 typed decoder-only Transformer training,以及带显式 KV-cache semantic 的 static inference prefill/decode。更多 model family、framework importer 与 online serving scenario 属于后续工作。 ## Canonical 形式化表示基础设施 @@ -107,34 +115,31 @@ Profiler adapter 把 runtime event 关联到 machine instruction 与 concrete co ## 依赖方向 ```text -frontend ───────► ir/common - │ -lowering/passes ────┼────► planning -bindings/session ───┘ │ - ▼ - architecture binding - │ - evidence ◄───────┼──────► scheduling - │ - ▼ - simulation / emission - │ - ▼ - observation / calibration +workload ──► synthesizer/frontend ──► ModelIR + │ + lowering/passes ──► portable planning + │ │ +system ─────────────────────────► analysis ▼ + │ architecture binding +evidence ──────────────────────────────┘ │ + ▼ + simulation / emission ``` -Canonical IR、binding、pass 与 lowering 基础设施位于 `src/blueprinting/synthesizer/`。分析子系统则是同级的 `src/blueprinting/analysis/`:synthesizer 产出显式 workload 与 plan facts,analysis 再用解析模型和外部证据评估这些事实。Analysis 可以依赖 canonical synthesis contract,但调用方不能把 cost evidence 当作隐式 lowering 决策。 +依赖方向是显式的:workload contract 不依赖 system description;system description 不依赖 analysis policy;analysis 不构造 canonical plan。Synthesizer 物化 workload/plan facts,analysis 再使用 system description 与外部 evidence 评估这些事实。调用方不能把 cost evidence 当作隐式 lowering 决策。 ## 当前源码映射 | 关注点 | 源码 | 状态 | |---|---|---| +| Workload semantic 与 logical mapping intent | `workload/` | Implemented Transformer slice | +| Chip、memory、interconnect 与聚合 system profile | `system/` | Implemented limited profile adapter | | ID、expression、codec、frozen value | `synthesizer/{ids,expr,codec,frozen}.py` | Implemented | | Canonical 形式化表示(`*IR`) | `synthesizer/ir/` | Implemented contracts | | Binding 与 session | `synthesizer/{bindings,session}.py` | Implemented | | Analysis/transformation transaction | `synthesizer/passes/base.py` | Implemented | -| Transformer frontend | `synthesizer/models/` | Implemented slice | +| Workload-to-IR/session frontend | `synthesizer/frontend/` | Implemented Transformer slice | | Workload 与 cost analysis | `analysis/` | Implemented slice | | Transformer derivation pass | `synthesizer/lowering/transformer.py` | Implemented through portable plan | -| 当前 hardware evidence adapter | `analysis/cost_model.py`、`analysis/cost/` | Implemented slice | +| 当前 system cost adapter | `analysis/cost_model.py`、`analysis/cost/` | Implemented slice | | Architecture model/search、evidence service、simulation、emission | Accepted boundary | Planned | diff --git a/docs/design/passes/target.en.md b/docs/design/passes/target.en.md index bef6888..4ba8401 100644 --- a/docs/design/passes/target.en.md +++ b/docs/design/passes/target.en.md @@ -92,7 +92,7 @@ This path makes a hardware or simulator mismatch actionable at the owning layer The next vertical slice should deliberately minimize target complexity: 1. implement one queue-centric and one non-queue-centric `VirtualTargetPlugin` to jointly validate the coordination core; -2. adapt the current `HardwareProfile` behind a normalized estimate provider; +2. adapt the current `SystemProfile` behind a normalized estimate provider; 3. legalize the existing Transformer `PortablePlanIR`; 4. build a single-device or simple-TP `ConcretePlanIR` with explicit ordering, resources, buffers, and typed extensions; 5. derive a timing projection, discrete-event result, and `TimelineBundle`; diff --git a/docs/design/passes/target.zh.md b/docs/design/passes/target.zh.md index 5a12c41..2c49a72 100644 --- a/docs/design/passes/target.zh.md +++ b/docs/design/passes/target.zh.md @@ -92,7 +92,7 @@ model operation -> distributed task -> portable task 下一条纵向切片应主动压低 target complexity: 1. 实现一个 queue-centric 和一个 non-queue-centric `VirtualTargetPlugin`,共同验证 coordination core; -2. 把现有 `HardwareProfile` 适配到 normalized estimate provider; +2. 把现有 `SystemProfile` 适配到 normalized estimate provider; 3. legalize 当前 Transformer `PortablePlanIR`; 4. 构造具有显式 ordering/resource/buffer 与 typed extension 的单设备或简单 TP `ConcretePlanIR`; 5. 派生 timing projection、discrete-event result 与 `TimelineBundle`; diff --git a/docs/design/passes/transformer.en.md b/docs/design/passes/transformer.en.md index 5bd1793..4efa5ee 100644 --- a/docs/design/passes/transformer.en.md +++ b/docs/design/passes/transformer.en.md @@ -17,7 +17,7 @@ TransformerModelSpec + TransformerExecutionSpec -> PortablePlanIR ``` -The red boundary in the figure is intentional. `HardwareProfile` is consumed only by a derived estimate after `PortablePlanIR`; it is not an implicit target binding and it does not make `ConcretePlanIR` available. +The red boundary in the figure is intentional. `SystemProfile` is consumed only by a derived estimate after `PortablePlanIR`; it is not an implicit target binding and it does not make `ConcretePlanIR` available. This slice currently models decoder-only training at block scope. Full-model PP/DP task graphs, inference prefill/decode, intermediate-buffer lifetimes, target legalization, and physical scheduling remain subsequent work. @@ -86,7 +86,8 @@ The derivation does not compensate for a discrepancy by reading a reference late | Concern | Source | Tests | |---|---|---| -| Typed Transformer specifications | `src/blueprinting/synthesizer/models/transformer.py` | binding and calibration tests | +| Typed Transformer specifications | `src/blueprinting/workload/transformer.py` | binding and calibration tests | +| Workload-to-IR frontend | `src/blueprinting/synthesizer/frontend/transformer.py` | canonical representation and calibration tests | | Workload algebra | `src/blueprinting/analysis/transformer_workload.py` | `tests/synthesizer/test_calculon_calibration.py` | | Two derivation passes | `src/blueprinting/synthesizer/lowering/transformer.py` | canonical representation and calibration tests | | Transaction/checkpoints | `src/blueprinting/synthesizer/passes/base.py` | `tests/synthesizer/test_pass_manager.py` | diff --git a/docs/design/passes/transformer.zh.md b/docs/design/passes/transformer.zh.md index 3d4a26f..e78ebaf 100644 --- a/docs/design/passes/transformer.zh.md +++ b/docs/design/passes/transformer.zh.md @@ -17,7 +17,7 @@ TransformerModelSpec + TransformerExecutionSpec -> PortablePlanIR ``` -图中的红色边界是有意保留的。`HardwareProfile` 只在 `PortablePlanIR` 之后被 derived estimate 消费;它既不是隐式 target binding,也不意味着系统已经能够生成 `ConcretePlanIR`。 +图中的红色边界是有意保留的。`SystemProfile` 只在 `PortablePlanIR` 之后被 derived estimate 消费;它既不是隐式 target binding,也不意味着系统已经能够生成 `ConcretePlanIR`。 当前切片只覆盖 decoder-only training 的 block scope。完整模型的 PP/DP task graph、推理 prefill/decode、中间 buffer lifetime、target legalization 和物理调度仍属于后续工作。 @@ -86,7 +86,8 @@ Observer 可以把这些 facts 与 framework trace 或 reference model 对比并 | 关注点 | 源码 | 测试 | |---|---|---| -| 强类型 Transformer specification | `src/blueprinting/synthesizer/models/transformer.py` | binding 与 calibration tests | +| 强类型 Transformer specification | `src/blueprinting/workload/transformer.py` | binding 与 calibration tests | +| Workload-to-IR frontend | `src/blueprinting/synthesizer/frontend/transformer.py` | canonical representation 与 calibration tests | | 工作量代数 | `src/blueprinting/analysis/transformer_workload.py` | `tests/synthesizer/test_calculon_calibration.py` | | 两个 derivation pass | `src/blueprinting/synthesizer/lowering/transformer.py` | canonical representation 与 calibration tests | | 事务与 checkpoint | `src/blueprinting/synthesizer/passes/base.py` | `tests/synthesizer/test_pass_manager.py` | diff --git a/docs/design/performance/database.en.md b/docs/design/performance/database.en.md index 8fe1304..caf5fd1 100644 --- a/docs/design/performance/database.en.md +++ b/docs/design/performance/database.en.md @@ -3,7 +3,7 @@ The performance database is a revisioned evidence store behind a normalized query protocol. It answers a precise question—how an architecture component or legal implementation is expected to behave in a declared context—without hiding architecture choices or calibration knobs inside a lookup table. !!! note "Design status" - The first general slice is implemented as `CostQuery`, `CostEstimate`, `CostResolver`, `PerformanceDatabase`, and typed providers. Static inference consumes that resolver; training still loads `HardwareProfile` directly pending equivalence migration. `VidurProfileBaseline` remains baseline-only, while the separate `VidurProfileImporter` is an explicit evidence-promotion path. See [Cost Providers and Performance-Data Imports](providers.md). + The first general slice is implemented as `CostQuery`, `CostEstimate`, `CostResolver`, `PerformanceDatabase`, and typed providers. Static inference consumes that resolver; training still loads `SystemProfile` directly pending equivalence migration. `VidurProfileBaseline` remains baseline-only, while the separate `VidurProfileImporter` is an explicit evidence-promotion path. See [Cost Providers and Performance-Data Imports](providers.md). ## Request contract @@ -94,9 +94,9 @@ Calibration learns target-wide or implementation-family response behavior from o Forbidden inputs include a benchmark case ID, comparison-oracle total time, or a per-model correction factor whose only purpose is matching a table. Those variables do not explain a causal target behavior and cannot generalize to a new plan. -## Migration from HardwareProfile +## Migration from SystemProfile -The existing `HardwareProfile` already supplies useful versioned curves for matrix/vector throughput, memory transfer, and collectives. Migration should preserve its behavior behind providers: +The existing `SystemProfile` already supplies useful versioned curves for matrix/vector throughput, memory transfer, and collectives. Migration should preserve its behavior behind providers: 1. **Done for static inference:** convert portable tasks into normalized queries; 2. **Done:** wrap the current profile as a roofline/system-evidence provider; diff --git a/docs/design/performance/database.zh.md b/docs/design/performance/database.zh.md index c9a40ef..229c87c 100644 --- a/docs/design/performance/database.zh.md +++ b/docs/design/performance/database.zh.md @@ -3,7 +3,7 @@ 性能数据库是 normalized query protocol 背后的版本化 evidence store。它回答一个精确问题——某个 architecture component 或合法 implementation 在明确 context 中预计如何表现——但不会把 architecture choice 或 calibration knob 隐藏在 lookup table 中。 !!! note "设计状态" - 第一版通用 slice 已实现为 `CostQuery`、`CostEstimate`、`CostResolver`、`PerformanceDatabase` 与 typed provider。Static inference 已消费该 resolver;training 在 equivalence migration 前仍直接加载 `HardwareProfile`。`VidurProfileBaseline` 保持 baseline-only;独立的 `VidurProfileImporter` 才是显式 evidence promotion 路径。详见 [Cost Provider 与性能数据导入](providers.md)。 + 第一版通用 slice 已实现为 `CostQuery`、`CostEstimate`、`CostResolver`、`PerformanceDatabase` 与 typed provider。Static inference 已消费该 resolver;training 在 equivalence migration 前仍直接加载 `SystemProfile`。`VidurProfileBaseline` 保持 baseline-only;独立的 `VidurProfileImporter` 才是显式 evidence promotion 路径。详见 [Cost Provider 与性能数据导入](providers.md)。 ## Request Contract @@ -94,9 +94,9 @@ Calibration 从 observation 学习 target-wide 或 implementation-family respons 禁止输入 benchmark case ID、comparison-oracle total time,或唯一作用是匹配某张表的 per-model correction factor。这些变量不能解释因果 target behavior,也无法泛化到新 plan。 -## 从 HardwareProfile 迁移 +## 从 SystemProfile 迁移 -现有 `HardwareProfile` 已经提供 matrix/vector throughput、memory transfer 与 collective 的有用版本化 curve。迁移应通过 provider 保持现有行为: +现有 `SystemProfile` 已经提供 matrix/vector throughput、memory transfer 与 collective 的有用版本化 curve。迁移应通过 provider 保持现有行为: 1. **Static inference 已完成:**把 portable task 转换为 normalized query; 2. **已完成:**将当前 profile 包装为 roofline/system-evidence provider; diff --git a/docs/design/performance/index.en.md b/docs/design/performance/index.en.md index 0bd98cb..3589601 100644 --- a/docs/design/performance/index.en.md +++ b/docs/design/performance/index.en.md @@ -78,7 +78,7 @@ This does not mean every stage is assigned a wall-clock duration. Early stages a The repository now provides normalized `CostQuery`/`CostEstimate` contracts, ordered `CostResolver` policy, analytical `RooflineCostProvider`, an immutable exact-selector `PerformanceDatabase`, generic simulator table ingestion, and explicit Vidur and AIConfigurator importers. Static inference derives queries from portable task facts and resolves both task and pipeline communication costs. It still separates admissible cost providers from the read-only `InferenceBaseline.lookup()` comparison contract. -This remains an implemented slice, not a complete architecture-exploration evidence service. The database supports exact declared selectors and repeated-sample uncertainty, but not calibrated interpolation, a durable append-only raw-evidence service, environment manifests, discrete-event simulation, observation ingestion, or calibration. Training still uses direct `HardwareProfile` costing until Calculon equivalence tests protect its resolver migration. See [Cost Providers and Performance-Data Imports](providers.md) for the executable boundary. +This remains an implemented slice, not a complete architecture-exploration evidence service. The database supports exact declared selectors and repeated-sample uncertainty, but not calibrated interpolation, a durable append-only raw-evidence service, environment manifests, discrete-event simulation, observation ingestion, or calibration. Training still uses direct `SystemProfile` costing until Calculon equivalence tests protect its resolver migration. See [Cost Providers and Performance-Data Imports](providers.md) for the executable boundary. ## Design invariants diff --git a/docs/design/performance/index.zh.md b/docs/design/performance/index.zh.md index d0fc93a..52d55a9 100644 --- a/docs/design/performance/index.zh.md +++ b/docs/design/performance/index.zh.md @@ -78,7 +78,7 @@ Planner 可以优化 expected latency、conservative bound 或 risk-adjusted obj 仓库现在已经提供 normalized `CostQuery`/`CostEstimate` contract、ordered `CostResolver` policy、analytical `RooflineCostProvider`、immutable exact-selector `PerformanceDatabase`、通用 simulator table ingestion,以及显式 Vidur/AIConfigurator importer。Static inference 从 portable task facts 推导 query,并解析 task 与 pipeline communication cost;同时仍严格区分 admissible cost provider 与只读 `InferenceBaseline.lookup()` comparison contract。 -这仍是 implemented slice,而不是完整 architecture-exploration evidence service。Database 支持 exact declared selector 与 repeated-sample uncertainty,但还没有 calibrated interpolation、durable append-only raw-evidence service、environment manifest、discrete-event simulation、observation ingestion 或 calibration。Training 在 Calculon equivalence test 能保护 resolver migration 之前,仍直接使用 `HardwareProfile` costing。可运行边界见 [Cost Provider 与性能数据导入](providers.md)。 +这仍是 implemented slice,而不是完整 architecture-exploration evidence service。Database 支持 exact declared selector 与 repeated-sample uncertainty,但还没有 calibrated interpolation、durable append-only raw-evidence service、environment manifest、discrete-event simulation、observation ingestion 或 calibration。Training 在 Calculon equivalence test 能保护 resolver migration 之前,仍直接使用 `SystemProfile` costing。可运行边界见 [Cost Provider 与性能数据导入](providers.md)。 ## 设计不变量 diff --git a/docs/design/performance/providers.en.md b/docs/design/performance/providers.en.md index bbfd981..7cf8657 100644 --- a/docs/design/performance/providers.en.md +++ b/docs/design/performance/providers.en.md @@ -34,7 +34,7 @@ Every field participates in the canonical query digest. Empty optional identity ## Roofline provider -`RooflineCostProvider` wraps a versioned `HardwareProfile`. For a local operator it computes: +`RooflineCostProvider` wraps a versioned `SystemProfile`. For a local operator it computes: ```text compute_time = operations / effective_engine_throughput diff --git a/docs/design/performance/providers.zh.md b/docs/design/performance/providers.zh.md index e4d7716..292f334 100644 --- a/docs/design/performance/providers.zh.md +++ b/docs/design/performance/providers.zh.md @@ -34,7 +34,7 @@ Resolver 只选择一个 provider,不会把 correction 相乘,也不会平 ## Roofline Provider -`RooflineCostProvider` 包装版本化 `HardwareProfile`。对于 local operator,它计算: +`RooflineCostProvider` 包装版本化 `SystemProfile`。对于 local operator,它计算: ```text compute_time = operations / effective_engine_throughput diff --git a/docs/experiments/calculon-calibration.en.md b/docs/experiments/calculon-calibration.en.md index 5457ad2..0ca0435 100644 --- a/docs/experiments/calculon-calibration.en.md +++ b/docs/experiments/calculon-calibration.en.md @@ -148,7 +148,8 @@ The original eight parametrized training regressions remain in `tests/synthesize Implementation map: -- `synthesizer/models/transformer.py`: typed frontend and execution facts; +- `workload/transformer.py`: typed workload and execution facts; +- `synthesizer/frontend/transformer.py`: canonical import and binding adapter; - `analysis/transformer_workload.py`: static operation/byte analysis; - `synthesizer/lowering/transformer.py`: the two canonical derivation passes; - `analysis/cost_model.py`: peak-only and evidence-backed views; diff --git a/docs/experiments/calculon-calibration.zh.md b/docs/experiments/calculon-calibration.zh.md index d3fd159..4a7de77 100644 --- a/docs/experiments/calculon-calibration.zh.md +++ b/docs/experiments/calculon-calibration.zh.md @@ -148,7 +148,8 @@ uv run pytest -m baseline_regression tests/regression 实现映射: -- `synthesizer/models/transformer.py`:typed frontend 与 execution facts; +- `workload/transformer.py`:typed workload 与 execution facts; +- `synthesizer/frontend/transformer.py`:canonical import 与 binding adapter; - `analysis/transformer_workload.py`:静态 operation/byte analysis; - `synthesizer/lowering/transformer.py`:两个 canonical derivation pass; - `analysis/cost_model.py`:peak-only 与 evidence-backed view; diff --git a/docs/exploration/design-space.en.md b/docs/exploration/design-space.en.md index 01e518f..5f7b2d8 100644 --- a/docs/exploration/design-space.en.md +++ b/docs/exploration/design-space.en.md @@ -3,7 +3,7 @@ Hardware exploration begins with a typed, versioned candidate definition. A candidate is not just a device name or peak-FLOP number; it is a composable blueprint of resources, topology, constraints, and implementation capabilities that can be mapped, simulated, compared, and revised. !!! note "Design status" - This page defines the accepted product model. The current repository has `HardwareProfile` evidence but not yet a complete public `ArchitectureBlueprint` schema or search API. + This page defines the accepted product model. The current repository has `SystemProfile` evidence but not yet a complete public `ArchitectureBlueprint` schema or search API. ## Candidate blueprint diff --git a/docs/exploration/design-space.zh.md b/docs/exploration/design-space.zh.md index 6614666..8768c56 100644 --- a/docs/exploration/design-space.zh.md +++ b/docs/exploration/design-space.zh.md @@ -3,7 +3,7 @@ 硬件探索从强类型、版本化 candidate definition 开始。Candidate 不能只是 device name 或 peak-FLOP number;它是一份可组合的 resource、topology、constraint 与 implementation capability 蓝图,能够被 mapping、simulation、comparison 与 revision。 !!! note "设计状态" - 本页定义已接受的产品模型。当前仓库已有 `HardwareProfile` evidence,但尚无完整 public `ArchitectureBlueprint` schema 或 search API。 + 本页定义已接受的产品模型。当前仓库已有 `SystemProfile` evidence,但尚无完整 public `ArchitectureBlueprint` schema 或 search API。 ## Candidate Blueprint diff --git a/docs/modeling/hardware.en.md b/docs/modeling/hardware.en.md index 3863b83..014a935 100644 --- a/docs/modeling/hardware.en.md +++ b/docs/modeling/hardware.en.md @@ -3,7 +3,7 @@ The hardware model is the semantic description of a candidate architecture. It defines what resources and capabilities exist and how they connect. Performance evidence estimates how those resources behave; deployment identifies concrete instances. These concerns must remain separate for design-space exploration to be meaningful. !!! note "Design status" - The current `HardwareProfile` implements a limited evidence profile for compute, memory, and networks. The hierarchical architecture schema described here is the target design and is not yet connected end to end. + The current `SystemProfile` implements a limited evidence profile for compute, memory, and networks. The hierarchical architecture schema described here is the target design and is not yet connected end to end. ## Architecture, deployment, and evidence @@ -94,6 +94,6 @@ Schema migration is explicit. Published experiments retain the original blueprin ## Current implementation gap -`HardwareProfile` currently supplies matrix/vector throughput curves, memory capacity/bandwidth curves, network tiers, and collective models used by the Calculon calibration. It does not yet model component hierarchy, NoC, queues, power/area/cost, architecture variables, or a general target capability graph. +`SystemProfile` currently supplies matrix/vector throughput curves, memory capacity/bandwidth curves, network tiers, and collective models used by the Calculon calibration. It does not yet model component hierarchy, NoC, queues, power/area/cost, architecture variables, or a general target capability graph. -The first migration step is to wrap `HardwareProfile` as evidence for a minimal virtual `ArchitectureBlueprint`, preserving existing results while introducing the separation above. See the [roadmap](../project/roadmap.md). +The first migration step is to wrap `SystemProfile` as evidence for a minimal virtual `ArchitectureBlueprint`, preserving existing results while introducing the separation above. See the [roadmap](../project/roadmap.md). diff --git a/docs/modeling/hardware.zh.md b/docs/modeling/hardware.zh.md index 6298132..d33e910 100644 --- a/docs/modeling/hardware.zh.md +++ b/docs/modeling/hardware.zh.md @@ -3,7 +3,7 @@ Hardware model 是 candidate architecture 的语义描述,定义存在哪些 resource/capability,以及它们如何连接。Performance evidence 估算这些 resource 如何表现;deployment 标识具体 instance。只有保持三者独立,design-space exploration 才有意义。 !!! note "设计状态" - 当前 `HardwareProfile` 只实现有限的 compute、memory 与 network evidence profile。这里描述的 hierarchical architecture schema 是目标设计,尚未端到端连接。 + 当前 `SystemProfile` 只实现有限的 compute、memory 与 network evidence profile。这里描述的 hierarchical architecture schema 是目标设计,尚未端到端连接。 ## Architecture、Deployment 与 Evidence @@ -94,6 +94,6 @@ Schema migration 必须显式。已发布 experiment 保留原始 blueprint/prov ## 当前实现差距 -`HardwareProfile` 当前提供 matrix/vector throughput curve、memory capacity/bandwidth curve、network tier 与 collective model,用于 Calculon calibration。它尚未建模 component hierarchy、NoC、queue、power/area/cost、architecture variable 或通用 target capability graph。 +`SystemProfile` 当前提供 matrix/vector throughput curve、memory capacity/bandwidth curve、network tier 与 collective model,用于 Calculon calibration。它尚未建模 component hierarchy、NoC、queue、power/area/cost、architecture variable 或通用 target capability graph。 -第一步迁移应把 `HardwareProfile` 包装为 minimal virtual `ArchitectureBlueprint` 的 evidence,在保持现有结果的同时引入上述分离。参见[路线图](../project/roadmap.md)。 +第一步迁移应把 `SystemProfile` 包装为 minimal virtual `ArchitectureBlueprint` 的 evidence,在保持现有结果的同时引入上述分离。参见[路线图](../project/roadmap.md)。 diff --git a/docs/modeling/inference.en.md b/docs/modeling/inference.en.md index 9d33145..c6680ac 100644 --- a/docs/modeling/inference.en.md +++ b/docs/modeling/inference.en.md @@ -6,7 +6,7 @@ Blueprinting now has a runnable decoder-inference slice, but its boundary is int ## What we adopt from related work -[LLMCompass](https://arxiv.org/abs/2312.03134) demonstrates why LLM inference hardware evaluation needs separate software, hardware, mapping, and cost concerns, plus an explicit mapping search rather than a single closed-form model. Blueprinting adopts that separation. Its canonical representations preserve workload and mapping facts before a hardware profile or measured latency is consulted. LLMCompass's area/cost and architecture design-space machinery remains future provider and exploration work; its artifact code is not copied into the canonical IR. +[LLMCompass](https://arxiv.org/abs/2312.03134) demonstrates why LLM inference hardware evaluation needs separate software, hardware, mapping, and cost concerns, plus an explicit mapping search rather than a single closed-form model. Blueprinting adopts that separation. Its canonical representations preserve workload and mapping facts before a system profile or measured latency is consulted. LLMCompass's area/cost and architecture design-space machinery remains future provider and exploration work; its artifact code is not copied into the canonical IR. [Vidur](https://github.com/microsoft/vidur) demonstrates a complementary boundary: request arrivals, replica scheduling, batching, and event progression are a discrete-event layer, while execution time is supplied by component predictors trained from profiling data. Blueprinting adopts that split. Phase plans are the stable cost subjects; a future serving simulator will schedule requests and batches against them rather than redefining Transformer work inside scheduler code. @@ -17,7 +17,7 @@ The resulting boundary is deliberate: | Transformer operation/byte/collective derivation | canonical inference analysis | Implemented slice | | Prefill and decode specialization | workload binding + lowering passes | Implemented slice | | KV-cache state and capacity | ModelIR effect + portable state buffer + memory view | Implemented slice | -| Analytical component cost | `HardwareProfile` fallback | Implemented slice | +| Analytical component cost | `SystemProfile` fallback | Implemented slice | | Vidur profiling CSV reuse | post-hoc exact-match baseline | Implemented experiment | | Static decoder-block phase composition | inference application service | Implemented slice | | Arrivals, queues, continuous batching, scheduling | serving discrete-event simulator | Planned | @@ -70,10 +70,11 @@ It is multiplied by the number of blocks in one pipeline stage. Weight storage i `VidurProfileBaseline.from_csv(...)` consumes user-supplied Vidur `attention.csv` and compute/MLP CSV files. The caller must pin an upstream revision, hardware identity, attention backend, and cache block size. The adapter hashes the inputs and identity into a baseline revision, converts Vidur's millisecond medians to seconds, and only returns a reference when model dimensions, maximum sequence length, TP, batch/token shape, phase, backend, block size, and context match exactly. Vidur records decode `kv_cache_size` before the current token is appended; Blueprinting records the visible context after append, so the adapter makes the explicit relation `vidur_kv_cache_size = context_tokens - 1`. ```python -from blueprinting.analysis import HardwareProfile, VidurProfileBaseline +from blueprinting.analysis import VidurProfileBaseline +from blueprinting.system import SystemProfile from blueprinting.synthesizer.bindings import InferencePhase from blueprinting.synthesizer.experiments import VidurExperimentCase, run_vidur_experiment -from blueprinting.synthesizer.models import TransformerInferenceExecutionSpec, TransformerModelSpec +from blueprinting.workload import TransformerInferenceExecutionSpec, TransformerModelSpec baseline = VidurProfileBaseline.from_csv( attention_csv="/profiles/attention.csv", @@ -88,7 +89,7 @@ case = VidurExperimentCase( name="decode/context-128", model=TransformerModelSpec(...), execution=TransformerInferenceExecutionSpec(...), - hardware=HardwareProfile(...), + hardware=SystemProfile(...), phase=InferencePhase.DECODE, batch_size=1, context_tokens=128, diff --git a/docs/modeling/inference.zh.md b/docs/modeling/inference.zh.md index 4af743d..0c8214f 100644 --- a/docs/modeling/inference.zh.md +++ b/docs/modeling/inference.zh.md @@ -6,7 +6,7 @@ Blueprinting 现在已经具备一条可运行的 decoder inference 切片,但 ## 从相关工作中吸收什么 -[LLMCompass](https://arxiv.org/abs/2312.03134)说明,LLM 推理硬件评估需要分离 software、hardware、mapping 与 cost,并通过显式 mapping search 取代单一闭式模型。Blueprinting 吸收了这个分层:canonical representation 先保存 workload 与 mapping 事实,之后才允许 hardware profile 或测量 latency 参与。LLMCompass 的 area/cost 与 architecture design-space machinery 属于后续 provider 和 exploration 工作;本项目没有把其 artifact code 复制进 canonical IR。 +[LLMCompass](https://arxiv.org/abs/2312.03134)说明,LLM 推理硬件评估需要分离 software、hardware、mapping 与 cost,并通过显式 mapping search 取代单一闭式模型。Blueprinting 吸收了这个分层:canonical representation 先保存 workload 与 mapping 事实,之后才允许 system profile 或测量 latency 参与。LLMCompass 的 area/cost 与 architecture design-space machinery 属于后续 provider 和 exploration 工作;本项目没有把其 artifact code 复制进 canonical IR。 [Vidur](https://github.com/microsoft/vidur)提供了另一条关键边界:request arrival、replica scheduling、batching 和 event progression 属于离散事件层,execution time 则由基于 profiling data 的 component predictor 提供。Blueprinting 吸收了这层分离:phase plan 是稳定的 cost subject;未来 serving simulator 在其上调度 request/batch,而不是在 scheduler 代码里重新定义 Transformer work。 @@ -17,7 +17,7 @@ Blueprinting 现在已经具备一条可运行的 decoder inference 切片,但 | Transformer operation/byte/collective 推导 | canonical inference analysis | Implemented slice | | Prefill 与 decode 特化 | workload binding + lowering passes | Implemented slice | | KV-cache state 与容量 | ModelIR effect + portable state buffer + memory view | Implemented slice | -| 解析式 component cost | `HardwareProfile` fallback | Implemented slice | +| 解析式 component cost | `SystemProfile` fallback | Implemented slice | | Vidur profiling CSV 复用 | post-hoc exact-match baseline | Implemented experiment | | 静态 decoder-block phase composition | inference application service | Implemented slice | | Arrival、queue、continuous batching、scheduling | serving discrete-event simulator | Planned | @@ -70,10 +70,11 @@ mean decode-step model time = decode total / (O-1), when O > 1 `VidurProfileBaseline.from_csv(...)` 读取用户提供的 Vidur `attention.csv` 与 compute/MLP CSV。调用者必须固定 upstream revision、hardware identity、attention backend 与 cache block size。Adapter 把输入文件和 identity 一起哈希为 baseline revision,将 Vidur 的毫秒 median 转为秒;只有 model dimension、maximum sequence length、TP、batch/token shape、phase、backend、block size 与 context 完全匹配时才返回 reference。Vidur 的 decode `kv_cache_size` 表示当前 token 写入前的长度,而 Blueprinting 的 context 表示写入后 attention 可见的长度,因此 adapter 显式使用 `vidur_kv_cache_size = context_tokens - 1`。 ```python -from blueprinting.analysis import HardwareProfile, VidurProfileBaseline +from blueprinting.analysis import VidurProfileBaseline +from blueprinting.system import SystemProfile from blueprinting.synthesizer.bindings import InferencePhase from blueprinting.synthesizer.experiments import VidurExperimentCase, run_vidur_experiment -from blueprinting.synthesizer.models import TransformerInferenceExecutionSpec, TransformerModelSpec +from blueprinting.workload import TransformerInferenceExecutionSpec, TransformerModelSpec baseline = VidurProfileBaseline.from_csv( attention_csv="/profiles/attention.csv", @@ -88,7 +89,7 @@ case = VidurExperimentCase( name="decode/context-128", model=TransformerModelSpec(...), execution=TransformerInferenceExecutionSpec(...), - hardware=HardwareProfile(...), + hardware=SystemProfile(...), phase=InferencePhase.DECODE, batch_size=1, context_tokens=128, diff --git a/docs/project/adr/0002-workload-system-domains.en.md b/docs/project/adr/0002-workload-system-domains.en.md new file mode 100644 index 0000000..dce5539 --- /dev/null +++ b/docs/project/adr/0002-workload-system-domains.en.md @@ -0,0 +1,76 @@ +# ADR-0002: Separate Workload and System Domain Packages + +- Date: 2026-08-09 +- Status: Accepted +- Scope: domain ownership, Python package paths, frontend adapters, and system-profile naming + +## Context + +Blueprinting derives mappings between two independent domain inputs: a workload and a candidate system. The source tree did not express that symmetry. Transformer semantics and request contracts lived under `blueprinting.synthesizer.models`, making the derivation engine appear to own its input domain. Compute, memory, and network profiles lived in `blueprinting.analysis.cost_model`, making cost analysis appear to own the system being evaluated. + +This placement obscured late binding and created the wrong dependency pressure. New workload importers would have accumulated inside the synthesizer, while new chip and interconnect abstractions would have accumulated inside a cost estimator. It also made it difficult to distinguish system facts from analysis policy and a limited evidence profile from the planned `ArchitectureBlueprint`. + +## Decision drivers + +- Make workload and system explicit, symmetric top-level inputs to exploration. +- Keep canonical derivation mechanics separate from domain contracts. +- Prevent cost analysis from owning chip, memory, or interconnect semantics. +- Keep target binding late and prevent system facts from leaking into workload state. +- Preserve canonical tags and target-neutral derivation digests during source reorganization. +- Avoid compatibility re-exports that would leave ownership ambiguous. + +## Considered alternatives + +**Leave workload types in `synthesizer.models`.** This minimizes imports but makes the synthesizer own both its input and its transformation semantics. + +**Leave system profiles in `analysis.cost_model`.** This preserves a small module count but couples the object being evaluated to one evaluation policy. + +**Add facade packages that re-export the old implementations.** This creates attractive new paths without moving authority; both old and new packages would remain plausible sources of truth. + +**Introduce the complete `ArchitectureBlueprint` immediately.** The current data and consumers do not yet support component hierarchy, NoC, power/area/cost, design variables, legality, or physical deployment. Naming the limited profile as the final architecture contract would overstate implementation maturity. + +## Decision + +Create two authoritative top-level domain packages: + +- `blueprinting.workload` owns target-neutral model semantics, request scenarios, and logical mapping intent. It currently contains the typed Transformer training and inference contracts. +- `blueprinting.system` owns chip compute engines, memory, interconnect tiers, collective volume rules, and the aggregate `SystemProfile` imported from the retained system data. + +Move workload-to-canonical-state adapters into `blueprinting.synthesizer.frontend`. These adapters construct `ModelIR` and explicit `SynthesisSession` bindings; workload contracts themselves do neither. Lowering consumes workload contracts but remains owned by the synthesizer. + +Rename `HardwareProfile` to `SystemProfile` and remove its re-export from `blueprinting.analysis`. Analysis selects whether to apply profile efficiency evidence through an explicit policy argument; the system package does not import or choose `CalibrationMode`. + +Do not provide `blueprinting.synthesizer.models` or `blueprinting.analysis.SystemProfile` compatibility facades. The legacy `blueprinting.types.system` package remains only for the retained calculator path and is not an admissible dependency for new formal-analysis code. + +Preserve the existing `compiler.transformer.*` and `compiler.analysis.*` codec tags, including `compiler.analysis.hardware_profile.v1`. They are opaque wire identities. The workload and system record fields remain unchanged, so canonical JSON and target-neutral plan digests remain stable. + +`SystemProfile` is explicitly an evidence-bearing compute/memory/network adapter. It is not the future hierarchical `ArchitectureBlueprint`, a deployment description, or a target binding. + +## Consequences + +- Domain ownership is visible from imports: workload, system, synthesis frontend, and analysis have distinct paths. +- Framework/model importers can grow under `workload` without becoming derivation passes. +- Chip and interconnect contracts can evolve under `system` without being tied to roofline or database providers. +- Existing Python callers must replace old package paths and the `HardwareProfile` class name. +- Existing canonical JSON remains readable because codec tags and fields are preserved; pickle/module-path compatibility is not supported. +- The current logical execution specs still combine workload scenario and mapping intent. Further separation into workload scenario and mapping strategy requires a later ADR if it changes serialized contracts. + +## Migration + +1. Import Transformer contracts from `blueprinting.workload`. +2. Import `build_transformer_*_model_ir()` and synthesis-session helpers from `blueprinting.synthesizer.frontend`. +3. Import `SystemProfile` and chip/interconnect component profiles from `blueprinting.system`. +4. Keep costing APIs in `blueprinting.analysis`; pass a `SystemProfile` explicitly. +5. Do not add new dependencies on `blueprinting.types.system` or recreate compatibility exports under the old paths. + +## Validation + +- Package-boundary tests require both top-level domain packages and reject `blueprinting.synthesizer.models`. +- Ownership tests require frontend builders to be absent from `workload` and `SystemProfile` to be absent from `analysis`. +- System tests validate chip, memory, interconnect, capacity, policy selection, and canonical round-trip behavior. +- Existing golden IR snapshots and training/inference baseline gates verify that the source move does not change target-neutral semantics or estimates. +- Ruff, the full pytest suite, bilingual documentation parity, and strict MkDocs builds remain release gates. + +## Status + +Accepted on 2026-08-09. This ADR governs the initial workload/system package split. The complete architecture-blueprint schema and any split of logical execution specs remain separate future decisions. diff --git a/docs/project/adr/0002-workload-system-domains.zh.md b/docs/project/adr/0002-workload-system-domains.zh.md new file mode 100644 index 0000000..ecd1f2c --- /dev/null +++ b/docs/project/adr/0002-workload-system-domains.zh.md @@ -0,0 +1,76 @@ +# ADR-0002:拆分 Workload 与 System 领域 Package + +- 日期:2026-08-09 +- 状态:Accepted +- 范围:domain ownership、Python package path、frontend adapter 与 system-profile naming + +## 背景 + +Blueprinting 在两个独立 domain input 之间推导 mapping:workload 与 candidate system。原有源码结构没有表达这种对称关系。Transformer semantic/request contract 位于 `blueprinting.synthesizer.models`,让 derivation engine 看起来拥有输入领域;compute、memory 与 network profile 位于 `blueprinting.analysis.cost_model`,让 cost analysis 看起来拥有被评估的系统。 + +这种放置方式掩盖了 late binding,也产生了错误的依赖压力:新的 workload importer 会不断堆入 synthesizer,新的 chip/interconnect abstraction 会不断堆入 cost estimator。同时,system fact 与 analysis policy、有限 evidence profile 与计划中的 `ArchitectureBlueprint` 都难以区分。 + +## 决策驱动因素 + +- 让 workload/system 成为 exploration 显式且对称的顶层输入。 +- 让 canonical derivation mechanics 与 domain contract 分离。 +- 防止 cost analysis 拥有 chip、memory 或 interconnect semantic。 +- 保持 target late binding,防止 system fact 泄漏进 workload state。 +- 在源码重组时保留 canonical tag 与 target-neutral derivation digest。 +- 避免 compatibility re-export 继续制造 ownership 歧义。 + +## 备选方案 + +**继续把 workload type 放在 `synthesizer.models`。** Import 改动最少,但 synthesizer 会同时拥有输入和 transformation semantic。 + +**继续把 system profile 放在 `analysis.cost_model`。** Module 数量较少,却把被评估对象和某一种 evaluation policy 耦合起来。 + +**增加 facade package 并 re-export 旧实现。** 这样只有新路径更好看,authority 并未迁移;新旧 package 都会继续像 source of truth。 + +**立即实现完整 `ArchitectureBlueprint`。** 当前数据和 consumer 尚不支持 component hierarchy、NoC、power/area/cost、design variable、legality 或 physical deployment。把有限 profile 命名成最终 architecture contract 会夸大实现成熟度。 + +## 决策 + +创建两个权威顶层 domain package: + +- `blueprinting.workload` 拥有 target-neutral model semantic、request scenario 与 logical mapping intent;当前包含 typed Transformer training/inference contract。 +- `blueprinting.system` 拥有 chip compute engine、memory、interconnect tier、collective volume rule,以及从现有 system data 导入的聚合 `SystemProfile`。 + +把 workload-to-canonical-state adapter 移入 `blueprinting.synthesizer.frontend`。这些 adapter 构造 `ModelIR` 与显式 `SynthesisSession` binding;workload contract 自身不执行这些工作。Lowering 消费 workload contract,但继续属于 synthesizer。 + +将 `HardwareProfile` 重命名为 `SystemProfile`,并移除 `blueprinting.analysis` 中的 re-export。Analysis 通过显式 policy argument 决定是否应用 profile efficiency evidence;system package 不导入也不选择 `CalibrationMode`。 + +不提供 `blueprinting.synthesizer.models` 或 `blueprinting.analysis.SystemProfile` compatibility facade。Legacy `blueprinting.types.system` 只保留给旧 calculator path,新 formal-analysis code 不得依赖它。 + +保留现有 `compiler.transformer.*` 和 `compiler.analysis.*` codec tag,包括 `compiler.analysis.hardware_profile.v1`。它们是 opaque wire identity。Workload/system record field 不变,因此 canonical JSON 与 target-neutral plan digest 保持稳定。 + +`SystemProfile` 被明确限定为 evidence-bearing compute/memory/network adapter;它不是未来 hierarchical `ArchitectureBlueprint`、deployment description 或 target binding。 + +## 影响 + +- Import 会直接表达 domain ownership:workload、system、synthesis frontend 与 analysis 使用不同路径。 +- Framework/model importer 可以在 `workload` 下扩展,而不会变成 derivation pass。 +- Chip/interconnect contract 可以在 `system` 下演进,而不依赖 roofline/database provider。 +- 现有 Python caller 必须迁移旧 package path 与 `HardwareProfile` class name。 +- Codec tag/field 被保留,因此旧 canonical JSON 仍可读取;不支持 pickle/module-path compatibility。 +- 当前 logical execution spec 仍混合 workload scenario 与 mapping intent。若后续拆分会改变 serialized contract,需要新的 ADR。 + +## 迁移 + +1. 从 `blueprinting.workload` 导入 Transformer contract。 +2. 从 `blueprinting.synthesizer.frontend` 导入 `build_transformer_*_model_ir()` 与 synthesis-session helper。 +3. 从 `blueprinting.system` 导入 `SystemProfile` 与 chip/interconnect component profile。 +4. Costing API 继续位于 `blueprinting.analysis`,调用时显式传入 `SystemProfile`。 +5. 不得新增对 `blueprinting.types.system` 的依赖,也不得在旧路径下重建 compatibility export。 + +## 验证 + +- Package-boundary test 要求两个顶层 domain package 存在,并拒绝 `blueprinting.synthesizer.models`。 +- Ownership test 要求 frontend builder 不出现在 `workload`,`SystemProfile` 不出现在 `analysis`。 +- System test 验证 chip、memory、interconnect、capacity、policy selection 与 canonical round trip。 +- 现有 golden IR snapshot 和 training/inference baseline gate 验证源码迁移不改变 target-neutral semantic 或 estimate。 +- Ruff、完整 pytest、双语文档一致性与 strict MkDocs build 继续作为 release gate。 + +## 状态 + +本 ADR 于 2026-08-09 被接受,约束初始 workload/system package split。完整 architecture-blueprint schema 与 logical execution spec 的进一步拆分属于独立未来决策。 diff --git a/docs/project/decisions.en.md b/docs/project/decisions.en.md index 3c019f4..f75569d 100644 --- a/docs/project/decisions.en.md +++ b/docs/project/decisions.en.md @@ -20,6 +20,7 @@ This page is the compact index of architecture commitments, rejected alternative | Schema maturity | Internal schema versions are not automatically public compatibility promises | A contract graduates only after producer, independent consumer, migration, and conformance gates pass | | Documentation | Colocated suffix-based bilingual sources | Navigation and language switching remain page-aligned | | Formal derivation package | Hard-cut Python rename to `blueprinting.synthesizer`; preserve historical codec tags | Source ownership matches formal plan synthesis without invalidating unchanged canonical snapshots; see [ADR-0001](adr/0001-synthesizer-package.md) | +| Domain packages | `blueprinting.workload` owns target-neutral workload contracts; `blueprinting.system` owns chip/interconnect/system profiles | Synthesis and analysis consume explicit domain inputs without owning them; see [ADR-0002](adr/0002-workload-system-domains.md) | ## Rejected alternatives diff --git a/docs/project/decisions.zh.md b/docs/project/decisions.zh.md index d25d1fb..a82f4a5 100644 --- a/docs/project/decisions.zh.md +++ b/docs/project/decisions.zh.md @@ -20,6 +20,7 @@ | Schema maturity | Internal schema version 不自动构成 public compatibility promise | Producer、独立 consumer、migration 与 conformance Gate 通过后才毕业为 stable contract | | Documentation | 同目录 suffix-based 双语 source | Navigation 与 language switching 始终按页面对齐 | | 形式化推导 package | Python path 硬切为 `blueprinting.synthesizer`;保留历史 codec tag | Source ownership 对齐 formal plan synthesis,同时不破坏未变化的 canonical snapshot;见 [ADR-0001](adr/0001-synthesizer-package.md) | +| Domain package | `blueprinting.workload` 拥有 target-neutral workload contract;`blueprinting.system` 拥有 chip/interconnect/system profile | Synthesis/analysis 消费显式 domain input,但不拥有它们;见 [ADR-0002](adr/0002-workload-system-domains.md) | ## 被拒绝方案 diff --git a/docs/project/roadmap.en.md b/docs/project/roadmap.en.md index c975e0c..c0736b9 100644 --- a/docs/project/roadmap.en.md +++ b/docs/project/roadmap.en.md @@ -46,7 +46,7 @@ Implement a minimal `ArchitectureBlueprint` with: - system multiplicity/topology; - fixed, variable, derived, and constrained fields; - canonical identity and verifier; -- `HardwareProfile` adapted as versioned evidence rather than architecture truth. +- `SystemProfile` adapted as versioned evidence rather than architecture truth. Acceptance requires two materially different virtual blueprints that bind the same portable workload, reject incompatible mappings with diagnostics, and preserve the same source-workload digest. @@ -98,7 +98,7 @@ Hardware program emission strengthens validation but is not part of this minimum ## Near-term delivery order 1. Define the minimal hierarchical `ArchitectureBlueprint` and verifier. -2. Wrap `HardwareProfile` behind normalized evidence requests/results. +2. Wrap `SystemProfile` behind normalized evidence requests/results. 3. Add two parameterized virtual blueprints and architecture legality. 4. Complete portable buffers/lifetimes needed for resource mapping. 5. Use queue-centric and non-queue-centric virtual targets to freeze the common coordination core and typed-extension boundary. diff --git a/docs/project/roadmap.zh.md b/docs/project/roadmap.zh.md index b76bba8..16e70b8 100644 --- a/docs/project/roadmap.zh.md +++ b/docs/project/roadmap.zh.md @@ -46,7 +46,7 @@ Portable workload baseline 不得出现 architecture latency 或 kernel identity - system multiplicity/topology; - fixed、variable、derived 与 constrained field; - canonical identity 与 verifier; -- 将 `HardwareProfile` 适配为 versioned evidence,而不是 architecture truth。 +- 将 `SystemProfile` 适配为 versioned evidence,而不是 architecture truth。 验收要求两个实质不同 virtual blueprint 绑定同一 portable workload,对 incompatible mapping 给出 diagnostic,并保持相同 source-workload digest。 @@ -98,7 +98,7 @@ Hardware program emission 会增强验证,但不属于这个 minimum definitio ## 近期交付顺序 1. 定义最小 hierarchical `ArchitectureBlueprint` 与 verifier。 -2. 把 `HardwareProfile` 包装到 normalized evidence request/result 后。 +2. 把 `SystemProfile` 包装到 normalized evidence request/result 后。 3. 增加两个 parameterized virtual blueprint 与 architecture legality。 4. 完成 resource mapping 所需的 portable buffer/lifetime。 5. 用 queue-centric/non-queue-centric virtual target 冻结 common coordination core 与 typed extension boundary。 diff --git a/docs/project/status.en.md b/docs/project/status.en.md index 3fdefec..f580584 100644 --- a/docs/project/status.en.md +++ b/docs/project/status.en.md @@ -21,7 +21,7 @@ This page separates Blueprinting's hardware-exploration product goals from the e | Typed Transformer training workload accounting | **Implemented** | exact block operations, bytes, collectives, recomputation, and phases | | Static Transformer inference phase planning | **Implemented slice** | independently verified prefill/decode plans, KV capacity, and decoder-block phase composition | | Target-neutral workload/mapping plan | **Implemented slice** | Transformer path reaches `PortablePlanIR` | -| Versioned compute/memory/network efficiency profile | **Implemented adapter** | `HardwareProfile` and two analytical estimate modes | +| Versioned compute/memory/network efficiency profile | **Implemented adapter** | `SystemProfile` and two analytical estimate modes | | Normalized task-cost resolution and performance-data ingestion | **Implemented slice** | immutable query/result/store, ordered resolver, roofline fallback, generic simulator tables, Vidur profiles, and four AIConfigurator table families | | Vidur raw component-profile alignment | **Implemented experiment** | exact-key CSV lookup after independent lowering/costing, with component coverage and non-cancelling error attribution | | Calculon/SeqSel workload and cost calibration | **Implemented experiment** | eight-case reproducible report and tests | @@ -55,7 +55,7 @@ TransformerModelSpec + TransformerExecutionSpec -> ModelIR -> DistributedTaskIR -> PortablePlanIR - -> HardwareProfile analytical estimate + -> SystemProfile analytical estimate -> Calculon / paper comparison report TransformerModelSpec + inference mapping + request cohort @@ -67,7 +67,7 @@ TransformerModelSpec + inference mapping + request cohort -> static prefill / decode-step model time and analytical memory report ``` -`PassManager` verifies each staged derivation and exposes immutable checkpoints. `HardwareProfile` supplies evidence after the portable plan. The path does not yet construct an architecture hierarchy, bind physical resources, execute a discrete-event simulation, or search hardware candidates. +`PassManager` verifies each staged derivation and exposes immutable checkpoints. `SystemProfile` supplies evidence after the portable plan. The path does not yet construct an architecture hierarchy, bind physical resources, execute a discrete-event simulation, or search hardware candidates. ## What the current result can claim @@ -82,11 +82,12 @@ It cannot yet claim serving-system SLO accuracy: arrivals, queueing, continuous | Immutable values, stable IDs, lineage, codec, digests | **Implemented** | `src/blueprinting/synthesizer/{frozen,ids,codec}.py` | | Five progressive formal-representation schemas (`*IR`) and verifiers | **Experimental Contract** | `src/blueprinting/synthesizer/ir/`; only the first three have a production derivation slice | | Typed workload/strategy/target/deployment bindings | **Implemented** | `bindings.py`, `session.py` | +| Chip, memory, interconnect, and aggregate system profile | **Implemented adapter** | `src/blueprinting/system/`; evidence-bearing profile, not the planned `ArchitectureBlueprint` | | Transactional analyses/transformations, checkpoints, observers | **Implemented** | `passes/base.py` | -| Transformer semantic frontend and workload algebra | **Implemented slice** | `models/transformer.py`, `analysis/transformer_workload.py` | +| Transformer workload contracts, frontend, and workload algebra | **Implemented slice** | `workload/transformer.py`, `synthesizer/frontend/transformer.py`, `analysis/transformer_workload.py` | | Distributed and portable mapping derivations | **Implemented slice** | `lowering/transformer.py` | | Cost protocol, resolver, roofline, database, and external importers | **Implemented slice** | `analysis/cost/`, `analysis/vidur.py`; exact task latency only, not plan simulation | -| Static inference frontend, lowering, cost, and request composition | **Implemented slice** | `models/transformer_inference.py`, `analysis/{transformer_inference,inference_cost}.py`, `lowering/transformer_inference.py`, `application/inference.py` | +| Static inference frontend, lowering, cost, and request composition | **Implemented slice** | `workload/transformer_inference.py`, `synthesizer/frontend/transformer_inference.py`, `analysis/{transformer_inference,inference_cost}.py`, `synthesizer/lowering/transformer_inference.py`, `application/inference.py` | | Vidur raw component-profile alignment | **Implemented experiment** | `analysis/vidur.py` + `experiments/vidur.py`; a minimal licensed CI slice is pinned locally and the full upstream corpus remains external | | Calculon experiment | **Implemented experiment** | `experiments/calculon.py` | | External-baseline regression gate | **Implemented** | frozen contract and licensed offline fixtures under `data/validation/`; `experiments/regression.py`; `.github/workflows/quality.yml` | @@ -106,7 +107,7 @@ The next milestone is a minimal two-blueprint exploration: ```text one Transformer workload suite + two parameterized virtual ArchitectureBlueprints - + normalized HardwareProfile evidence + + normalized SystemProfile evidence -> legal architecture-bound plans -> deterministic resource simulation -> bottleneck + utilization + latency/memory comparison diff --git a/docs/project/status.zh.md b/docs/project/status.zh.md index e645233..33d68aa 100644 --- a/docs/project/status.zh.md +++ b/docs/project/status.zh.md @@ -21,7 +21,7 @@ | Typed Transformer training workload accounting | **Implemented** | 精确 block operation、byte、collective、recomputation 与 phase | | Static Transformer inference phase planning | **Implemented slice** | 独立验证的 prefill/decode plan、KV 容量以及 decoder-block phase composition | | Target-neutral workload/mapping plan | **Implemented slice** | Transformer path 到达 `PortablePlanIR` | -| 版本化 compute/memory/network efficiency profile | **Implemented adapter** | `HardwareProfile` 与两种 analytical estimate mode | +| 版本化 compute/memory/network efficiency profile | **Implemented adapter** | `SystemProfile` 与两种 analytical estimate mode | | Normalized task-cost resolution 与性能数据导入 | **Implemented slice** | immutable query/result/store、ordered resolver、roofline fallback、通用 simulator 表、Vidur profile 与四类 AIConfigurator 表 | | Vidur raw component-profile 对齐 | **Implemented experiment** | 独立 lowering/costing 后进行 exact-key CSV lookup,并报告 component coverage 与不可抵消的误差归因 | | Calculon/SeqSel workload 与 cost calibration | **Implemented experiment** | 8 case 可复现 report 与 test | @@ -55,7 +55,7 @@ TransformerModelSpec + TransformerExecutionSpec -> ModelIR -> DistributedTaskIR -> PortablePlanIR - -> HardwareProfile analytical estimate + -> SystemProfile analytical estimate -> Calculon / paper comparison report TransformerModelSpec + inference mapping + request cohort @@ -67,7 +67,7 @@ TransformerModelSpec + inference mapping + request cohort -> 静态 prefill / decode-step model time 与解析 memory report ``` -`PassManager` 验证每次 staged derivation 并暴露 immutable checkpoint。`HardwareProfile` 在 portable plan 后提供 evidence。当前路径尚未构造 architecture hierarchy、绑定 physical resource、执行 discrete-event simulation 或搜索 hardware candidate。 +`PassManager` 验证每次 staged derivation 并暴露 immutable checkpoint。`SystemProfile` 在 portable plan 后提供 evidence。当前路径尚未构造 architecture hierarchy、绑定 physical resource、执行 discrete-event simulation 或搜索 hardware candidate。 ## 当前结果可以声称什么 @@ -82,11 +82,12 @@ TransformerModelSpec + inference mapping + request cohort | Immutable value、stable ID、lineage、codec、digest | **Implemented** | `src/blueprinting/synthesizer/{frozen,ids,codec}.py` | | 五层 progressive formal-representation schema(`*IR`)与 verifier | **Experimental Contract** | `src/blueprinting/synthesizer/ir/`;只有前三层存在 production derivation slice | | Typed workload/strategy/target/deployment binding | **Implemented** | `bindings.py`、`session.py` | +| Chip、memory、interconnect 与聚合 system profile | **Implemented adapter** | `src/blueprinting/system/`;是 evidence-bearing profile,不是计划中的 `ArchitectureBlueprint` | | Transactional analysis/transformation、checkpoint、observer | **Implemented** | `passes/base.py` | -| Transformer semantic frontend 与 workload algebra | **Implemented slice** | `models/transformer.py`、`analysis/transformer_workload.py` | +| Transformer workload contract、frontend 与 workload algebra | **Implemented slice** | `workload/transformer.py`、`synthesizer/frontend/transformer.py`、`analysis/transformer_workload.py` | | Distributed/portable mapping derivation | **Implemented slice** | `lowering/transformer.py` | | Cost protocol、resolver、roofline、database 与外部 importer | **Implemented slice** | `analysis/cost/`、`analysis/vidur.py`;仅覆盖 exact task latency,不是 plan simulation | -| Static inference frontend、lowering、cost 与 request composition | **Implemented slice** | `models/transformer_inference.py`、`analysis/{transformer_inference,inference_cost}.py`、`lowering/transformer_inference.py`、`application/inference.py` | +| Static inference frontend、lowering、cost 与 request composition | **Implemented slice** | `workload/transformer_inference.py`、`synthesizer/frontend/transformer_inference.py`、`analysis/{transformer_inference,inference_cost}.py`、`synthesizer/lowering/transformer_inference.py`、`application/inference.py` | | Vidur raw component-profile 对齐 | **Implemented experiment** | `analysis/vidur.py` + `experiments/vidur.py`;最小带许可证 CI slice 固定在本地,完整 upstream corpus 仍保持外部依赖 | | Calculon experiment | **Implemented experiment** | `experiments/calculon.py` | | 外部 baseline 回归门禁 | **Implemented** | `data/validation/` 下的冻结 contract 与带许可证离线 fixture、`experiments/regression.py`、`.github/workflows/quality.yml` | @@ -106,7 +107,7 @@ TransformerModelSpec + inference mapping + request cohort ```text one Transformer workload suite + two parameterized virtual ArchitectureBlueprints - + normalized HardwareProfile evidence + + normalized SystemProfile evidence -> legal architecture-bound plans -> deterministic resource simulation -> bottleneck + utilization + latency/memory comparison diff --git a/mkdocs.yml b/mkdocs.yml index 1ad7668..4d67f63 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -79,6 +79,7 @@ nav: - Roadmap: project/roadmap.md - Decisions and Terminology: project/decisions.md - ADR-0001 — Synthesizer Package: project/adr/0001-synthesizer-package.md + - ADR-0002 — Workload and System Domains: project/adr/0002-workload-system-domains.md - Architecture Risk Register: project/risks.md - Documentation Guide: contributing/documentation.md @@ -138,6 +139,7 @@ plugins: Roadmap: 路线图 Decisions and Terminology: 设计决策与术语 ADR-0001 — Synthesizer Package: ADR-0001 — Synthesizer 包命名 + ADR-0002 — Workload and System Domains: ADR-0002 — Workload 与 System 领域 Architecture Risk Register: 架构风险登记表 Documentation Guide: 文档维护指南 diff --git a/src/blueprinting/analysis/__init__.py b/src/blueprinting/analysis/__init__.py index 2e34190..62138cd 100644 --- a/src/blueprinting/analysis/__init__.py +++ b/src/blueprinting/analysis/__init__.py @@ -26,7 +26,6 @@ from .cost_model import ( BlockEstimate, CalibrationMode, - HardwareProfile, IterationEstimate, IterationMemory, estimate_block, @@ -79,7 +78,6 @@ "EstimateMatch", "EstimateMethod", "EvidenceProvenance", - "HardwareProfile", "InferenceBlockMemoryFacts", "InferenceBaseline", "InferenceCostProvider", diff --git a/src/blueprinting/analysis/cost/roofline.py b/src/blueprinting/analysis/cost/roofline.py index f10a69c..726fe25 100644 --- a/src/blueprinting/analysis/cost/roofline.py +++ b/src/blueprinting/analysis/cost/roofline.py @@ -4,7 +4,8 @@ from ...synthesizer.codec import content_digest from ...synthesizer.frozen import FrozenDict -from ..cost_model import CalibrationMode, HardwareProfile +from ...system import SystemProfile +from ..cost_model import CalibrationMode from .protocol import ( CostEstimate, CostProvider, @@ -29,13 +30,13 @@ class RooflineCostProvider(CostProvider): def __init__( self, - hardware: HardwareProfile, + hardware: SystemProfile, *, mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, processing_mode: str = "roofline", ) -> None: - if not isinstance(hardware, HardwareProfile): - raise TypeError("hardware must be HardwareProfile") + if not isinstance(hardware, SystemProfile): + raise TypeError("hardware must be SystemProfile") if not isinstance(mode, CalibrationMode): raise TypeError("mode must be CalibrationMode") if processing_mode == "profile": @@ -67,14 +68,14 @@ def revision(self) -> str: return self._revision @property - def hardware(self) -> HardwareProfile: + def hardware(self) -> SystemProfile: return self._hardware def supports(self, query: CostQuery) -> CostSupport: if query.hardware != self._hardware.name: - return CostSupport.unavailable("query targets a different hardware profile") + return CostSupport.unavailable("query targets a different system profile") if query.datatype != self._hardware.datatype: - return CostSupport.unavailable("query datatype is not covered by this hardware profile") + return CostSupport.unavailable("query datatype is not covered by this system profile") if query.hardware_revision and query.hardware_revision != self._hardware.evidence_revision: return CostSupport.unavailable("query requires a different hardware evidence revision") if query.subject is CostSubject.OPERATOR: @@ -84,7 +85,7 @@ def supports(self, query: CostQuery) -> CostSupport: ) return CostSupport.available("compute/memory roofline is defined") if query.network_tier >= len(self._hardware.networks): - return CostSupport.unavailable("hardware profile does not define the requested network tier") + return CostSupport.unavailable("system profile does not define the requested network tier") network = self._hardware.networks[query.network_tier] if query.operation not in network.operations: return CostSupport.unavailable("network tier does not define the requested communication operation") @@ -103,10 +104,16 @@ def estimate(self, query: CostQuery) -> CostEstimate: if query.subject is CostSubject.OPERATOR: processor = self._hardware.matrix if query.engine == "matrix" else self._hardware.vector if query.operations: - compute_seconds = query.operations / processor.throughput(query.operations, self._mode) + compute_seconds = query.operations / processor.throughput( + query.operations, + apply_efficiency=self._mode is CalibrationMode.SYSTEM_EVIDENCE, + ) transferred_bytes = query.read_bytes + query.write_bytes if transferred_bytes: - memory_seconds = transferred_bytes / self._hardware.memory.throughput(transferred_bytes, self._mode) + memory_seconds = transferred_bytes / self._hardware.memory.throughput( + transferred_bytes, + apply_efficiency=self._mode is CalibrationMode.SYSTEM_EVIDENCE, + ) if self._processing_mode == "roofline": seconds = max(compute_seconds, memory_seconds) bottleneck = "compute" if compute_seconds >= memory_seconds else "memory" @@ -121,7 +128,7 @@ def estimate(self, query: CostQuery) -> CostEstimate: query.operation, query.message_bytes, query.participants, - self._mode, + apply_efficiency=self._mode is CalibrationMode.SYSTEM_EVIDENCE, ) seconds = network_seconds bottleneck = "network" diff --git a/src/blueprinting/analysis/cost_model.py b/src/blueprinting/analysis/cost_model.py index 6809cb8..b02115c 100644 --- a/src/blueprinting/analysis/cost_model.py +++ b/src/blueprinting/analysis/cost_model.py @@ -15,15 +15,13 @@ from __future__ import annotations import math -from collections.abc import Mapping from dataclasses import dataclass from enum import Enum -from typing import Any -from ..synthesizer.codec import content_digest, enum_type, record_type -from ..synthesizer.frozen import FrozenDict +from ..synthesizer.codec import enum_type from ..synthesizer.ir import CollectiveKind, PortablePlanIR -from ..synthesizer.models.transformer import ( +from ..system import SystemProfile +from ..workload import ( RecomputePolicy, TensorParallelCommunication, TransformerExecutionSpec, @@ -46,183 +44,6 @@ class CalibrationMode(Enum): SYSTEM_EVIDENCE = "system_evidence" -@record_type("compiler.analysis.efficiency_point.v1") -@dataclass(frozen=True) -class EfficiencyPoint: - threshold: int - efficiency: float - - def __post_init__(self) -> None: - if isinstance(self.threshold, bool) or not isinstance(self.threshold, int) or self.threshold < 0: - raise ValueError("efficiency threshold must be a non-negative integer") - if not isinstance(self.efficiency, (int, float)) or not 0 < self.efficiency <= 1: - raise ValueError("efficiency must be in (0, 1]") - - -@record_type("compiler.analysis.efficiency_curve.v1") -@dataclass(frozen=True) -class EfficiencyCurve: - points: tuple[EfficiencyPoint, ...] - - def __post_init__(self) -> None: - object.__setattr__(self, "points", tuple(self.points)) - if not self.points or any(not isinstance(point, EfficiencyPoint) for point in self.points): - raise ValueError("an efficiency curve requires typed points") - thresholds = tuple(point.threshold for point in self.points) - if thresholds != tuple(sorted(thresholds, reverse=True)) or len(set(thresholds)) != len(thresholds): - raise ValueError("efficiency thresholds must be unique and descending") - if thresholds[-1] != 0: - raise ValueError("efficiency curve must cover a zero threshold") - - def lookup(self, work: int) -> float: - if isinstance(work, bool) or not isinstance(work, int) or work < 0: - raise ValueError("curve lookup work must be a non-negative integer") - for point in self.points: - if work >= point.threshold: - return point.efficiency - raise AssertionError("zero-threshold curve failed to cover work") - - -@record_type("compiler.analysis.processor_profile.v1") -@dataclass(frozen=True) -class ProcessorProfile: - peak_operations_per_second: float - efficiency: EfficiencyCurve - - def throughput(self, operations: int, mode: CalibrationMode) -> float: - efficiency = self.efficiency.lookup(operations) if mode is CalibrationMode.SYSTEM_EVIDENCE else 1.0 - return self.peak_operations_per_second * efficiency - - -@record_type("compiler.analysis.memory_profile.v1") -@dataclass(frozen=True) -class MemoryProfile: - capacity_bytes: int - peak_bytes_per_second: float - efficiency: EfficiencyCurve - - def throughput(self, transferred_bytes: int, mode: CalibrationMode) -> float: - efficiency = self.efficiency.lookup(transferred_bytes) if mode is CalibrationMode.SYSTEM_EVIDENCE else 1.0 - return self.peak_bytes_per_second * efficiency - - -@record_type("compiler.analysis.network_operation.v1") -@dataclass(frozen=True) -class NetworkOperationProfile: - volume_multiplier: float - participant_offset: int - - -@record_type("compiler.analysis.network_profile.v1") -@dataclass(frozen=True) -class NetworkProfile: - peak_bytes_per_second: float - efficiency: float - latency_seconds: float - participant_capacity: int - operations: FrozenDict - - def __post_init__(self) -> None: - object.__setattr__(self, "operations", FrozenDict(self.operations)) - - def time( - self, - operation: str, - message_bytes: int, - participants: int, - mode: CalibrationMode, - ) -> float: - profile = self.operations.get(operation) - if not isinstance(profile, NetworkOperationProfile): - raise ValueError(f"network does not define operation {operation!r}") - if participants < 2: - return 0.0 - scaled = message_bytes * profile.volume_multiplier - scaled += scaled / participants * profile.participant_offset - efficiency = self.efficiency if mode is CalibrationMode.SYSTEM_EVIDENCE else 1.0 - return self.latency_seconds + scaled / (self.peak_bytes_per_second * efficiency) - - -@record_type("compiler.analysis.hardware_profile.v1") -@dataclass(frozen=True) -class HardwareProfile: - name: str - datatype: str - matrix: ProcessorProfile - vector: ProcessorProfile - memory: MemoryProfile - processing_mode: str - networks: tuple[NetworkProfile, ...] - evidence_revision: str - - def __post_init__(self) -> None: - object.__setattr__(self, "networks", tuple(self.networks)) - if self.processing_mode not in {"roofline", "no_overlap"}: - raise ValueError("processing_mode must be roofline or no_overlap") - if not self.name or not self.datatype or not self.evidence_revision: - raise ValueError("hardware profile identity must not be empty") - - @classmethod - def from_mapping( - cls, - name: str, - data: Mapping[str, Any], - *, - datatype: str, - ) -> HardwareProfile: - def processor(section: str) -> ProcessorProfile: - item = data[section][datatype] - curve = EfficiencyCurve( - tuple( - EfficiencyPoint(int(giga_operations * 1e9), efficiency) - for giga_operations, efficiency in item["gflops_efficiency"] - ) - ) - return ProcessorProfile(item["tflops"] * 1e12, curve) - - memory_data = data["mem1"] - memory_curve = EfficiencyCurve( - tuple( - EfficiencyPoint(int(megabytes * 1e6), efficiency) - for megabytes, efficiency in memory_data["MB_efficiency"] - ) - ) - networks = [] - for network in data["networks"]: - operations = {} - for operation, (multiplier, offset) in network["ops"].items(): - operations[operation] = NetworkOperationProfile(multiplier, 0 if offset is None else offset) - networks.append( - NetworkProfile( - peak_bytes_per_second=network["bandwidth"] * 1e9, - efficiency=network["efficiency"], - latency_seconds=network["latency"], - participant_capacity=network["size"], - operations=FrozenDict(operations), - ) - ) - revision = content_digest(FrozenDict(dict(data)), f"hardware-profile:{name}:{datatype}") - return cls( - name=name, - datatype=datatype, - matrix=processor("matrix"), - vector=processor("vector"), - memory=MemoryProfile( - int(memory_data["GiB"] * 1024**3), - memory_data["GBps"] * 1e9, - memory_curve, - ), - processing_mode=data["processing_mode"], - networks=tuple(networks), - evidence_revision=revision, - ) - - def processing_time(self, compute_seconds: float, memory_seconds: float) -> float: - if self.processing_mode == "roofline": - return max(compute_seconds, memory_seconds) - return compute_seconds + memory_seconds - - @dataclass(frozen=True) class TaskEstimate: invocation: PrimitiveInvocation @@ -286,15 +107,22 @@ class IterationEstimate: def _task_estimate( invocation: PrimitiveInvocation, - hardware: HardwareProfile, + hardware: SystemProfile, participants: int, mode: CalibrationMode, ) -> TaskEstimate: work = invocation.work processor = hardware.matrix if invocation.engine is EngineKind.MATRIX else hardware.vector - compute_seconds = work.operations / processor.throughput(work.operations, mode) if work.operations else 0.0 + apply_efficiency = mode is CalibrationMode.SYSTEM_EVIDENCE + compute_seconds = ( + work.operations / processor.throughput(work.operations, apply_efficiency=apply_efficiency) + if work.operations + else 0.0 + ) memory_seconds = ( - work.memory_bytes / hardware.memory.throughput(work.memory_bytes, mode) if work.memory_bytes else 0.0 + work.memory_bytes / hardware.memory.throughput(work.memory_bytes, apply_efficiency=apply_efficiency) + if work.memory_bytes + else 0.0 ) local_seconds = hardware.processing_time(compute_seconds, memory_seconds) network_seconds = 0.0 @@ -302,7 +130,12 @@ def _task_estimate( if invocation.network_tier is None or invocation.collective is None: raise ValueError("collective invocation is missing network facts") network = hardware.networks[invocation.network_tier] - network_seconds = network.time(invocation.collective.value, work.message_bytes, participants, mode) + network_seconds = network.time( + invocation.collective.value, + work.message_bytes, + participants, + apply_efficiency=apply_efficiency, + ) return TaskEstimate( invocation=invocation, compute_seconds=compute_seconds, @@ -314,7 +147,7 @@ def _task_estimate( def estimate_block( plan: PortablePlanIR, - hardware: HardwareProfile, + hardware: SystemProfile, mode: CalibrationMode, ) -> BlockEstimate: execution = plan.attributes.get("execution_spec") @@ -395,7 +228,7 @@ def _iteration_memory( def estimate_iteration( plan: PortablePlanIR, - hardware: HardwareProfile, + hardware: SystemProfile, mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, ) -> IterationEstimate: """Apply an explicit 1F1B/interleaved schedule to a derived block plan.""" @@ -410,7 +243,7 @@ def estimate_iteration( if not isinstance(block_memory, BlockMemoryFacts): raise TypeError("portable plan is missing BlockMemoryFacts") if hardware.datatype != execution.datatype: - raise ValueError("hardware profile datatype does not match execution datatype") + raise ValueError("system profile datatype does not match execution datatype") blocks_per_processor = math.ceil(model.block_count / execution.pipeline_parallel) if execution.pipeline_interleaving > blocks_per_processor: @@ -428,7 +261,12 @@ def estimate_iteration( pipeline_message = activation_elements * execution.bytes_per_element if execution.pipeline_parallel > 1: pipeline_network = hardware.networks[execution.pipeline_parallel_network] - pipeline_point_to_point = pipeline_network.time("p2p", pipeline_message, 2, mode) + pipeline_point_to_point = pipeline_network.time( + "p2p", + pipeline_message, + 2, + apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, + ) else: pipeline_point_to_point = 0.0 @@ -484,19 +322,19 @@ def estimate_iteration( CollectiveKind.REDUCE_SCATTER.value, block_memory.weights, execution.data_parallel, - mode, + apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, ) + network.time( CollectiveKind.ALL_GATHER.value, block_memory.weights, execution.data_parallel, - mode, + apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, ) else: per_block = network.time( CollectiveKind.ALL_REDUCE.value, block_memory.weights, execution.data_parallel, - mode, + apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, ) data_parallel = blocks_per_processor * per_block diff --git a/src/blueprinting/analysis/inference_cost.py b/src/blueprinting/analysis/inference_cost.py index e8699b9..66335af 100644 --- a/src/blueprinting/analysis/inference_cost.py +++ b/src/blueprinting/analysis/inference_cost.py @@ -7,10 +7,10 @@ from ..synthesizer.bindings import InferencePhase from ..synthesizer.frozen import FrozenDict from ..synthesizer.ir import CollectiveKind, PlanBuffer, PlanTask, PortablePlanIR -from ..synthesizer.models.transformer import TransformerModelSpec -from ..synthesizer.models.transformer_inference import TransformerInferenceExecutionSpec +from ..system import SystemProfile +from ..workload import TransformerInferenceExecutionSpec, TransformerModelSpec from .cost import CostQuery, CostQueryContext, CostResolver, CostSubject -from .cost_model import CalibrationMode, HardwareProfile +from .cost_model import CalibrationMode from .inference_evidence import InferenceCostProvider, InferenceEvidenceQuery from .transformer_inference import InferenceInvocation from .transformer_workload import EngineKind, PhaseWork @@ -63,7 +63,7 @@ class InferencePhaseEstimate: def inference_evidence_query_for( invocation: InferenceInvocation, *, - hardware: HardwareProfile, + hardware: SystemProfile, execution: TransformerInferenceExecutionSpec, model: TransformerModelSpec, batch_size: int, @@ -112,7 +112,7 @@ def _merge_dimensions(base: dict[str, object], extra: FrozenDict) -> FrozenDict: def cost_query_for_inference_task( task: PlanTask, *, - hardware: HardwareProfile, + hardware: SystemProfile, execution: TransformerInferenceExecutionSpec, model: TransformerModelSpec, batch_size: int, @@ -193,7 +193,7 @@ def cost_query_for_inference_task( def _task_estimate( task: PlanTask, *, - hardware: HardwareProfile, + hardware: SystemProfile, execution: TransformerInferenceExecutionSpec, model: TransformerModelSpec, batch_size: int, @@ -207,9 +207,16 @@ def _task_estimate( invocation = _invocation_from_plan_task(task) work = invocation.work processor = hardware.matrix if invocation.engine is EngineKind.MATRIX else hardware.vector - compute_seconds = work.operations / processor.throughput(work.operations, mode) if work.operations else 0.0 + apply_efficiency = mode is CalibrationMode.SYSTEM_EVIDENCE + compute_seconds = ( + work.operations / processor.throughput(work.operations, apply_efficiency=apply_efficiency) + if work.operations + else 0.0 + ) memory_seconds = ( - work.memory_bytes / hardware.memory.throughput(work.memory_bytes, mode) if work.memory_bytes else 0.0 + work.memory_bytes / hardware.memory.throughput(work.memory_bytes, apply_efficiency=apply_efficiency) + if work.memory_bytes + else 0.0 ) network_seconds = 0.0 if invocation.engine is EngineKind.COLLECTIVE: @@ -218,12 +225,12 @@ def _task_estimate( try: network = hardware.networks[invocation.network_tier] except IndexError as error: - raise ValueError(f"hardware profile does not define network tier {invocation.network_tier}") from error + raise ValueError(f"system profile does not define network tier {invocation.network_tier}") from error network_seconds = network.time( invocation.collective.value, work.message_bytes, execution.tensor_parallel, - mode, + apply_efficiency=apply_efficiency, ) analytical_seconds = hardware.processing_time(compute_seconds, memory_seconds) + network_seconds provider_name = "analytical-system-profile" @@ -358,7 +365,7 @@ def _semantic_buffer_size(plan: PortablePlanIR, semantic: str) -> int: def estimate_inference_phase( plan: PortablePlanIR, - hardware: HardwareProfile, + hardware: SystemProfile, mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, *, cost_provider: InferenceCostProvider | None = None, @@ -377,7 +384,7 @@ def estimate_inference_phase( if not isinstance(phase, InferencePhase): raise TypeError("portable inference plan is missing InferencePhase") if hardware.datatype != execution.datatype: - raise ValueError("hardware profile datatype does not match inference execution datatype") + raise ValueError("system profile datatype does not match inference execution datatype") if cost_provider is not None and cost_resolver is not None: raise ValueError("cost_provider and cost_resolver are mutually exclusive") if not isinstance(cost_context, CostQueryContext): @@ -417,9 +424,14 @@ def estimate_inference_phase( network = hardware.networks[execution.pipeline_parallel_network] except IndexError as error: raise ValueError( - f"hardware profile does not define network tier {execution.pipeline_parallel_network}" + f"system profile does not define network tier {execution.pipeline_parallel_network}" ) from error - one_hop_seconds = network.time("p2p", boundary_bytes, 2, mode) + one_hop_seconds = network.time( + "p2p", + boundary_bytes, + 2, + apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, + ) else: pipeline_dimensions = { "semantic_operation": "p2p", diff --git a/src/blueprinting/analysis/transformer_inference.py b/src/blueprinting/analysis/transformer_inference.py index 68354e5..146cac3 100644 --- a/src/blueprinting/analysis/transformer_inference.py +++ b/src/blueprinting/analysis/transformer_inference.py @@ -13,8 +13,7 @@ from ..synthesizer.bindings import InferencePhase from ..synthesizer.codec import record_type from ..synthesizer.ir import CollectiveKind -from ..synthesizer.models.transformer import TransformerModelSpec -from ..synthesizer.models.transformer_inference import TransformerInferenceExecutionSpec +from ..workload import TransformerInferenceExecutionSpec, TransformerModelSpec from .transformer_workload import EngineKind, PhaseWork # Keep the legacy codec namespace as a stable serialized identity. diff --git a/src/blueprinting/analysis/transformer_workload.py b/src/blueprinting/analysis/transformer_workload.py index 3752fdf..a2ba2f1 100644 --- a/src/blueprinting/analysis/transformer_workload.py +++ b/src/blueprinting/analysis/transformer_workload.py @@ -13,7 +13,7 @@ from ..synthesizer.codec import enum_type, record_type from ..synthesizer.ir import CollectiveKind -from ..synthesizer.models.transformer import ( +from ..workload import ( RecomputePolicy, TensorParallelCommunication, TransformerExecutionSpec, diff --git a/src/blueprinting/application/analysis.py b/src/blueprinting/application/analysis.py index 6cf69e7..2d1be31 100644 --- a/src/blueprinting/application/analysis.py +++ b/src/blueprinting/application/analysis.py @@ -16,23 +16,20 @@ from itertools import product from typing import TYPE_CHECKING, Any -from blueprinting.analysis import CalibrationMode, HardwareProfile, estimate_iteration +from blueprinting.analysis import CalibrationMode, estimate_iteration from blueprinting.synthesizer.codec import content_digest from blueprinting.synthesizer.errors import ( IRVerificationError, PassExecutionError, SynthesisError, ) +from blueprinting.synthesizer.frontend import build_transformer_model_ir, synthesis_session_for from blueprinting.synthesizer.frozen import FrozenDict, freeze, thaw from blueprinting.synthesizer.ir import DistributedTaskIR, ModelIR, PortablePlanIR from blueprinting.synthesizer.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass -from blueprinting.synthesizer.models import ( - TransformerExecutionSpec, - TransformerModelSpec, - build_transformer_model_ir, - synthesis_session_for, -) from blueprinting.synthesizer.passes import AnalysisStore, PassManager, PassPipeline +from blueprinting.system import SystemProfile +from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec LOGGER = logging.getLogger(__name__) @@ -450,7 +447,7 @@ def _analyze(self, draft: AnalysisDraft) -> AnalysisOutcome: model = TransformerModelSpec.from_mapping(draft.model_name, model_data) execution = TransformerExecutionSpec.from_mapping(execution_data) - hardware = HardwareProfile.from_mapping( + hardware = SystemProfile.from_mapping( draft.hardware_name, hardware_data, datatype=execution.datatype, diff --git a/src/blueprinting/application/inference.py b/src/blueprinting/application/inference.py index be43335..8a60a7b 100644 --- a/src/blueprinting/application/inference.py +++ b/src/blueprinting/application/inference.py @@ -16,7 +16,6 @@ from blueprinting.analysis import ( CalibrationMode, - HardwareProfile, InferenceCostProvider, InferencePhaseEstimate, estimate_inference_phase, @@ -24,17 +23,20 @@ from blueprinting.synthesizer.bindings import InferencePhase from blueprinting.synthesizer.codec import content_digest from blueprinting.synthesizer.errors import IRVerificationError, PassExecutionError, SynthesisError +from blueprinting.synthesizer.frontend import ( + build_transformer_inference_model_ir, + inference_synthesis_session_for, +) from blueprinting.synthesizer.frozen import FrozenDict, freeze, thaw from blueprinting.synthesizer.ir import ModelIR, PortablePlanIR from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from blueprinting.synthesizer.models import ( +from blueprinting.synthesizer.passes import AnalysisStore, PassCheckpoint, PassManager, PassPipeline +from blueprinting.system import SystemProfile +from blueprinting.workload import ( TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, TransformerModelSpec, - build_transformer_inference_model_ir, - inference_synthesis_session_for, ) -from blueprinting.synthesizer.passes import AnalysisStore, PassCheckpoint, PassManager, PassPipeline from .analysis import ( AnalysisDiagnostic, @@ -306,7 +308,7 @@ def _derive_phase( source: ModelIR, model: TransformerModelSpec, execution: TransformerInferenceExecutionSpec, - hardware: HardwareProfile, + hardware: SystemProfile, draft: InferenceAnalysisDraft, *, phase: InferencePhase, @@ -345,7 +347,7 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: request = TransformerInferenceRequestSpec.from_mapping(request_data) execution.validate_model(model) request.validate_model(model) - hardware = HardwareProfile.from_mapping( + hardware = SystemProfile.from_mapping( draft.hardware_name, hardware_data, datatype=execution.datatype, @@ -547,7 +549,7 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: "replicas 只参与映射合法性与 world-size 记账;当前报告是单 replica cohort latency,不估算跨 replica serving capacity。", "当前 workload dialect 支持 dense multi-head attention 与非 gated MLP;embedding、LM head 和 sampler 尚未建模。", "PortablePlanIR 尚未绑定 attention implementation;working memory 使用未融合 score materialization 的保守上界。", - "除非提供 Blueprinting cost provider,组件耗时使用共享 hardware profile 的解析 roofline 证据;comparison baseline 不参与该选择。", + "除非提供 Blueprinting cost provider,组件耗时使用共享 system profile 的解析 roofline 证据;comparison baseline 不参与该选择。", ), ) return InferenceAnalysisOutcome(draft.fingerprint, diagnostics, report) diff --git a/src/blueprinting/synthesizer/experiments/calculon.py b/src/blueprinting/synthesizer/experiments/calculon.py index c58125f..bb1f4bf 100644 --- a/src/blueprinting/synthesizer/experiments/calculon.py +++ b/src/blueprinting/synthesizer/experiments/calculon.py @@ -6,7 +6,7 @@ * a source of historical SeqSel paper values for held-out validation. Calculon results are never read while constructing IR, workload facts, or the -hardware profile. The calibrated estimate consumes only the same target-wide +system profile. The calibrated estimate consumes only the same target-wide system evidence curves for every case. """ @@ -23,19 +23,15 @@ from ...analysis.cost_model import ( CalibrationMode, - HardwareProfile, IterationEstimate, estimate_iteration, ) from ...analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase +from ...system import SystemProfile +from ...workload import TransformerExecutionSpec, TransformerModelSpec +from ..frontend import build_transformer_model_ir, synthesis_session_for from ..ir import PortablePlanIR from ..lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass -from ..models import ( - TransformerExecutionSpec, - TransformerModelSpec, - build_transformer_model_ir, - synthesis_session_for, -) from ..passes import PassManager, PassPipeline SEQSEL_TABLE5_SECONDS = { @@ -368,7 +364,7 @@ def run_calculon_experiment(cases: tuple[CalculonCase, ...]) -> CalculonExperime model = TransformerModelSpec.from_mapping(case.model_path.stem, model_data) execution = TransformerExecutionSpec.from_mapping(execution_data) plan, checkpoints, model_digest, distributed_digest = _derive_plan(model, execution) - hardware = HardwareProfile.from_mapping(case.system_path.stem, system_data, datatype=execution.datatype) + hardware = SystemProfile.from_mapping(case.system_path.stem, system_data, datatype=execution.datatype) if not evidence_revision: evidence_revision = hardware.evidence_revision elif evidence_revision != hardware.evidence_revision: diff --git a/src/blueprinting/synthesizer/experiments/regression.py b/src/blueprinting/synthesizer/experiments/regression.py index 9680537..7b21510 100644 --- a/src/blueprinting/synthesizer/experiments/regression.py +++ b/src/blueprinting/synthesizer/experiments/regression.py @@ -9,9 +9,10 @@ from pathlib import Path from typing import Any -from ...analysis import HardwareProfile, VidurProfileBaseline +from ...analysis import VidurProfileBaseline +from ...system import SystemProfile +from ...workload import TransformerInferenceExecutionSpec, TransformerModelSpec from ..bindings import InferencePhase -from ..models import TransformerInferenceExecutionSpec, TransformerModelSpec from .calculon import CalculonExperimentReport, discover_seqsel_tab5_cases, run_calculon_experiment from .vidur import VidurExperimentCase, VidurExperimentReport, run_vidur_experiment @@ -278,7 +279,7 @@ def _load_vidur_report( model = TransformerModelSpec(**manifest["blueprinting"]["model"]) execution = TransformerInferenceExecutionSpec(**manifest["blueprinting"]["execution"]) hardware_manifest = manifest["blueprinting"]["hardware"] - hardware = HardwareProfile.from_mapping( + hardware = SystemProfile.from_mapping( hardware_manifest["name"], _read_json(repository_root / hardware_manifest["profile"]), datatype=execution.datatype, diff --git a/src/blueprinting/synthesizer/experiments/vidur.py b/src/blueprinting/synthesizer/experiments/vidur.py index 6de84f7..b5eb672 100644 --- a/src/blueprinting/synthesizer/experiments/vidur.py +++ b/src/blueprinting/synthesizer/experiments/vidur.py @@ -14,21 +14,17 @@ from ...analysis import ( CalibrationMode, - HardwareProfile, InferenceBaseline, InferencePhaseEstimate, estimate_inference_phase, inference_evidence_query_for, ) +from ...system import SystemProfile +from ...workload import TransformerInferenceExecutionSpec, TransformerModelSpec from ..bindings import InferencePhase +from ..frontend import build_transformer_inference_model_ir, inference_synthesis_session_for from ..ir import PortablePlanIR from ..lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from ..models import ( - TransformerInferenceExecutionSpec, - TransformerModelSpec, - build_transformer_inference_model_ir, - inference_synthesis_session_for, -) from ..passes import PassManager, PassPipeline @@ -166,7 +162,7 @@ class VidurExperimentCase: name: str model: TransformerModelSpec execution: TransformerInferenceExecutionSpec - hardware: HardwareProfile + hardware: SystemProfile phase: InferencePhase batch_size: int context_tokens: int @@ -294,7 +290,7 @@ def to_json(self) -> str: def compare_inference_phase_to_vidur( plan: PortablePlanIR, estimate: InferencePhaseEstimate, - hardware: HardwareProfile, + hardware: SystemProfile, baseline: InferenceBaseline, ) -> VidurPhaseComparison: """Compare an already-lowered and already-costed phase with Vidur.""" diff --git a/src/blueprinting/synthesizer/frontend/__init__.py b/src/blueprinting/synthesizer/frontend/__init__.py new file mode 100644 index 0000000..9c6313e --- /dev/null +++ b/src/blueprinting/synthesizer/frontend/__init__.py @@ -0,0 +1,14 @@ +"""Adapters from workload contracts into canonical synthesis state.""" + +from .transformer import build_transformer_model_ir, synthesis_session_for +from .transformer_inference import ( + build_transformer_inference_model_ir, + inference_synthesis_session_for, +) + +__all__ = [ + "build_transformer_inference_model_ir", + "build_transformer_model_ir", + "inference_synthesis_session_for", + "synthesis_session_for", +] diff --git a/src/blueprinting/synthesizer/frontend/transformer.py b/src/blueprinting/synthesizer/frontend/transformer.py new file mode 100644 index 0000000..0467578 --- /dev/null +++ b/src/blueprinting/synthesizer/frontend/transformer.py @@ -0,0 +1,84 @@ +"""Transformer training adapters for formal synthesis. + +This module is the explicit dependency seam between target-neutral workload +contracts and Blueprinting's canonical representation/binding machinery. +""" + +from __future__ import annotations + +from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec + +from ..axes import BindingAxis +from ..bindings import BindingSet, StrategyBinding, WorkloadBinding, WorkloadMode +from ..expr import Symbol +from ..frozen import FrozenDict +from ..ids import Lineage, NodeId, ValueId +from ..ir import ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole +from ..session import SynthesisSession + +_SUPPORTED_DATATYPES = frozenset({"float8", "float16", "bfloat16", "float32"}) + + +def build_transformer_model_ir(model: TransformerModelSpec, *, datatype: str = "float16") -> ModelIR: + """Import a model as one semantic operation before structural lowering.""" + + if datatype not in _SUPPORTED_DATATYPES: + raise ValueError(f"unsupported datatype: {datatype!r}") + batch = Symbol("microbatch_size", BindingAxis.WORKLOAD, positive=True) + sequence = Symbol("sequence_length", BindingAxis.WORKLOAD, positive=True) + tensor_type = TensorType((batch, sequence, model.hidden_size), datatype) + input_id = ValueId.derive("transformer", model, "hidden-input") + output_id = ValueId.derive("transformer", model, "hidden-output") + operation_id = NodeId.derive("transformer", model, "decoder-training") + return ModelIR( + name=model.name, + values=( + ModelValue(input_id, tensor_type, ValueRole.INPUT, Lineage.root("transformer-import"), "hidden_input"), + ModelValue( + output_id, + tensor_type, + ValueRole.OUTPUT, + Lineage.lowered("transformer-semantic-op", (input_id,)), + "hidden_output", + ), + ), + operations=( + ModelOperation( + operation_id, + OperationName("transformer", "decoder_training"), + (input_id,), + (output_id,), + Lineage.root("transformer-import"), + attributes=FrozenDict({"model_spec": model}), + ), + ), + inputs=(input_id,), + outputs=(output_id,), + attributes=FrozenDict({"model_family": "decoder-only-transformer"}), + ) + + +def synthesis_session_for( + model: TransformerModelSpec, + execution: TransformerExecutionSpec, +) -> SynthesisSession: + """Create the explicit session consumed by Transformer lowering passes.""" + + workload = WorkloadBinding( + WorkloadMode.TRAINING, + batch_size=execution.microbatch_size, + sequence_length=model.sequence_length, + micro_batches=execution.microbatch_count, + ) + strategy = StrategyBinding( + tensor_parallel=execution.tensor_parallel, + pipeline_parallel=execution.pipeline_parallel, + data_parallel=execution.data_parallel, + recompute_policy=execution.recompute.value, + pipeline_policy=f"1f1b-interleaved-{execution.pipeline_interleaving}", + attributes=FrozenDict({"execution_spec": execution}), + ) + return SynthesisSession( + bindings=BindingSet(workload=workload, strategy=strategy), + features=frozenset({"transformer-training-analysis-v1"}), + ) diff --git a/src/blueprinting/synthesizer/frontend/transformer_inference.py b/src/blueprinting/synthesizer/frontend/transformer_inference.py new file mode 100644 index 0000000..c6f6728 --- /dev/null +++ b/src/blueprinting/synthesizer/frontend/transformer_inference.py @@ -0,0 +1,126 @@ +"""Transformer inference adapters for formal synthesis.""" + +from __future__ import annotations + +from typing import Any + +from blueprinting.workload import TransformerInferenceExecutionSpec, TransformerModelSpec + +from ..axes import BindingAxis +from ..bindings import BindingSet, InferencePhase, StrategyBinding, WorkloadBinding, WorkloadMode +from ..expr import Symbol +from ..frozen import FrozenDict +from ..ids import Lineage, NodeId, ValueId +from ..ir import Effect, EffectKind, ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole +from ..session import SynthesisSession + +_SUPPORTED_DATATYPES = frozenset({"float8", "float16", "bfloat16", "float32"}) + + +def _positive_integer(value: Any, name: str) -> int: + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise ValueError(f"{name} must be a positive integer") + return value + + +def build_transformer_inference_model_ir( + model: TransformerModelSpec, + *, + datatype: str = "float16", +) -> ModelIR: + """Import a phase-neutral decoder inference operation. + + KV cache is an explicit mutable semantic resource. Its concrete context + extent is supplied by a phase workload binding, not embedded in the model. + """ + + if datatype not in _SUPPORTED_DATATYPES: + raise ValueError(f"unsupported datatype: {datatype!r}") + batch = Symbol("batch_size", BindingAxis.WORKLOAD, positive=True) + context = Symbol("sequence_length", BindingAxis.WORKLOAD, positive=True) + query = Symbol("query_tokens", BindingAxis.WORKLOAD, positive=True) + hidden_type = TensorType((batch, query, model.hidden_size), datatype) + cache_type = TensorType((model.block_count, 2, batch, context, model.hidden_size), datatype) + input_id = ValueId.derive("transformer-inference", model, "hidden-input") + cache_id = ValueId.derive("transformer-inference", model, "kv-cache") + output_id = ValueId.derive("transformer-inference", model, "hidden-output") + operation_id = NodeId.derive("transformer-inference", model, "decoder") + return ModelIR( + name=f"{model.name}-inference", + values=( + ModelValue( + input_id, hidden_type, ValueRole.INPUT, Lineage.root("transformer-inference-import"), "hidden_input" + ), + ModelValue( + cache_id, cache_type, ValueRole.KV_CACHE, Lineage.root("transformer-inference-import"), "kv_cache" + ), + ModelValue( + output_id, + hidden_type, + ValueRole.OUTPUT, + Lineage.lowered("transformer-inference-semantic-op", (input_id,)), + "hidden_output", + ), + ), + operations=( + ModelOperation( + operation_id, + OperationName("transformer", "decoder_inference"), + (input_id, cache_id), + (output_id,), + Lineage.root("transformer-inference-import"), + effects=(Effect(EffectKind.STATE, "kv_cache"),), + attributes=FrozenDict({"model_spec": model}), + ), + ), + inputs=(input_id,), + outputs=(output_id,), + attributes=FrozenDict( + { + "model_family": "decoder-only-transformer", + "workload_mode": WorkloadMode.INFERENCE.value, + } + ), + ) + + +def inference_synthesis_session_for( + model: TransformerModelSpec, + execution: TransformerInferenceExecutionSpec, + *, + phase: InferencePhase, + batch_size: int, + context_tokens: int, +) -> SynthesisSession: + """Create an explicit phase binding for static inference specialization.""" + + execution.validate_model(model) + _positive_integer(batch_size, "batch_size") + _positive_integer(context_tokens, "context_tokens") + if not isinstance(phase, InferencePhase): + raise TypeError("phase must be InferencePhase") + query_tokens = context_tokens if phase is InferencePhase.PREFILL else 1 + workload = WorkloadBinding( + WorkloadMode.INFERENCE, + batch_size=batch_size, + sequence_length=context_tokens, + inference_phase=phase, + attributes=FrozenDict( + { + "query_tokens": query_tokens, + "context_tokens": context_tokens, + } + ), + ) + strategy = StrategyBinding( + tensor_parallel=execution.tensor_parallel, + pipeline_parallel=execution.pipeline_parallel, + data_parallel=execution.replicas, + recompute_policy="none", + pipeline_policy="static-inference", + attributes=FrozenDict({"inference_execution_spec": execution}), + ) + return SynthesisSession( + bindings=BindingSet(workload=workload, strategy=strategy), + features=frozenset({"transformer-inference-analysis-v1", f"inference-{phase.value}"}), + ) diff --git a/src/blueprinting/synthesizer/lowering/transformer.py b/src/blueprinting/synthesizer/lowering/transformer.py index 94be355..465513a 100644 --- a/src/blueprinting/synthesizer/lowering/transformer.py +++ b/src/blueprinting/synthesizer/lowering/transformer.py @@ -7,6 +7,11 @@ PrimitiveInvocation, derive_transformer_block, ) +from ...workload import ( + TensorParallelCommunication, + TransformerExecutionSpec, + TransformerModelSpec, +) from ..axes import BindingAxis from ..frozen import FrozenDict from ..ids import BufferId, Lineage, NodeId, ValueId @@ -40,11 +45,6 @@ ValueRole, WorkloadFacts, ) -from ..models.transformer import ( - TensorParallelCommunication, - TransformerExecutionSpec, - TransformerModelSpec, -) from ..passes import DerivationPass, PassContext, PassContract diff --git a/src/blueprinting/synthesizer/lowering/transformer_inference.py b/src/blueprinting/synthesizer/lowering/transformer_inference.py index 380b608..e852b28 100644 --- a/src/blueprinting/synthesizer/lowering/transformer_inference.py +++ b/src/blueprinting/synthesizer/lowering/transformer_inference.py @@ -8,6 +8,7 @@ derive_transformer_inference_block, ) from ...analysis.transformer_workload import EngineKind +from ...workload import TransformerInferenceExecutionSpec, TransformerModelSpec from ..axes import BindingAxis from ..bindings import InferencePhase, WorkloadMode from ..frozen import FrozenDict @@ -44,8 +45,6 @@ ValueRole, WorkloadFacts, ) -from ..models.transformer import TransformerModelSpec -from ..models.transformer_inference import TransformerInferenceExecutionSpec from ..passes import DerivationPass, PassContext, PassContract diff --git a/src/blueprinting/system/__init__.py b/src/blueprinting/system/__init__.py new file mode 100644 index 0000000..4046080 --- /dev/null +++ b/src/blueprinting/system/__init__.py @@ -0,0 +1,15 @@ +"""Typed chip, memory, interconnect, and aggregate system abstractions.""" + +from .chip import EfficiencyCurve, EfficiencyPoint, MemoryProfile, ProcessorProfile +from .interconnect import NetworkOperationProfile, NetworkProfile +from .profile import SystemProfile + +__all__ = [ + "EfficiencyCurve", + "EfficiencyPoint", + "MemoryProfile", + "NetworkOperationProfile", + "NetworkProfile", + "ProcessorProfile", + "SystemProfile", +] diff --git a/src/blueprinting/system/chip.py b/src/blueprinting/system/chip.py new file mode 100644 index 0000000..7ea9a78 --- /dev/null +++ b/src/blueprinting/system/chip.py @@ -0,0 +1,113 @@ +"""Immutable compute and memory descriptions for one accelerator chip.""" + +from __future__ import annotations + +import math +from dataclasses import dataclass + +from blueprinting.synthesizer.codec import record_type + + +def _positive_rate(value: float, name: str) -> None: + if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value <= 0: + raise ValueError(f"{name} must be a finite positive number") + + +def _non_negative_integer(value: int, name: str) -> None: + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise ValueError(f"{name} must be a non-negative integer") + + +@record_type("compiler.analysis.efficiency_point.v1") +@dataclass(frozen=True) +class EfficiencyPoint: + """Measured or simulated efficiency above one work-size threshold.""" + + threshold: int + efficiency: float + + def __post_init__(self) -> None: + if isinstance(self.threshold, bool) or not isinstance(self.threshold, int) or self.threshold < 0: + raise ValueError("efficiency threshold must be a non-negative integer") + if ( + isinstance(self.efficiency, bool) + or not isinstance(self.efficiency, (int, float)) + or not math.isfinite(self.efficiency) + or not 0 < self.efficiency <= 1 + ): + raise ValueError("efficiency must be finite and in (0, 1]") + + +@record_type("compiler.analysis.efficiency_curve.v1") +@dataclass(frozen=True) +class EfficiencyCurve: + """Piecewise-constant utilization evidence indexed by exact work size.""" + + points: tuple[EfficiencyPoint, ...] + + def __post_init__(self) -> None: + object.__setattr__(self, "points", tuple(self.points)) + if not self.points or any(not isinstance(point, EfficiencyPoint) for point in self.points): + raise ValueError("an efficiency curve requires typed points") + thresholds = tuple(point.threshold for point in self.points) + if thresholds != tuple(sorted(thresholds, reverse=True)) or len(set(thresholds)) != len(thresholds): + raise ValueError("efficiency thresholds must be unique and descending") + if thresholds[-1] != 0: + raise ValueError("efficiency curve must cover a zero threshold") + + def lookup(self, work: int) -> float: + if isinstance(work, bool) or not isinstance(work, int) or work < 0: + raise ValueError("curve lookup work must be a non-negative integer") + for point in self.points: + if work >= point.threshold: + return point.efficiency + raise AssertionError("zero-threshold curve failed to cover work") + + +@record_type("compiler.analysis.processor_profile.v1") +@dataclass(frozen=True) +class ProcessorProfile: + """One chip compute engine and its size-dependent utilization evidence.""" + + peak_operations_per_second: float + efficiency: EfficiencyCurve + + def __post_init__(self) -> None: + _positive_rate(self.peak_operations_per_second, "peak_operations_per_second") + if not isinstance(self.efficiency, EfficiencyCurve): + raise TypeError("efficiency must be EfficiencyCurve") + + def throughput(self, operations: int, *, apply_efficiency: bool = True) -> float: + _non_negative_integer(operations, "operations") + if not isinstance(apply_efficiency, bool): + raise TypeError("apply_efficiency must be bool") + efficiency = self.efficiency.lookup(operations) if apply_efficiency else 1.0 + return self.peak_operations_per_second * efficiency + + +@record_type("compiler.analysis.memory_profile.v1") +@dataclass(frozen=True) +class MemoryProfile: + """One chip-visible memory tier and its transfer-efficiency evidence.""" + + capacity_bytes: int + peak_bytes_per_second: float + efficiency: EfficiencyCurve + + def __post_init__(self) -> None: + if ( + isinstance(self.capacity_bytes, bool) + or not isinstance(self.capacity_bytes, int) + or self.capacity_bytes <= 0 + ): + raise ValueError("capacity_bytes must be a positive integer") + _positive_rate(self.peak_bytes_per_second, "peak_bytes_per_second") + if not isinstance(self.efficiency, EfficiencyCurve): + raise TypeError("efficiency must be EfficiencyCurve") + + def throughput(self, transferred_bytes: int, *, apply_efficiency: bool = True) -> float: + _non_negative_integer(transferred_bytes, "transferred_bytes") + if not isinstance(apply_efficiency, bool): + raise TypeError("apply_efficiency must be bool") + efficiency = self.efficiency.lookup(transferred_bytes) if apply_efficiency else 1.0 + return self.peak_bytes_per_second * efficiency diff --git a/src/blueprinting/system/interconnect.py b/src/blueprinting/system/interconnect.py new file mode 100644 index 0000000..9297a2d --- /dev/null +++ b/src/blueprinting/system/interconnect.py @@ -0,0 +1,103 @@ +"""Immutable interconnect descriptions shared by system analysis providers.""" + +from __future__ import annotations + +import math +from dataclasses import dataclass + +from blueprinting.synthesizer.codec import record_type +from blueprinting.synthesizer.frozen import FrozenDict + + +@record_type("compiler.analysis.network_operation.v1") +@dataclass(frozen=True) +class NetworkOperationProfile: + """Explicit byte-volume rule for one point-to-point or collective operation.""" + + volume_multiplier: float + participant_offset: int + + def __post_init__(self) -> None: + if ( + isinstance(self.volume_multiplier, bool) + or not isinstance(self.volume_multiplier, (int, float)) + or not math.isfinite(self.volume_multiplier) + or self.volume_multiplier <= 0 + ): + raise ValueError("volume_multiplier must be a finite positive number") + if isinstance(self.participant_offset, bool) or not isinstance(self.participant_offset, int): + raise TypeError("participant_offset must be an integer") + + +@record_type("compiler.analysis.network_profile.v1") +@dataclass(frozen=True) +class NetworkProfile: + """One interconnect tier with bandwidth, latency, capacity, and volume rules.""" + + peak_bytes_per_second: float + efficiency: float + latency_seconds: float + participant_capacity: int + operations: FrozenDict + + def __post_init__(self) -> None: + if ( + isinstance(self.peak_bytes_per_second, bool) + or not isinstance(self.peak_bytes_per_second, (int, float)) + or not math.isfinite(self.peak_bytes_per_second) + or self.peak_bytes_per_second <= 0 + ): + raise ValueError("peak_bytes_per_second must be a finite positive number") + if ( + isinstance(self.efficiency, bool) + or not isinstance(self.efficiency, (int, float)) + or not math.isfinite(self.efficiency) + or not 0 < self.efficiency <= 1 + ): + raise ValueError("efficiency must be finite and in (0, 1]") + if ( + isinstance(self.latency_seconds, bool) + or not isinstance(self.latency_seconds, (int, float)) + or not math.isfinite(self.latency_seconds) + or self.latency_seconds < 0 + ): + raise ValueError("latency_seconds must be a finite non-negative number") + if ( + isinstance(self.participant_capacity, bool) + or not isinstance(self.participant_capacity, int) + or self.participant_capacity <= 0 + ): + raise ValueError("participant_capacity must be a positive integer") + operations = FrozenDict(self.operations) + if any(not isinstance(item, NetworkOperationProfile) for item in operations.values()): + raise TypeError("operations must contain NetworkOperationProfile values") + object.__setattr__(self, "operations", operations) + + def transferred_bytes(self, operation: str, message_bytes: int, participants: int) -> float: + profile = self.operations.get(operation) + if not isinstance(profile, NetworkOperationProfile): + raise ValueError(f"network does not define operation {operation!r}") + if isinstance(message_bytes, bool) or not isinstance(message_bytes, int) or message_bytes < 0: + raise ValueError("message_bytes must be a non-negative integer") + if isinstance(participants, bool) or not isinstance(participants, int) or participants < 1: + raise ValueError("participants must be a positive integer") + if participants > self.participant_capacity: + raise ValueError("participants exceed interconnect capacity") + scaled = message_bytes * profile.volume_multiplier + return scaled + scaled / participants * profile.participant_offset + + def time( + self, + operation: str, + message_bytes: int, + participants: int, + *, + apply_efficiency: bool = True, + ) -> float: + if not isinstance(apply_efficiency, bool): + raise TypeError("apply_efficiency must be bool") + transferred_bytes = self.transferred_bytes(operation, message_bytes, participants) + if participants < 2: + return 0.0 + efficiency = self.efficiency if apply_efficiency else 1.0 + return self.latency_seconds + transferred_bytes / (self.peak_bytes_per_second * efficiency) diff --git a/src/blueprinting/system/profile.py b/src/blueprinting/system/profile.py new file mode 100644 index 0000000..ed1ac70 --- /dev/null +++ b/src/blueprinting/system/profile.py @@ -0,0 +1,114 @@ +"""Aggregate chip and interconnect descriptions into one system profile.""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass +from typing import Any + +from blueprinting.synthesizer.codec import content_digest, record_type +from blueprinting.synthesizer.frozen import FrozenDict + +from .chip import EfficiencyCurve, EfficiencyPoint, MemoryProfile, ProcessorProfile +from .interconnect import NetworkOperationProfile, NetworkProfile + + +@record_type("compiler.analysis.hardware_profile.v1") +@dataclass(frozen=True) +class SystemProfile: + """One accelerator system used for analytical evaluation. + + Matrix/vector engines and memory describe chip-local resources; networks + describe ordered interconnect tiers. ``evidence_revision`` identifies the + exact imported system evidence snapshot. + """ + + name: str + datatype: str + matrix: ProcessorProfile + vector: ProcessorProfile + memory: MemoryProfile + processing_mode: str + networks: tuple[NetworkProfile, ...] + evidence_revision: str + + def __post_init__(self) -> None: + if not isinstance(self.name, str) or not self.name: + raise ValueError("system profile name must not be empty") + if not isinstance(self.datatype, str) or not self.datatype: + raise ValueError("system profile datatype must not be empty") + if not isinstance(self.matrix, ProcessorProfile) or not isinstance(self.vector, ProcessorProfile): + raise TypeError("matrix and vector must be ProcessorProfile") + if not isinstance(self.memory, MemoryProfile): + raise TypeError("memory must be MemoryProfile") + if self.processing_mode not in {"roofline", "no_overlap"}: + raise ValueError("processing_mode must be roofline or no_overlap") + networks = tuple(self.networks) + if any(not isinstance(network, NetworkProfile) for network in networks): + raise TypeError("networks must contain NetworkProfile values") + object.__setattr__(self, "networks", networks) + if not isinstance(self.evidence_revision, str) or not self.evidence_revision: + raise ValueError("evidence_revision must not be empty") + + @classmethod + def from_mapping( + cls, + name: str, + data: Mapping[str, Any], + *, + datatype: str, + ) -> SystemProfile: + """Import the retained Calculon-compatible system profile schema.""" + + def processor(section: str) -> ProcessorProfile: + item = data[section][datatype] + curve = EfficiencyCurve( + tuple( + EfficiencyPoint(int(giga_operations * 1e9), efficiency) + for giga_operations, efficiency in item["gflops_efficiency"] + ) + ) + return ProcessorProfile(item["tflops"] * 1e12, curve) + + memory_data = data["mem1"] + memory_curve = EfficiencyCurve( + tuple( + EfficiencyPoint(int(megabytes * 1e6), efficiency) + for megabytes, efficiency in memory_data["MB_efficiency"] + ) + ) + networks = [] + for network in data["networks"]: + operations = { + operation: NetworkOperationProfile(multiplier, 0 if offset is None else offset) + for operation, (multiplier, offset) in network["ops"].items() + } + networks.append( + NetworkProfile( + peak_bytes_per_second=network["bandwidth"] * 1e9, + efficiency=network["efficiency"], + latency_seconds=network["latency"], + participant_capacity=network["size"], + operations=FrozenDict(operations), + ) + ) + revision = content_digest(FrozenDict(dict(data)), f"hardware-profile:{name}:{datatype}") + return cls( + name=name, + datatype=datatype, + matrix=processor("matrix"), + vector=processor("vector"), + memory=MemoryProfile( + int(memory_data["GiB"] * 1024**3), + memory_data["GBps"] * 1e9, + memory_curve, + ), + processing_mode=data["processing_mode"], + networks=tuple(networks), + evidence_revision=revision, + ) + + def processing_time(self, compute_seconds: float, memory_seconds: float) -> float: + if self.processing_mode == "roofline": + return max(compute_seconds, memory_seconds) + return compute_seconds + memory_seconds diff --git a/src/blueprinting/synthesizer/models/__init__.py b/src/blueprinting/workload/__init__.py similarity index 57% rename from src/blueprinting/synthesizer/models/__init__.py rename to src/blueprinting/workload/__init__.py index 7f9d855..35453f1 100644 --- a/src/blueprinting/synthesizer/models/__init__.py +++ b/src/blueprinting/workload/__init__.py @@ -1,18 +1,14 @@ -"""Semantic model frontends for formal plan synthesis.""" +"""Target-neutral workload and logical-mapping contracts.""" from .transformer import ( RecomputePolicy, TensorParallelCommunication, TransformerExecutionSpec, TransformerModelSpec, - build_transformer_model_ir, - synthesis_session_for, ) from .transformer_inference import ( TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, - build_transformer_inference_model_ir, - inference_synthesis_session_for, ) __all__ = [ @@ -22,8 +18,4 @@ "TransformerInferenceExecutionSpec", "TransformerInferenceRequestSpec", "TransformerModelSpec", - "build_transformer_inference_model_ir", - "build_transformer_model_ir", - "synthesis_session_for", - "inference_synthesis_session_for", ] diff --git a/src/blueprinting/synthesizer/models/transformer.py b/src/blueprinting/workload/transformer.py similarity index 69% rename from src/blueprinting/synthesizer/models/transformer.py rename to src/blueprinting/workload/transformer.py index fba39fe..6943ff7 100644 --- a/src/blueprinting/synthesizer/models/transformer.py +++ b/src/blueprinting/workload/transformer.py @@ -1,10 +1,8 @@ -"""Typed decoder-only Transformer frontend. +"""Target-neutral decoder-only Transformer workload contracts. -The frontend deliberately imports *semantic* model and execution facts. It -does not attach a target, a kernel choice, or an estimated duration. The -coarse ``transformer.decoder_training`` operation is decomposed by later -passes, which makes the operation-counting rules inspectable at an IR -boundary instead of hiding them in an end-to-end formula. +These immutable descriptions own model semantics and logical mapping intent. +They do not construct canonical IR, bind a hardware target, or estimate time; +the synthesizer frontend and analysis packages own those responsibilities. """ from __future__ import annotations @@ -14,14 +12,7 @@ from enum import Enum from typing import Any -from ..axes import BindingAxis -from ..bindings import BindingSet, StrategyBinding, WorkloadBinding, WorkloadMode -from ..codec import enum_type, record_type -from ..expr import Symbol -from ..frozen import FrozenDict -from ..ids import Lineage, NodeId, ValueId -from ..ir import ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole -from ..session import SynthesisSession +from blueprinting.synthesizer.codec import enum_type, record_type def _positive_integer(value: Any, name: str) -> int: @@ -200,68 +191,3 @@ def from_mapping(cls, data: Mapping[str, Any]) -> TransformerExecutionSpec: activation_offload=data.get("activations_offload", False), optimizer_offload=data.get("optimizer_offload", False), ) - - -def build_transformer_model_ir(model: TransformerModelSpec, *, datatype: str = "float16") -> ModelIR: - """Import a model as one semantic operation before structural lowering.""" - - if datatype not in {"float16", "bfloat16", "float32", "float8"}: - raise ValueError(f"unsupported datatype: {datatype!r}") - batch = Symbol("microbatch_size", BindingAxis.WORKLOAD, positive=True) - sequence = Symbol("sequence_length", BindingAxis.WORKLOAD, positive=True) - tensor_type = TensorType((batch, sequence, model.hidden_size), datatype) - input_id = ValueId.derive("transformer", model, "hidden-input") - output_id = ValueId.derive("transformer", model, "hidden-output") - operation_id = NodeId.derive("transformer", model, "decoder-training") - return ModelIR( - name=model.name, - values=( - ModelValue(input_id, tensor_type, ValueRole.INPUT, Lineage.root("transformer-import"), "hidden_input"), - ModelValue( - output_id, - tensor_type, - ValueRole.OUTPUT, - Lineage.lowered("transformer-semantic-op", (input_id,)), - "hidden_output", - ), - ), - operations=( - ModelOperation( - operation_id, - OperationName("transformer", "decoder_training"), - (input_id,), - (output_id,), - Lineage.root("transformer-import"), - attributes=FrozenDict({"model_spec": model}), - ), - ), - inputs=(input_id,), - outputs=(output_id,), - attributes=FrozenDict({"model_family": "decoder-only-transformer"}), - ) - - -def synthesis_session_for( - model: TransformerModelSpec, - execution: TransformerExecutionSpec, -) -> SynthesisSession: - """Create the explicit session consumed by Transformer lowering passes.""" - - workload = WorkloadBinding( - WorkloadMode.TRAINING, - batch_size=execution.microbatch_size, - sequence_length=model.sequence_length, - micro_batches=execution.microbatch_count, - ) - strategy = StrategyBinding( - tensor_parallel=execution.tensor_parallel, - pipeline_parallel=execution.pipeline_parallel, - data_parallel=execution.data_parallel, - recompute_policy=execution.recompute.value, - pipeline_policy=f"1f1b-interleaved-{execution.pipeline_interleaving}", - attributes=FrozenDict({"execution_spec": execution}), - ) - return SynthesisSession( - bindings=BindingSet(workload=workload, strategy=strategy), - features=frozenset({"transformer-training-analysis-v1"}), - ) diff --git a/src/blueprinting/synthesizer/models/transformer_inference.py b/src/blueprinting/workload/transformer_inference.py similarity index 51% rename from src/blueprinting/synthesizer/models/transformer_inference.py rename to src/blueprinting/workload/transformer_inference.py index 6a6bee8..9257656 100644 --- a/src/blueprinting/synthesizer/models/transformer_inference.py +++ b/src/blueprinting/workload/transformer_inference.py @@ -1,4 +1,4 @@ -"""Typed frontend facts for decoder-only Transformer inference. +"""Target-neutral workload facts for decoder-only Transformer inference. Inference keeps three concerns separate: @@ -6,9 +6,8 @@ * :class:`TransformerInferenceExecutionSpec` describes a logical mapping; * :class:`TransformerInferenceRequestSpec` describes one request cohort. -The frontend emits a phase-neutral semantic operation. ``PREFILL`` and -``DECODE`` become explicit workload bindings, so the same model snapshot can -be specialized independently for request-level simulation later on. +The contracts describe logical mapping and one request cohort. Canonical IR +construction and phase binding are owned by the synthesizer frontend. """ from __future__ import annotations @@ -17,20 +16,8 @@ from dataclasses import dataclass from typing import Any -from ..axes import BindingAxis -from ..bindings import ( - BindingSet, - InferencePhase, - StrategyBinding, - WorkloadBinding, - WorkloadMode, -) -from ..codec import record_type -from ..expr import Symbol -from ..frozen import FrozenDict -from ..ids import Lineage, NodeId, ValueId -from ..ir import Effect, EffectKind, ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole -from ..session import SynthesisSession +from blueprinting.synthesizer.codec import record_type + from .transformer import TransformerModelSpec _SUPPORTED_DATATYPES = frozenset({"float8", "float16", "bfloat16", "float32"}) @@ -142,106 +129,3 @@ def from_mapping(cls, data: Mapping[str, Any]) -> TransformerInferenceRequestSpe prompt_tokens=data["prompt_tokens"], generated_tokens=data["generated_tokens"], ) - - -def build_transformer_inference_model_ir( - model: TransformerModelSpec, - *, - datatype: str = "float16", -) -> ModelIR: - """Import a phase-neutral decoder inference operation. - - KV cache is an explicit mutable semantic resource. Its concrete context - extent is supplied by a phase workload binding, not embedded in the model. - """ - - if datatype not in _SUPPORTED_DATATYPES: - raise ValueError(f"unsupported datatype: {datatype!r}") - batch = Symbol("batch_size", BindingAxis.WORKLOAD, positive=True) - context = Symbol("sequence_length", BindingAxis.WORKLOAD, positive=True) - query = Symbol("query_tokens", BindingAxis.WORKLOAD, positive=True) - hidden_type = TensorType((batch, query, model.hidden_size), datatype) - cache_type = TensorType((model.block_count, 2, batch, context, model.hidden_size), datatype) - input_id = ValueId.derive("transformer-inference", model, "hidden-input") - cache_id = ValueId.derive("transformer-inference", model, "kv-cache") - output_id = ValueId.derive("transformer-inference", model, "hidden-output") - operation_id = NodeId.derive("transformer-inference", model, "decoder") - return ModelIR( - name=f"{model.name}-inference", - values=( - ModelValue( - input_id, hidden_type, ValueRole.INPUT, Lineage.root("transformer-inference-import"), "hidden_input" - ), - ModelValue( - cache_id, cache_type, ValueRole.KV_CACHE, Lineage.root("transformer-inference-import"), "kv_cache" - ), - ModelValue( - output_id, - hidden_type, - ValueRole.OUTPUT, - Lineage.lowered("transformer-inference-semantic-op", (input_id,)), - "hidden_output", - ), - ), - operations=( - ModelOperation( - operation_id, - OperationName("transformer", "decoder_inference"), - (input_id, cache_id), - (output_id,), - Lineage.root("transformer-inference-import"), - effects=(Effect(EffectKind.STATE, "kv_cache"),), - attributes=FrozenDict({"model_spec": model}), - ), - ), - inputs=(input_id,), - outputs=(output_id,), - attributes=FrozenDict( - { - "model_family": "decoder-only-transformer", - "workload_mode": WorkloadMode.INFERENCE.value, - } - ), - ) - - -def inference_synthesis_session_for( - model: TransformerModelSpec, - execution: TransformerInferenceExecutionSpec, - *, - phase: InferencePhase, - batch_size: int, - context_tokens: int, -) -> SynthesisSession: - """Create an explicit phase binding for static inference specialization.""" - - execution.validate_model(model) - _positive_integer(batch_size, "batch_size") - _positive_integer(context_tokens, "context_tokens") - if not isinstance(phase, InferencePhase): - raise TypeError("phase must be InferencePhase") - query_tokens = context_tokens if phase is InferencePhase.PREFILL else 1 - workload = WorkloadBinding( - WorkloadMode.INFERENCE, - batch_size=batch_size, - sequence_length=context_tokens, - inference_phase=phase, - attributes=FrozenDict( - { - "query_tokens": query_tokens, - "context_tokens": context_tokens, - } - ), - ) - strategy = StrategyBinding( - tensor_parallel=execution.tensor_parallel, - pipeline_parallel=execution.pipeline_parallel, - data_parallel=execution.replicas, - recompute_policy="none", - pipeline_policy="static-inference", - attributes=FrozenDict({"inference_execution_spec": execution}), - ) - return SynthesisSession( - bindings=BindingSet(workload=workload, strategy=strategy), - features=frozenset({"transformer-inference-analysis-v1", f"inference-{phase.value}"}), - ) diff --git a/tests/analysis/test_cost_model_providers.py b/tests/analysis/test_cost_model_providers.py index abd4562..e00bf68 100644 --- a/tests/analysis/test_cost_model_providers.py +++ b/tests/analysis/test_cost_model_providers.py @@ -14,7 +14,6 @@ CostSubject, EstimateMethod, EvidenceProvenance, - HardwareProfile, LatencyUnit, PerformanceDatabase, PerformanceDatabaseProvider, @@ -28,21 +27,24 @@ ) from blueprinting.analysis.cost import InvalidCostEvidenceError from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.synthesizer.frontend import ( + build_transformer_inference_model_ir, + inference_synthesis_session_for, +) from blueprinting.synthesizer.frozen import FrozenDict from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from blueprinting.synthesizer.models import ( +from blueprinting.synthesizer.passes import PassManager, PassPipeline +from blueprinting.system import SystemProfile +from blueprinting.workload import ( TransformerInferenceExecutionSpec, TransformerModelSpec, - build_transformer_inference_model_ir, - inference_synthesis_session_for, ) -from blueprinting.synthesizer.passes import PassManager, PassPipeline ROOT = Path(__file__).resolve().parents[2] -def _hardware(name: str = "fixture-hardware") -> HardwareProfile: - return HardwareProfile.from_mapping( +def _hardware(name: str = "fixture-hardware") -> SystemProfile: + return SystemProfile.from_mapping( name, json.loads((ROOT / "data" / "systems" / "a100_80g.json").read_text(encoding="utf-8")), datatype="float16", diff --git a/tests/analysis/test_package_boundary.py b/tests/analysis/test_package_boundary.py index 6107fd8..4d479b9 100644 --- a/tests/analysis/test_package_boundary.py +++ b/tests/analysis/test_package_boundary.py @@ -1,11 +1,30 @@ from __future__ import annotations +import ast import importlib.util +from pathlib import Path import pytest import blueprinting.analysis as analysis import blueprinting.synthesizer as synthesizer +import blueprinting.synthesizer.frontend as frontend +import blueprinting.system as system +import blueprinting.workload as workload + +PACKAGE_ROOT = Path(__file__).resolve().parents[2] / "src" / "blueprinting" + + +def _absolute_imports(package: str) -> frozenset[str]: + imports = set() + for source in (PACKAGE_ROOT / package).rglob("*.py"): + tree = ast.parse(source.read_text(encoding="utf-8"), filename=str(source)) + for node in ast.walk(tree): + if isinstance(node, ast.Import): + imports.update(alias.name for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module: + imports.add(node.module) + return frozenset(imports) def test_analysis_is_a_top_level_blueprinting_package() -> None: @@ -25,3 +44,27 @@ def test_synthesizer_is_the_only_formal_synthesis_package() -> None: def test_legacy_public_symbols_are_not_reexported() -> None: assert not hasattr(synthesizer, "CompilationSession") assert not hasattr(synthesizer, "CompilerError") + + +def test_workload_and_system_are_top_level_domain_packages() -> None: + assert workload.__name__ == "blueprinting.workload" + assert system.__name__ == "blueprinting.system" + assert importlib.util.find_spec("blueprinting.workload") is not None + assert importlib.util.find_spec("blueprinting.system") is not None + assert importlib.util.find_spec("blueprinting.synthesizer.models") is None + + +def test_domain_ownership_is_not_hidden_by_compatibility_reexports() -> None: + assert hasattr(workload, "TransformerModelSpec") + assert not hasattr(workload, "build_transformer_model_ir") + assert hasattr(frontend, "build_transformer_model_ir") + assert hasattr(system, "SystemProfile") + assert not hasattr(analysis, "SystemProfile") + + +def test_domain_packages_do_not_depend_on_each_other_or_analysis_policy() -> None: + workload_imports = _absolute_imports("workload") + system_imports = _absolute_imports("system") + + assert not any(name.startswith(("blueprinting.analysis", "blueprinting.system")) for name in workload_imports) + assert not any(name.startswith(("blueprinting.analysis", "blueprinting.workload")) for name in system_imports) diff --git a/tests/analysis/test_system_profile.py b/tests/analysis/test_system_profile.py new file mode 100644 index 0000000..077d465 --- /dev/null +++ b/tests/analysis/test_system_profile.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from blueprinting.synthesizer.codec import canonical_dumps, canonical_loads +from blueprinting.system import SystemProfile + +ROOT = Path(__file__).resolve().parents[2] + + +def _profile() -> SystemProfile: + data = json.loads((ROOT / "data" / "systems" / "a100_80g.json").read_text(encoding="utf-8")) + return SystemProfile.from_mapping("a100_80g", data, datatype="float16") + + +def test_system_profile_owns_chip_memory_and_interconnect_contracts() -> None: + profile = _profile() + + assert profile.matrix.peak_operations_per_second == 312e12 + assert profile.memory.capacity_bytes == 80 * 1024**3 + assert profile.networks + assert profile.networks[0].operations + + +def test_analysis_policy_is_explicit_at_the_system_profile_boundary() -> None: + profile = _profile() + operations = 100_000_000 + + peak = profile.matrix.throughput(operations, apply_efficiency=False) + evidence_backed = profile.matrix.throughput(operations, apply_efficiency=True) + + assert peak == profile.matrix.peak_operations_per_second + assert evidence_backed < peak + + with pytest.raises(ValueError, match="operations must be a non-negative integer"): + profile.matrix.throughput(-1, apply_efficiency=False) + + +def test_system_profile_round_trip_preserves_legacy_wire_identity() -> None: + profile = _profile() + payload = canonical_dumps(profile) + restored = canonical_loads(payload) + + assert restored == profile + assert '"$type":"compiler.analysis.hardware_profile.v1"' in payload + + +def test_interconnect_rejects_participant_counts_beyond_its_capacity() -> None: + network = _profile().networks[0] + + with pytest.raises(ValueError, match="exceed interconnect capacity"): + network.time("all_reduce", 1024, network.participant_capacity + 1) + + with pytest.raises(ValueError, match="message_bytes must be a non-negative integer"): + network.time("all_reduce", -1, 1) diff --git a/tests/application/test_analysis_service.py b/tests/application/test_analysis_service.py index 8dc5d1c..9eb636b 100644 --- a/tests/application/test_analysis_service.py +++ b/tests/application/test_analysis_service.py @@ -4,9 +4,10 @@ from blueprinting.analysis import CalibrationMode from blueprinting.application import AnalysisDraft, BlueprintingService, SweepRequest +from blueprinting.synthesizer.frontend import build_transformer_model_ir from blueprinting.synthesizer.frozen import FrozenDict -from blueprinting.synthesizer.models import TransformerModelSpec, build_transformer_model_ir from blueprinting.workbench import default_catalog +from blueprinting.workload import TransformerModelSpec def _draft(**execution_changes) -> AnalysisDraft: diff --git a/tests/synthesizer/test_calculon_calibration.py b/tests/synthesizer/test_calculon_calibration.py index b256b1e..8d7ed5a 100644 --- a/tests/synthesizer/test_calculon_calibration.py +++ b/tests/synthesizer/test_calculon_calibration.py @@ -5,17 +5,14 @@ import pytest -from blueprinting.analysis.cost_model import CalibrationMode, HardwareProfile, estimate_iteration +from blueprinting.analysis.cost_model import CalibrationMode, estimate_iteration from blueprinting.analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase from blueprinting.synthesizer.experiments import discover_seqsel_tab5_cases, run_calculon_experiment +from blueprinting.synthesizer.frontend import build_transformer_model_ir, synthesis_session_for from blueprinting.synthesizer.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass -from blueprinting.synthesizer.models import ( - TransformerExecutionSpec, - TransformerModelSpec, - build_transformer_model_ir, - synthesis_session_for, -) from blueprinting.synthesizer.passes import PassManager, PassPipeline +from blueprinting.system import SystemProfile +from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec ROOT = Path(__file__).resolve().parents[2] @@ -78,7 +75,7 @@ def test_selective_recompute_is_structural_and_linear_gradients_are_derived(): def test_hardware_evidence_is_shared_and_does_not_change_workload(): _, execution, _, result = _derive("gpt3-175B", "full") - hardware = HardwareProfile.from_mapping( + hardware = SystemProfile.from_mapping( "a100_80g", _json(ROOT / "data" / "systems" / "a100_80g.json"), datatype=execution.datatype, diff --git a/tests/synthesizer/test_transformer_inference.py b/tests/synthesizer/test_transformer_inference.py index 08c990f..7597a32 100644 --- a/tests/synthesizer/test_transformer_inference.py +++ b/tests/synthesizer/test_transformer_inference.py @@ -6,7 +6,6 @@ import pytest from blueprinting.analysis import ( - HardwareProfile, InferenceCostProvider, InferenceEvidenceQuery, VidurProfileBaseline, @@ -18,15 +17,18 @@ compare_inference_phase_to_vidur, run_vidur_experiment, ) +from blueprinting.synthesizer.frontend import ( + build_transformer_inference_model_ir, + inference_synthesis_session_for, +) from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from blueprinting.synthesizer.models import ( +from blueprinting.synthesizer.passes import PassManager, PassPipeline +from blueprinting.system import SystemProfile +from blueprinting.workload import ( TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, TransformerModelSpec, - build_transformer_inference_model_ir, - inference_synthesis_session_for, ) -from blueprinting.synthesizer.passes import PassManager, PassPipeline ROOT = Path(__file__).resolve().parents[2] @@ -213,7 +215,7 @@ def test_vidur_adapter_uses_only_exact_shape_matches(tmp_path: Path): assert compute_exact.seconds == pytest.approx(0.00075) _, plan = _derive(InferencePhase.DECODE, 96) - hardware = HardwareProfile.from_mapping( + hardware = SystemProfile.from_mapping( "fixture-hardware", json.loads((ROOT / "data" / "systems" / "a100_80g.json").read_text(encoding="utf-8")), datatype="float16", From 1ad47aea7bf5d88cb125d653deac6832f4b58ffa Mon Sep 17 00:00:00 2001 From: Reiase Date: Sun, 9 Aug 2026 18:49:54 +0800 Subject: [PATCH 5/6] refactor: enforce architecture domain boundaries --- .github/workflows/quality.yml | 44 +++++ AGENTS.md | 13 +- README.md | 54 +++--- data/evidence/aiconfigurator/README.md | 14 ++ .../evidence/aiconfigurator}/a100_sxm.yaml | 0 .../evidence/aiconfigurator}/b200_sxm.yaml | 0 .../data/a100_sxm/nccl/2.27.3/nccl_perf.txt | 0 .../data/a100_sxm/sglang/0.5.8/gemm_perf.txt | 0 .../a100_sxm/sglang/0.5.8/mla_bmm_perf.txt | 0 .../data/a100_sxm/sglang/0.5.8/moe_perf.txt | 0 .../trtllm/1.0.0/context_attention_perf.txt | 0 .../trtllm/1.0.0/context_mla_perf.txt | 0 .../trtllm/1.0.0/custom_allreduce_perf.txt | 0 .../data/a100_sxm/trtllm/1.0.0/gemm_perf.txt | 0 .../1.0.0/generation_attention_perf.txt | 0 .../trtllm/1.0.0/generation_mla_perf.txt | 0 .../a100_sxm/trtllm/1.0.0/mla_bmm_perf.txt | 0 .../data/a100_sxm/trtllm/1.0.0/moe_perf.txt | 0 .../vllm/0.12.0/context_attention_perf.txt | 0 .../a100_sxm/vllm/0.12.0/context_mla_perf.txt | 0 .../vllm/0.12.0/custom_allreduce_perf.txt | 0 .../data/a100_sxm/vllm/0.12.0/gemm_perf.txt | 0 .../vllm/0.12.0/generation_attention_perf.txt | 0 .../vllm/0.12.0/generation_mla_perf.txt | 0 .../data/a100_sxm/vllm/0.12.0/moe_perf.txt | 0 .../data/b200_sxm/nccl/2.27.3/nccl_perf.txt | 0 .../0.5.6.post2/context_attention_perf.txt | 0 .../sglang/0.5.6.post2/context_mla_perf.txt | 0 .../0.5.6.post2/custom_allreduce_perf.txt | 0 .../b200_sxm/sglang/0.5.6.post2/gemm_perf.txt | 0 .../0.5.6.post2/generation_attention_perf.txt | 0 .../0.5.6.post2/generation_mla_perf.txt | 0 .../sglang/0.5.6.post2/mla_bmm_perf.txt | 0 .../b200_sxm/sglang/0.5.6.post2/moe_perf.txt | 0 .../trtllm/1.0.0rc6/computescale_perf.txt | 0 .../1.0.0rc6/context_attention_perf.txt | 0 .../trtllm/1.0.0rc6/context_mla_perf.txt | 0 .../trtllm/1.0.0rc6/custom_allreduce_perf.txt | 0 .../b200_sxm/trtllm/1.0.0rc6/gemm_perf.txt | 0 .../1.0.0rc6/generation_attention_perf.txt | 0 .../trtllm/1.0.0rc6/generation_mla_perf.txt | 0 .../b200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt | 0 .../b200_sxm/trtllm/1.0.0rc6/moe_perf.txt | 0 .../trtllm/1.0.0rc6/scale_matrix_perf.txt | 0 .../trtllm/1.2.0rc5/computescale_perf.txt | 0 .../1.2.0rc5/context_attention_perf.txt | 0 .../trtllm/1.2.0rc5/context_mla_perf.txt | 0 .../trtllm/1.2.0rc5/custom_allreduce_perf.txt | 0 .../b200_sxm/trtllm/1.2.0rc5/gemm_perf.txt | 0 .../1.2.0rc5/generation_attention_perf.txt | 0 .../trtllm/1.2.0rc5/generation_mla_perf.txt | 0 .../b200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt | 0 .../b200_sxm/trtllm/1.2.0rc5/moe_perf.txt | 0 .../trtllm/1.2.0rc5/scale_matrix_perf.txt | 0 .../data/gb200_sxm/nccl/2.23/nccl_perf.txt | 0 .../1.0.0rc6/context_attention_perf.txt | 0 .../trtllm/1.0.0rc6/context_mla_perf.txt | 0 .../trtllm/1.0.0rc6/custom_allreduce_perf.txt | 0 .../gb200_sxm/trtllm/1.0.0rc6/gemm_perf.txt | 0 .../1.0.0rc6/generation_attention_perf.txt | 0 .../trtllm/1.0.0rc6/generation_mla_perf.txt | 0 .../trtllm/1.0.0rc6/mla_bmm_perf.txt | 0 .../gb200_sxm/trtllm/1.0.0rc6/moe_perf.txt | 0 .../1.2.0rc5/context_attention_perf.txt | 0 .../trtllm/1.2.0rc5/context_mla_perf.txt | 0 .../trtllm/1.2.0rc5/custom_allreduce_perf.txt | 0 .../gb200_sxm/trtllm/1.2.0rc5/gemm_perf.txt | 0 .../1.2.0rc5/generation_attention_perf.txt | 0 .../trtllm/1.2.0rc5/generation_mla_perf.txt | 0 .../trtllm/1.2.0rc5/mla_bmm_perf.txt | 0 .../gb200_sxm/trtllm/1.2.0rc5/moe_perf.txt | 0 .../data/h100_sxm/nccl/2.23/nccl_perf.txt | 0 .../data/h100_sxm/nccl/2.27.3/nccl_perf.txt | 0 .../0.5.6.post2/context_attention_perf.txt | 0 .../sglang/0.5.6.post2/context_mla_perf.txt | 0 .../0.5.6.post2/custom_allreduce_perf.txt | 0 .../h100_sxm/sglang/0.5.6.post2/gemm_perf.txt | 0 .../0.5.6.post2/generation_attention_perf.txt | 0 .../0.5.6.post2/generation_mla_perf.txt | 0 .../sglang/0.5.6.post2/mla_bmm_perf.txt | 0 .../h100_sxm/sglang/0.5.6.post2/moe_perf.txt | 0 .../0.5.6.post2/wideep_context_mla_perf.txt | 0 .../0.5.6.post2/wideep_context_mlp_perf.txt | 0 .../0.5.6.post2/wideep_context_moe_perf.txt | 0 .../0.5.6.post2/wideep_deepep_ll_perf.txt | 0 .../0.5.6.post2/wideep_deepep_normal_perf.txt | 0 .../wideep_generation_mla_perf.txt | 0 .../wideep_generation_mlp_perf.txt | 0 .../wideep_generation_moe_perf.txt | 0 .../trtllm/1.0.0rc3/computescale_perf.txt | 0 .../1.0.0rc3/context_attention_perf.txt | 0 .../trtllm/1.0.0rc3/context_mla_perf.txt | 0 .../trtllm/1.0.0rc3/custom_allreduce_perf.txt | 0 .../h100_sxm/trtllm/1.0.0rc3/gemm_perf.txt | 0 .../1.0.0rc3/generation_attention_perf.txt | 0 .../trtllm/1.0.0rc3/generation_mla_perf.txt | 0 .../h100_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt | 0 .../h100_sxm/trtllm/1.0.0rc3/moe_perf.txt | 0 .../trtllm/1.0.0rc3/scale_matrix_perf.txt | 0 .../trtllm/1.2.0rc5/computescale_perf.txt | 0 .../1.2.0rc5/context_attention_perf.txt | 0 .../trtllm/1.2.0rc5/context_mla_perf.txt | 0 .../trtllm/1.2.0rc5/custom_allreduce_perf.txt | 0 .../h100_sxm/trtllm/1.2.0rc5/gemm_perf.txt | 0 .../1.2.0rc5/generation_attention_perf.txt | 0 .../trtllm/1.2.0rc5/generation_mla_perf.txt | 0 .../h100_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt | 0 .../h100_sxm/trtllm/1.2.0rc5/moe_perf.txt | 0 .../trtllm/1.2.0rc5/scale_matrix_perf.txt | 0 .../vllm/0.12.0/context_attention_perf.txt | 0 .../h100_sxm/vllm/0.12.0/context_mla_perf.txt | 0 .../vllm/0.12.0/custom_allreduce_perf.txt | 0 .../data/h100_sxm/vllm/0.12.0/gemm_perf.txt | 0 .../vllm/0.12.0/generation_attention_perf.txt | 0 .../vllm/0.12.0/generation_mla_perf.txt | 0 .../data/h100_sxm/vllm/0.12.0/moe_perf.txt | 0 .../vllm/0.14.0/context_attention_perf.txt | 0 .../h100_sxm/vllm/0.14.0/context_mla_perf.txt | 0 .../vllm/0.14.0/custom_allreduce_perf.txt | 0 .../data/h100_sxm/vllm/0.14.0/gemm_perf.txt | 0 .../vllm/0.14.0/generation_attention_perf.txt | 0 .../vllm/0.14.0/generation_mla_perf.txt | 0 .../data/h100_sxm/vllm/0.14.0/moe_perf.txt | 0 .../data/h200_sxm/nccl/2.23/nccl_perf.txt | 0 .../data/h200_sxm/nccl/2.26.2/nccl_perf.txt | 0 .../0.5.6.post2/context_attention_perf.txt | 0 .../sglang/0.5.6.post2/context_mla_perf.txt | 0 .../0.5.6.post2/custom_allreduce_perf.txt | 0 .../h200_sxm/sglang/0.5.6.post2/gemm_perf.txt | 0 .../0.5.6.post2/generation_attention_perf.txt | 0 .../0.5.6.post2/generation_mla_perf.txt | 0 .../sglang/0.5.6.post2/mla_bmm_perf.txt | 0 .../h200_sxm/sglang/0.5.6.post2/moe_perf.txt | 0 .../0.5.6.post2/wideep_context_mla_perf.txt | 0 .../0.5.6.post2/wideep_context_mlp_perf.txt | 0 .../0.5.6.post2/wideep_context_moe_perf.txt | 0 .../0.5.6.post2/wideep_deepep_ll_perf.txt | 0 .../0.5.6.post2/wideep_deepep_normal_perf.txt | 0 .../wideep_generation_mla_perf.txt | 0 .../wideep_generation_mlp_perf.txt | 0 .../wideep_generation_moe_perf.txt | 0 .../trtllm/1.0.0rc3/computescale_perf.txt | 0 .../1.0.0rc3/context_attention_perf.txt | 0 .../trtllm/1.0.0rc3/context_mla_perf.txt | 0 .../trtllm/1.0.0rc3/custom_allreduce_perf.txt | 0 .../h200_sxm/trtllm/1.0.0rc3/gemm_perf.txt | 0 .../1.0.0rc3/generation_attention_perf.txt | 0 .../trtllm/1.0.0rc3/generation_mla_perf.txt | 0 .../h200_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt | 0 .../h200_sxm/trtllm/1.0.0rc3/moe_perf.txt | 0 .../trtllm/1.0.0rc3/scale_matrix_perf.txt | 0 .../trtllm/1.2.0rc5/computescale_perf.txt | 0 .../1.2.0rc5/context_attention_perf.txt | 0 .../trtllm/1.2.0rc5/context_mla_perf.txt | 0 .../trtllm/1.2.0rc5/custom_allreduce_perf.txt | 0 .../h200_sxm/trtllm/1.2.0rc5/gemm_perf.txt | 0 .../1.2.0rc5/generation_attention_perf.txt | 0 .../trtllm/1.2.0rc5/generation_mla_perf.txt | 0 .../h200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt | 0 .../h200_sxm/trtllm/1.2.0rc5/moe_perf.txt | 0 .../trtllm/1.2.0rc5/scale_matrix_perf.txt | 0 .../vllm/0.12.0/context_attention_perf.txt | 0 .../h200_sxm/vllm/0.12.0/context_mla_perf.txt | 0 .../vllm/0.12.0/custom_allreduce_perf.txt | 0 .../data/h200_sxm/vllm/0.12.0/gemm_perf.txt | 0 .../vllm/0.12.0/generation_attention_perf.txt | 0 .../vllm/0.12.0/generation_mla_perf.txt | 0 .../data/h200_sxm/vllm/0.12.0/moe_perf.txt | 0 .../data/l40s/nccl/2.27.3/nccl_perf.txt | 0 .../0.5.5.post3/context_attention_perf.txt | 0 .../0.5.5.post3/custom_allreduce_perf.txt | 0 .../l40s/sglang/0.5.5.post3/gemm_perf.txt | 0 .../0.5.5.post3/generation_attention_perf.txt | 0 .../l40s/sglang/0.5.5.post3/mla_bmm_perf.txt | 0 .../data/l40s/sglang/0.5.5.post3/moe_perf.txt | 0 .../l40s/trtllm/1.0.0/computescale_perf.txt | 0 .../trtllm/1.0.0/context_attention_perf.txt | 0 .../l40s/trtllm/1.0.0/context_mla_perf.txt | 0 .../trtllm/1.0.0/custom_allreduce_perf.txt | 0 .../data/l40s/trtllm/1.0.0/gemm_perf.txt | 0 .../1.0.0/generation_attention_perf.txt | 0 .../l40s/trtllm/1.0.0/generation_mla_perf.txt | 0 .../data/l40s/trtllm/1.0.0/mla_bmm_perf.txt | 0 .../data/l40s/trtllm/1.0.0/moe_perf.txt | 0 .../l40s/trtllm/1.0.0/scale_matrix_perf.txt | 0 .../evidence/aiconfigurator}/gb200_sxm.yaml | 0 .../evidence/aiconfigurator}/h100_sxm.yaml | 0 .../evidence/aiconfigurator}/h200_sxm.yaml | 0 .../evidence/aiconfigurator}/l40s.yaml | 0 .../aiconfigurator}/support_matrix.csv | 0 .../baseline_regression_contract.json | 28 +-- .../implemented-derivation-path.svg | 30 +-- docs/design/modules.en.md | 44 +++-- docs/design/modules.zh.md | 44 +++-- docs/design/passes/transformer.en.md | 14 +- docs/design/passes/transformer.zh.md | 14 +- docs/experiments/calculon-calibration.en.md | 10 +- docs/experiments/calculon-calibration.zh.md | 10 +- docs/modeling/inference.en.md | 17 +- docs/modeling/inference.zh.md | 17 +- docs/project/status.en.md | 19 +- docs/project/status.zh.md | 19 +- examples/calculon_calibration.py | 2 +- examples/calculon_calibration_result.json | 96 ++++----- pages/LLM_Calc/distexp.py | 2 +- pages/LLM_Calc/overview.py | 2 +- pyproject.toml | 9 +- scripts/check_wheel_contract.py | 44 +++++ src/blueprinting/__init__.py | 105 +--------- src/blueprinting/__main__.py | 5 + src/blueprinting/analysis/__init__.py | 24 +-- .../analysis/cost/aiconfigurator.py | 5 +- src/blueprinting/analysis/cost/database.py | 5 +- src/blueprinting/analysis/cost/importers.py | 5 +- src/blueprinting/analysis/cost/protocol.py | 4 +- src/blueprinting/analysis/cost/roofline.py | 5 +- src/blueprinting/analysis/cost_model.py | 183 ++++++++++++------ src/blueprinting/analysis/inference_cost.py | 97 +++++----- src/blueprinting/analysis/vidur.py | 5 +- src/blueprinting/application/__init__.py | 5 +- src/blueprinting/application/analysis.py | 172 ++++------------ src/blueprinting/application/inference.py | 62 +++--- src/blueprinting/application/reporting.py | 138 +++++++++++++ src/blueprinting/cli/__init__.py | 44 +++++ src/blueprinting/cli/llm.py | 58 ------ src/blueprinting/mapping/__init__.py | 17 ++ src/blueprinting/mapping/network.py | 55 ++++++ src/blueprinting/mapping/transformer.py | 166 ++++++++++++++++ src/blueprinting/schema/__init__.py | 24 +++ .../{synthesizer => schema}/codec.py | 2 +- src/blueprinting/schema/errors.py | 9 + .../{synthesizer => schema}/frozen.py | 7 +- src/blueprinting/synthesizer/__init__.py | 10 - src/blueprinting/synthesizer/axes.py | 2 +- src/blueprinting/synthesizer/bindings.py | 5 +- .../synthesizer/dialects/__init__.py | 1 + .../dialects/transformer/__init__.py | 22 +++ .../dialects/transformer/common.py | 42 ++++ .../dialects/transformer/inference.py} | 34 ++-- .../dialects/transformer/training.py} | 154 +++++++-------- src/blueprinting/synthesizer/errors.py | 4 - src/blueprinting/synthesizer/expr.py | 3 +- .../synthesizer/frontend/transformer.py | 28 +-- .../frontend/transformer_inference.py | 23 ++- src/blueprinting/synthesizer/ids.py | 3 +- src/blueprinting/synthesizer/ir/common.py | 8 +- .../synthesizer/ir/concrete_plan.py | 5 +- .../synthesizer/ir/distributed.py | 5 +- src/blueprinting/synthesizer/ir/machine.py | 5 +- src/blueprinting/synthesizer/ir/model.py | 5 +- .../synthesizer/ir/portable_plan.py | 5 +- .../synthesizer/lowering/transformer.py | 106 +++++----- .../lowering/transformer_inference.py | 70 +++---- src/blueprinting/synthesizer/passes/base.py | 7 +- src/blueprinting/synthesizer/session.py | 5 +- src/blueprinting/system/chip.py | 2 +- src/blueprinting/system/interconnect.py | 4 +- src/blueprinting/system/profile.py | 4 +- .../experiments => validation}/__init__.py | 0 .../experiments => validation}/calculon.py | 62 +++--- .../validation/legacy/__init__.py | 1 + .../legacy}/seqsel_fig1.py | 27 ++- .../legacy}/seqsel_fig7.py | 23 +-- .../legacy}/seqsel_tab5.py | 21 +- .../experiments => validation}/regression.py | 23 ++- .../experiments => validation}/vidur.py | 60 ++++-- src/blueprinting/workbench/catalog.py | 8 +- src/blueprinting/workload/__init__.py | 19 +- src/blueprinting/workload/transformer.py | 124 ++---------- .../workload/transformer_inference.py | 77 ++------ tests/analysis/test_cost_model_providers.py | 29 ++- tests/analysis/test_domain_contracts.py | 59 ++++++ tests/analysis/test_package_boundary.py | 62 ++++-- tests/analysis/test_system_profile.py | 2 +- tests/application/test_analysis_service.py | 30 ++- tests/application/test_cli.py | 28 +++ .../test_inference_analysis_service.py | 22 +++ .../regression/test_baseline_quality_gate.py | 2 +- tests/synthesizer/conftest.py | 2 +- tests/synthesizer/test_bindings.py | 5 +- tests/synthesizer/test_canonical_ir.py | 4 +- tests/synthesizer/test_pass_manager.py | 2 +- .../synthesizer/test_transformer_inference.py | 52 +++-- tests/synthesizer/test_verifiers.py | 3 +- .../legacy/test_seqsel_fig1.py} | 4 +- .../legacy/test_seqsel_fig7.py} | 4 +- .../legacy/test_seqsel_tab5.py} | 4 +- .../test_calculon.py} | 70 +++++-- tests/workbench/test_catalog.py | 12 ++ 289 files changed, 1880 insertions(+), 1271 deletions(-) create mode 100644 data/evidence/aiconfigurator/README.md rename {src/blueprinting/systems => data/evidence/aiconfigurator}/a100_sxm.yaml (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/b200_sxm.yaml (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/nccl/2.27.3/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/sglang/0.5.8/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/sglang/0.5.8/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/sglang/0.5.8/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/trtllm/1.0.0/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/vllm/0.12.0/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/vllm/0.12.0/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/vllm/0.12.0/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/vllm/0.12.0/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/vllm/0.12.0/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/vllm/0.12.0/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/a100_sxm/vllm/0.12.0/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/nccl/2.27.3/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/sglang/0.5.6.post2/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/computescale_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.0.0rc6/scale_matrix_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/computescale_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/b200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/nccl/2.23/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.0.0rc6/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/gb200_sxm/trtllm/1.2.0rc5/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/nccl/2.23/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/nccl/2.27.3/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/computescale_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/computescale_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.12.0/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.12.0/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.12.0/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.12.0/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.12.0/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.12.0/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.12.0/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.14.0/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.14.0/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.14.0/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.14.0/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.14.0/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.14.0/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h100_sxm/vllm/0.14.0/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/nccl/2.23/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/nccl/2.26.2/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/computescale_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/computescale_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/vllm/0.12.0/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/vllm/0.12.0/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/vllm/0.12.0/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/vllm/0.12.0/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/vllm/0.12.0/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/vllm/0.12.0/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/h200_sxm/vllm/0.12.0/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/nccl/2.27.3/nccl_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/sglang/0.5.5.post3/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/sglang/0.5.5.post3/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/sglang/0.5.5.post3/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/sglang/0.5.5.post3/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/sglang/0.5.5.post3/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/sglang/0.5.5.post3/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/computescale_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/context_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/context_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/custom_allreduce_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/gemm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/generation_attention_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/generation_mla_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/mla_bmm_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/moe_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/data/l40s/trtllm/1.0.0/scale_matrix_perf.txt (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/gb200_sxm.yaml (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/h100_sxm.yaml (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/h200_sxm.yaml (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/l40s.yaml (100%) rename {src/blueprinting/systems => data/evidence/aiconfigurator}/support_matrix.csv (100%) create mode 100644 scripts/check_wheel_contract.py mode change 100755 => 100644 src/blueprinting/__init__.py create mode 100644 src/blueprinting/__main__.py create mode 100644 src/blueprinting/application/reporting.py mode change 100755 => 100644 src/blueprinting/cli/__init__.py delete mode 100755 src/blueprinting/cli/llm.py create mode 100644 src/blueprinting/mapping/__init__.py create mode 100644 src/blueprinting/mapping/network.py create mode 100644 src/blueprinting/mapping/transformer.py create mode 100644 src/blueprinting/schema/__init__.py rename src/blueprinting/{synthesizer => schema}/codec.py (99%) create mode 100644 src/blueprinting/schema/errors.py rename src/blueprinting/{synthesizer => schema}/frozen.py (91%) create mode 100644 src/blueprinting/synthesizer/dialects/__init__.py create mode 100644 src/blueprinting/synthesizer/dialects/transformer/__init__.py create mode 100644 src/blueprinting/synthesizer/dialects/transformer/common.py rename src/blueprinting/{analysis/transformer_inference.py => synthesizer/dialects/transformer/inference.py} (92%) rename src/blueprinting/{analysis/transformer_workload.py => synthesizer/dialects/transformer/training.py} (89%) rename src/blueprinting/{synthesizer/experiments => validation}/__init__.py (100%) rename src/blueprinting/{synthesizer/experiments => validation}/calculon.py (89%) create mode 100644 src/blueprinting/validation/legacy/__init__.py rename src/blueprinting/{validations/cases => validation/legacy}/seqsel_fig1.py (80%) mode change 100755 => 100644 rename src/blueprinting/{validations/cases => validation/legacy}/seqsel_fig7.py (82%) mode change 100755 => 100644 rename src/blueprinting/{validations/cases => validation/legacy}/seqsel_tab5.py (80%) mode change 100755 => 100644 rename src/blueprinting/{synthesizer/experiments => validation}/regression.py (96%) rename src/blueprinting/{synthesizer/experiments => validation}/vidur.py (89%) create mode 100644 tests/analysis/test_domain_contracts.py create mode 100644 tests/application/test_cli.py rename tests/{validations/cases/seqsel_fig1_test.py => validation/legacy/test_seqsel_fig1.py} (72%) mode change 100755 => 100644 rename tests/{validations/cases/seqsel_fig7_test.py => validation/legacy/test_seqsel_fig7.py} (66%) mode change 100755 => 100644 rename tests/{validations/cases/seqsel_tab5_test.py => validation/legacy/test_seqsel_tab5.py} (66%) mode change 100755 => 100644 rename tests/{synthesizer/test_calculon_calibration.py => validation/test_calculon.py} (58%) create mode 100644 tests/workbench/test_catalog.py diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 8b13269..3e2012d 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -58,5 +58,49 @@ jobs: - name: Install locked dependencies run: uv sync --locked --extra legacy-ui + - name: Lint supported architecture boundary + run: >- + uv run ruff check + src/blueprinting/__init__.py + src/blueprinting/__main__.py + src/blueprinting/cli + src/blueprinting/schema + src/blueprinting/workload + src/blueprinting/mapping + src/blueprinting/system + src/blueprinting/synthesizer + src/blueprinting/analysis + src/blueprinting/validation + src/blueprinting/application + src/blueprinting/workbench + tests/synthesizer + tests/analysis + tests/application + tests/regression + tests/workbench + - name: Run tests run: uv run pytest + + package-contract: + name: Base wheel contract + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + enable-cache: true + + - name: Install Python + run: uv python install 3.12 + + - name: Build wheel + run: uv build --wheel + + - name: Verify wheel contents and size + run: uv run python scripts/check_wheel_contract.py dist/*.whl diff --git a/AGENTS.md b/AGENTS.md index 789adf9..a6d21d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,9 +142,12 @@ handling。若 LPU 的 issue cycle/slot 具有 correctness 含义,它在 targe ## 9. 当前实现边界 -Target-neutral workload contract 位于 `src/blueprinting/workload/`,芯片、memory、interconnect 与 system profile -位于 `src/blueprinting/system/`;canonical 表示与形式化推导机制位于 `src/blueprinting/synthesizer/`,分析与证据评估 -位于同级 `src/blueprinting/analysis/`。Synthesizer 表示 formal plan synthesis 的实现边界,不是产品身份、RTL 综合器或独立 Compiler 组件。已经实现: +无领域依赖的 codec 与 immutable schema primitive 位于 `src/blueprinting/schema/`;target-neutral workload contract +位于 `src/blueprinting/workload/`,逻辑策略与显式 deployment mapping 位于 `src/blueprinting/mapping/`,芯片、memory、 +interconnect 与 system profile 位于 `src/blueprinting/system/`;canonical 表示与形式化推导机制位于 +`src/blueprinting/synthesizer/`,分析与证据评估位于同级 `src/blueprinting/analysis/`,外部 baseline 与回归 gate +位于 `src/blueprinting/validation/`。Synthesizer 表示 formal plan synthesis 的实现边界,不是产品身份、RTL 综合器 +或独立 Compiler 组件。已经实现: - 五层 canonical IR 的 immutable schema、serialization 和 structural verifier;其中后两层仍是 experimental contract; - stable ID、lineage、typed scalar expression、binding/session; @@ -165,8 +168,8 @@ Target-neutral workload contract 位于 `src/blueprinting/workload/`,芯片、 ## 10. 代码与仓库规则 -- Workload semantic/request/mapping contract 进入 `src/blueprinting/workload/`;芯片、memory、interconnect 与 system - contract 进入 `src/blueprinting/system/`;workload-to-IR adapter、canonical 表示与推导进入 +- 无领域依赖的 canonical codec、frozen value 与 schema error 进入 `src/blueprinting/schema/`;workload semantic/request contract 进入 `src/blueprinting/workload/`;逻辑 strategy 与 deployment mapping 进入 + `src/blueprinting/mapping/`;芯片、memory、interconnect 与 system contract 进入 `src/blueprinting/system/`;workload-to-IR adapter、canonical 表示与推导进入 `src/blueprinting/synthesizer/`;cost/evidence analysis 进入 `src/blueprinting/analysis/`。不得新建平行表示栈。 - `SystemProfile` 是当前有限的 compute/memory/network evidence-bearing adapter,不得被描述成已经实现的完整 `ArchitectureBlueprint`;`src/blueprinting/types/system/` 只服务 legacy calculator,新代码不得依赖它。 diff --git a/README.md b/README.md index 7417a68..94a02bf 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ provides: - immutable schemas and structural verifiers for the current five-layer IR backbone; only the first three layers have a production derivation slice, while `ConcretePlanIR` and `MachineIR` remain experimental contracts; - stable IDs, lineage, schema-versioned serialization, content digests, and typed binding sessions; - declarative transformation contracts with analysis invalidation and derivation checkpoints; -- a typed decoder-only Transformer training frontend; -- `ModelIR -> DistributedTaskIR -> PortablePlanIR` staged derivation with explicit TP collectives, recomputation, workload, - and buffer facts; -- peak-only and hardware-evidence cost views; -- a reproducible Calculon/SeqSel calibration experiment. +- typed decoder-only Transformer training and static prefill/decode frontends; +- `ModelIR -> DistributedTaskIR -> PortablePlanIR` staged derivation with explicit TP collectives, recomputation, + KV state, workload, and buffer facts; +- peak-only, hardware-evidence, database, and explicit roofline fallback cost views; +- reproducible Calculon/SeqSel and Vidur comparison gates that remain downstream of derivation. First-class architecture blueprints, hardware design variables, concrete resource simulation, network/hardware simulator adapters, bottleneck/sensitivity reports, energy/area/cost models, and Pareto search are planned product @@ -86,11 +86,13 @@ from blueprinting.synthesizer.lowering import ( ) from blueprinting.synthesizer.frontend import build_transformer_model_ir, synthesis_session_for from blueprinting.synthesizer.passes import PassManager, PassPipeline -from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec +from blueprinting.mapping import TransformerTrainingMappingSpec +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec model = TransformerModelSpec.from_mapping("gpt3-175B", model_config) -execution = TransformerExecutionSpec.from_mapping(execution_config) -source = build_transformer_model_ir(model) +workload = TransformerTrainingWorkloadSpec.from_mapping(execution_config) +mapping = TransformerTrainingMappingSpec.from_mapping(execution_config) +source = build_transformer_model_ir(model, datatype=workload.datatype) result = PassManager().run( PassPipeline.of( @@ -98,7 +100,7 @@ result = PassManager().run( PlanTransformerTrainingPass(), ), source, - session=synthesis_session_for(model, execution), + session=synthesis_session_for(model, workload, mapping), ) portable_plan = result.ir @@ -106,8 +108,8 @@ for checkpoint in result.checkpoints: print(checkpoint.pass_name, checkpoint.ir.digest) ``` -The mapping inputs use the model and execution schemas in `data/`. Invalid topology such as -`world_size != tp * pp * dp` is rejected at the typed frontend boundary. +The adapter reads the retained model/execution JSON presets in `data/`, then separates workload facts from the +logical mapping. Invalid topology such as `world_size != tp * pp * dp` is rejected at the typed frontend boundary. ## Reproduce the Calculon calibration @@ -148,26 +150,24 @@ Calculon remains an adjacent calibration utility and does not participate in the ## Repository layout ```text -src/blueprinting/synthesizer/ -├── ir/ # five canonical IR contracts -├── frontend/ # workload-to-IR/session adapters -├── lowering/ # staged derivation passes -├── experiments/ # reproducible validation experiments -├── passes/ # transformation contracts and manager -└── session.py # explicit bindings and typed derivation context - -src/blueprinting/workload/ # target-neutral workload and mapping contracts -src/blueprinting/system/ # chip, memory, interconnect, and system profiles -src/blueprinting/analysis/ # exact workload and evidence-backed cost analyses -src/blueprinting/application/ # framework-neutral analysis service +src/blueprinting/schema/ # dependency-free codec and immutable schema primitives +src/blueprinting/workload/ # target-neutral model and scenario facts +src/blueprinting/mapping/ # logical strategies and explicit deployment mappings +src/blueprinting/system/ # chip, memory, interconnect, and system profiles +src/blueprinting/synthesizer/ # canonical IR, exact-work dialects, and verified derivation +src/blueprinting/analysis/ # evidence protocols, cost resolution, and projections +src/blueprinting/application/ # framework-neutral analysis services and reports +src/blueprinting/validation/ # external baselines and strict regression gates src/blueprinting/workbench/ # NiceGUI workbench and legacy presentation adapters -tests/synthesizer/ # current formal-representation and calibration tests -docs/ # bilingual MkDocs design, reference, experiment, and project documentation +data/evidence/ # optional external evidence, excluded from the base package +tests/ # domain, derivation, application, and regression contracts +docs/ # bilingual MkDocs design, experiment, and project documentation ``` -`workload` and `system` own the two domain inputs. `synthesizer` connects them through canonical representations -and verified derivation mechanics; `analysis` evaluates the resulting facts without owning either domain model. +`workload`, `mapping`, and `system` own separate input concerns. `synthesizer` derives canonical plans from workload +and logical-strategy contracts without reading a physical system; `analysis` later evaluates those plans against an +explicit system, deployment mapping, and evidence snapshot. External oracles remain downstream in `validation`. ## Development diff --git a/data/evidence/aiconfigurator/README.md b/data/evidence/aiconfigurator/README.md new file mode 100644 index 0000000..804e0b3 --- /dev/null +++ b/data/evidence/aiconfigurator/README.md @@ -0,0 +1,14 @@ +# AIConfigurator evidence bundle + +This directory is an external performance-data snapshot, not a Python package +and not a Blueprinting system contract. Consumers must load individual files +through an explicit evidence importer and record the selected file digest, +runtime/backend revision, hardware identity, and measurement protocol. + +The imported snapshot does not currently include one repository-level source +revision manifest. Treat it as exploratory evidence; do not use it as a frozen +regression oracle until provenance and license metadata are pinned for the +whole bundle. The files retain their original SPDX headers where supplied. + +The directory is intentionally excluded from default wheel and source +distribution artifacts because it is large and optional. diff --git a/src/blueprinting/systems/a100_sxm.yaml b/data/evidence/aiconfigurator/a100_sxm.yaml similarity index 100% rename from src/blueprinting/systems/a100_sxm.yaml rename to data/evidence/aiconfigurator/a100_sxm.yaml diff --git a/src/blueprinting/systems/b200_sxm.yaml b/data/evidence/aiconfigurator/b200_sxm.yaml similarity index 100% rename from src/blueprinting/systems/b200_sxm.yaml rename to data/evidence/aiconfigurator/b200_sxm.yaml diff --git a/src/blueprinting/systems/data/a100_sxm/nccl/2.27.3/nccl_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/nccl/2.27.3/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/nccl/2.27.3/nccl_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/nccl/2.27.3/nccl_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/sglang/0.5.8/gemm_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/sglang/0.5.8/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/sglang/0.5.8/gemm_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/sglang/0.5.8/gemm_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/sglang/0.5.8/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/sglang/0.5.8/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/sglang/0.5.8/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/sglang/0.5.8/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/sglang/0.5.8/moe_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/sglang/0.5.8/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/sglang/0.5.8/moe_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/sglang/0.5.8/moe_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/context_attention_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/context_mla_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/gemm_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/gemm_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/gemm_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/moe_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/trtllm/1.0.0/moe_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/trtllm/1.0.0/moe_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/context_attention_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/context_mla_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/gemm_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/gemm_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/gemm_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/moe_perf.txt b/data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/a100_sxm/vllm/0.12.0/moe_perf.txt rename to data/evidence/aiconfigurator/data/a100_sxm/vllm/0.12.0/moe_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/nccl/2.27.3/nccl_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/nccl/2.27.3/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/nccl/2.27.3/nccl_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/nccl/2.27.3/nccl_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/context_attention_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/context_mla_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/gemm_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/gemm_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/gemm_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/moe_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/sglang/0.5.6.post2/moe_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/sglang/0.5.6.post2/moe_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/computescale_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/computescale_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/computescale_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/computescale_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/gemm_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/gemm_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/gemm_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/moe_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/moe_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/moe_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/scale_matrix_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/scale_matrix_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.0.0rc6/scale_matrix_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.0.0rc6/scale_matrix_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/computescale_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/computescale_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/computescale_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/computescale_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/gemm_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/gemm_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/gemm_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/moe_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/moe_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/moe_perf.txt diff --git a/src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt b/data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt similarity index 100% rename from src/blueprinting/systems/data/b200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt rename to data/evidence/aiconfigurator/data/b200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/nccl/2.23/nccl_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/nccl/2.23/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/nccl/2.23/nccl_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/nccl/2.23/nccl_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/gemm_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/gemm_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/gemm_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/moe_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.0.0rc6/moe_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.0.0rc6/moe_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/gemm_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/gemm_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/gemm_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/moe_perf.txt b/data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/gb200_sxm/trtllm/1.2.0rc5/moe_perf.txt rename to data/evidence/aiconfigurator/data/gb200_sxm/trtllm/1.2.0rc5/moe_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/nccl/2.23/nccl_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/nccl/2.23/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/nccl/2.23/nccl_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/nccl/2.23/nccl_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/nccl/2.27.3/nccl_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/nccl/2.27.3/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/nccl/2.27.3/nccl_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/nccl/2.27.3/nccl_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/gemm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/moe_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/moe_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/moe_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/computescale_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/computescale_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/computescale_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/computescale_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/gemm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/moe_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/moe_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/moe_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/computescale_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/computescale_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/computescale_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/computescale_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/gemm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/moe_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/moe_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/moe_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/gemm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/moe_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.12.0/moe_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.12.0/moe_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/gemm_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/moe_perf.txt b/data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h100_sxm/vllm/0.14.0/moe_perf.txt rename to data/evidence/aiconfigurator/data/h100_sxm/vllm/0.14.0/moe_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/nccl/2.23/nccl_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/nccl/2.23/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/nccl/2.23/nccl_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/nccl/2.23/nccl_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/nccl/2.26.2/nccl_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/nccl/2.26.2/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/nccl/2.26.2/nccl_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/nccl/2.26.2/nccl_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/gemm_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/moe_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/moe_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/moe_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_context_mlp_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_context_moe_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_ll_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_deepep_normal_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_mlp_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/sglang/0.5.6.post2/wideep_generation_moe_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/computescale_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/computescale_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/computescale_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/computescale_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/gemm_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/moe_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/moe_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/moe_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.0.0rc3/scale_matrix_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/computescale_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/computescale_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/computescale_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/computescale_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/gemm_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/moe_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/moe_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/moe_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/trtllm/1.2.0rc5/scale_matrix_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/context_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/context_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/gemm_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/gemm_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/gemm_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/moe_perf.txt b/data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/h200_sxm/vllm/0.12.0/moe_perf.txt rename to data/evidence/aiconfigurator/data/h200_sxm/vllm/0.12.0/moe_perf.txt diff --git a/src/blueprinting/systems/data/l40s/nccl/2.27.3/nccl_perf.txt b/data/evidence/aiconfigurator/data/l40s/nccl/2.27.3/nccl_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/nccl/2.27.3/nccl_perf.txt rename to data/evidence/aiconfigurator/data/l40s/nccl/2.27.3/nccl_perf.txt diff --git a/src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/context_attention_perf.txt b/data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/gemm_perf.txt b/data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/gemm_perf.txt rename to data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/gemm_perf.txt diff --git a/src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/moe_perf.txt b/data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/sglang/0.5.5.post3/moe_perf.txt rename to data/evidence/aiconfigurator/data/l40s/sglang/0.5.5.post3/moe_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/computescale_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/computescale_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/computescale_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/computescale_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/context_attention_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/context_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/context_attention_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/context_attention_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/context_mla_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/context_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/context_mla_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/context_mla_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/custom_allreduce_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/custom_allreduce_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/custom_allreduce_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/custom_allreduce_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/gemm_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/gemm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/gemm_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/gemm_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/generation_attention_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/generation_attention_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/generation_attention_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/generation_attention_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/generation_mla_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/generation_mla_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/generation_mla_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/generation_mla_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/mla_bmm_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/mla_bmm_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/mla_bmm_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/mla_bmm_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/moe_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/moe_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/moe_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/moe_perf.txt diff --git a/src/blueprinting/systems/data/l40s/trtllm/1.0.0/scale_matrix_perf.txt b/data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/scale_matrix_perf.txt similarity index 100% rename from src/blueprinting/systems/data/l40s/trtllm/1.0.0/scale_matrix_perf.txt rename to data/evidence/aiconfigurator/data/l40s/trtllm/1.0.0/scale_matrix_perf.txt diff --git a/src/blueprinting/systems/gb200_sxm.yaml b/data/evidence/aiconfigurator/gb200_sxm.yaml similarity index 100% rename from src/blueprinting/systems/gb200_sxm.yaml rename to data/evidence/aiconfigurator/gb200_sxm.yaml diff --git a/src/blueprinting/systems/h100_sxm.yaml b/data/evidence/aiconfigurator/h100_sxm.yaml similarity index 100% rename from src/blueprinting/systems/h100_sxm.yaml rename to data/evidence/aiconfigurator/h100_sxm.yaml diff --git a/src/blueprinting/systems/h200_sxm.yaml b/data/evidence/aiconfigurator/h200_sxm.yaml similarity index 100% rename from src/blueprinting/systems/h200_sxm.yaml rename to data/evidence/aiconfigurator/h200_sxm.yaml diff --git a/src/blueprinting/systems/l40s.yaml b/data/evidence/aiconfigurator/l40s.yaml similarity index 100% rename from src/blueprinting/systems/l40s.yaml rename to data/evidence/aiconfigurator/l40s.yaml diff --git a/src/blueprinting/systems/support_matrix.csv b/data/evidence/aiconfigurator/support_matrix.csv similarity index 100% rename from src/blueprinting/systems/support_matrix.csv rename to data/evidence/aiconfigurator/support_matrix.csv diff --git a/data/validation/baseline_regression_contract.json b/data/validation/baseline_regression_contract.json index 056cffa..1d2e08a 100644 --- a/data/validation/baseline_regression_contract.json +++ b/data/validation/baseline_regression_contract.json @@ -18,14 +18,14 @@ "paper_mean_absolute_error_percent": 3.654361437637248, "paper_max_absolute_error_percent": 8.874452316395821, "portable_digests": { - "seqsel-tab5/megatron-22B/full": "9ce66dd1c115a1efbb0cad1cc3921c3f28ffc58f", - "seqsel-tab5/megatron-22B/seqsel": "873c82dde8f06f97a619855bf7fa74d5730c7d55", - "seqsel-tab5/gpt3-175B/full": "2c9566d969073f2983b2ca3e35d840c986063058", - "seqsel-tab5/gpt3-175B/seqsel": "d3f1826b4e0b24ee91fc5920c95c1dd3b3ca6782", - "seqsel-tab5/turing-530B/full": "1eca776c1c27807964278cf5e768193a6f06c4cc", - "seqsel-tab5/turing-530B/seqsel": "8095358ace4a3a62466c13b0895e03545c6f5621", - "seqsel-tab5/megatron-1T/full": "5ffc23392d5a374f442ef2f448366e3d9f6118ce", - "seqsel-tab5/megatron-1T/seqsel": "377375178daedbd9655bdccb618eab11ba8b68f3" + "seqsel-tab5/megatron-22B/full": "a702e06f09e0e7ed49b1d06371fdc5f01df40ead", + "seqsel-tab5/megatron-22B/seqsel": "d8c1a76517458d5bd92af95c125225a00c1b6a61", + "seqsel-tab5/gpt3-175B/full": "b9ed7a9c81a7cbd02066e000478d3488e31c3321", + "seqsel-tab5/gpt3-175B/seqsel": "bf7be9b50d482232aaee649c506f4afca5c9dfcb", + "seqsel-tab5/turing-530B/full": "3988fe962f1ccecca2cee3a08b98da5e177f8c65", + "seqsel-tab5/turing-530B/seqsel": "2e9047b02f855c3527c07cfae116bbc41631eaea", + "seqsel-tab5/megatron-1T/full": "d4a11ba89f39f36f002ee9c0179bbc02dc62a542", + "seqsel-tab5/megatron-1T/seqsel": "7549a08665566ef4187e9a37ca50d860308c8800" } } }, @@ -54,8 +54,8 @@ "system_evidence_component_max_absolute_error_percent": 99.50036630036631, "cases": { "phi2-a100-tp1/decode/b1-c33": { - "distributed_digest": "1d6a4598cff8907554b3c246d3993143d17b3d5a", - "portable_digest": "5bbe78b2017f21215b6e66904ba95fa1ccda5e49", + "distributed_digest": "3aaaddf8d05e49da9d675acc2b1dc0e252c4f1fc", + "portable_digest": "d96d85895fa4f19655b2dc0592b5dc402e1bfb0d", "component_coverage": 0.75, "baseline_comparable_block_seconds": 0.000147, "system_evidence_comparable_block_seconds": 0.00010824697435897437, @@ -64,8 +64,8 @@ "system_evidence_component_max_absolute_error_percent": 99.50036630036631 }, "phi2-a100-tp1/decode/b1-c129": { - "distributed_digest": "4ea3575ed2cd51d69d789e6addc7197a45d32efb", - "portable_digest": "74335a2d5a3ed2f9da31b334a93519c559a0c6a3", + "distributed_digest": "727acb5e4a18f54570180de6dfe7c6f28b634b92", + "portable_digest": "ce6e6d1fd1233134300445391f4cd33a4ae7dff3", "component_coverage": 0.75, "baseline_comparable_block_seconds": 0.00015000000000000001, "system_evidence_comparable_block_seconds": 0.00010879564102564103, @@ -74,8 +74,8 @@ "system_evidence_component_max_absolute_error_percent": 99.50036630036631 }, "phi2-a100-tp1/prefill/b1-c128": { - "distributed_digest": "a91b167c1f4644bc7f16b38184f61a16d42d004c", - "portable_digest": "7fe68d256445d118ae842cef0d23e123741a7b62", + "distributed_digest": "56e34687555489beae3d40b66deea30e529bde10", + "portable_digest": "71d41b98eb84dfc57ee3ec0e4614ce1a52622aef", "component_coverage": 0.75, "baseline_comparable_block_seconds": 0.0002015, "system_evidence_comparable_block_seconds": 0.00023554310256410256, diff --git a/docs/assets/architecture/implemented-derivation-path.svg b/docs/assets/architecture/implemented-derivation-path.svg index f945b09..c67d466 100644 --- a/docs/assets/architecture/implemented-derivation-path.svg +++ b/docs/assets/architecture/implemented-derivation-path.svg @@ -1,6 +1,6 @@ Currently implemented Blueprinting workload-derivation path - The typed Transformer model is derived through ModelIR, DistributedTaskIR, and PortablePlanIR with verified checkpoints. Hardware evidence enters only through derived cost views after portable planning. + Independent Transformer model, workload, and logical mapping contracts are derived through ModelIR, DistributedTaskIR, and PortablePlanIR with verified checkpoints. Hardware evidence and physical network-tier binding enter only through derived cost views after portable planning. @@ -32,12 +32,15 @@ DERIVED VALIDATION / 派生验证 - + TransformerModelSpec - hidden · FFN · heads · sequence · blocks - - TransformerExecutionSpec - TP/PP/DP · microbatch · recompute · comm mode + shape · graph semantics + + TrainingWorkloadSpec + batch · microbatch · dtype + + TrainingMappingSpec + TP/PP/DP · recompute · comm Typed validation / 类型校验 @@ -53,14 +56,15 @@ NO target · deployment · calibration binding - - + + + - transformer-distribute-v1 + transformer-distribute-v2 ModelIR → primitive phases + local TP mesh + explicit collectives + structural recomputation DistributedTaskIR @@ -69,7 +73,7 @@ PassCheckpoint #1 - transformer-plan-work-v1 + transformer-plan-work-v2 DistributedTaskIR → exact workload facts + abstract resources/buffers + capability alternatives PortablePlanIR @@ -95,9 +99,9 @@ - SystemProfile - peak throughput/bandwidth + versioned efficiency curves - same evidence revision shared across every case + SystemProfile + NetworkTierBinding + versioned rates/curves + explicit TP/PP/DP tier placement + late-bound; never part of the PortablePlanIR digest Peak-only view diff --git a/docs/design/modules.en.md b/docs/design/modules.en.md index 99adb5a..2354e46 100644 --- a/docs/design/modules.en.md +++ b/docs/design/modules.en.md @@ -26,13 +26,17 @@ result.sensitivity Internally, each candidate creates an immutable typed derivation context for workload mapping, architecture binding, and analysis addressing. The current implementation names this object `SynthesisSession`; that class and its workload/strategy bindings are implemented, while `ExplorationSession` and the end-to-end product facade are planned. Global mutable configuration is forbidden because it would invalidate experiment reproducibility. -## Workload and system domain models +## Workload, mapping, and system domain models -`blueprinting.workload` owns target-neutral model semantics, request scenarios, and logical mapping intent. A workload object cannot contain a chip name, peak rate, empirical latency, kernel identity, or physical placement. The current slice provides typed Transformer training and inference contracts. +`blueprinting.workload` owns target-neutral model semantics and request/training scenarios. A workload object cannot contain parallel placement, a chip name, peak rate, empirical latency, kernel identity, or physical placement. The current slice provides typed Transformer model, training-workload, and inference-request contracts. + +`blueprinting.mapping` owns target-neutral logical strategies such as TP/PP/DP, recomputation, and collective form. It also owns explicit deployment-side associations such as `NetworkTierBinding`; those associations are supplied to evaluation after portable planning and are never embedded in workload facts or a `PortablePlanIR`. This separation makes the same portable plan evaluable on materially different systems. + +Boundary importers still accept the retained Calculon-style field names, but aliases are not a second schema: if canonical and legacy spellings are both present they must agree, otherwise import fails before derivation. Newly constructed domain objects and reports use canonical ownership and typed fields. `blueprinting.system` owns immutable chip-local compute engines, memory capacity/bandwidth, interconnect tiers, collective volume rules, and their imported evidence revision. `SystemProfile` is the current limited compute/memory/network adapter; it is not yet the hierarchical `ArchitectureBlueprint`, physical deployment, or target binding described by the product design. Cost policy remains in `analysis`: the system contract exposes peak and evidence-bearing facts but does not choose calibration mode. -These packages are authoritative domain inputs, not alternative IR hierarchies. Canonical derivation starts only when a synthesizer frontend imports a workload contract into `ModelIR`; a system profile remains outside canonical workload state and is consumed by explicit analysis or later target binding. +These three packages are authoritative domain inputs, not alternative IR hierarchies. Canonical derivation starts only when a synthesizer frontend imports workload and logical-strategy contracts into `ModelIR` plus a typed `SynthesisSession`; a system profile and deployment-side network binding remain outside canonical workload state and are consumed by explicit analysis or later target binding. ## Frontends @@ -44,7 +48,7 @@ The current frontend covers typed decoder-only Transformer training plus static ## Canonical formal-representation infrastructure -The representation core—whose concrete types currently use the `*IR` suffix—provides immutable values, `NodeId` and `ValueId`, typed lineage, exact scalar expressions, canonical JSON, schema versions, feature sets, deterministic digests, and verifier diagnostics. +`blueprinting.schema` provides the dependency-free canonical codec, frozen maps, and serialization errors shared by all typed contracts. The representation core in `blueprinting.synthesizer`—whose concrete types currently use the `*IR` suffix—provides `NodeId` and `ValueId`, typed lineage, exact scalar expressions, schema headers, feature sets, deterministic digests, and verifier diagnostics. It has no dependency on Transformer-specific derivation, target plugins, performance providers, or simulation. Typed extensions may carry namespaced semantics; free-form metadata has no compatibility meaning. @@ -115,31 +119,37 @@ The same lineage supports forward and reverse queries from model operation to ru ## Dependency direction ```text -workload ──► synthesizer/frontend ──► ModelIR - │ - lowering/passes ──► portable planning - │ │ -system ─────────────────────────► analysis ▼ - │ architecture binding -evidence ──────────────────────────────┘ │ +schema ──► workload ──► mapping + │ │ │ + ├──────────┴───────────┴──► synthesizer ──► PortablePlanIR + │ │ │ + └──► system ───────────────────────────┼──► analysis ◄── evidence + NetworkTierBinding │ ▼ - simulation / emission + application / validation ``` -The dependency direction is explicit: workload contracts do not depend on system descriptions; system descriptions do not depend on analysis policy; analysis does not construct canonical plans. The synthesizer materializes workload and plan facts, while analysis evaluates those facts against system descriptions and external evidence. Callers must not treat cost evidence as an implicit lowering decision. +The dependency direction is explicit: workload contracts do not depend on mapping or system descriptions; logical mappings may validate against workload shapes but do not read systems; system descriptions do not depend on analysis policy; analysis does not construct canonical plans. The synthesizer materializes workload and plan facts, while analysis evaluates those facts against explicit system, deployment mapping, and external evidence. Validation may consume the whole supported stack but no production layer depends on validation or an external oracle. ## Current source map | Concern | Source | Status | |---|---|---| -| Workload semantics and logical mapping intent | `workload/` | Implemented Transformer slice | +| Canonical codec and frozen schema values | `schema/` | Implemented | +| Model and workload semantics | `workload/` | Implemented Transformer slice | +| Logical strategies and explicit deployment mapping | `mapping/` | Implemented Transformer/network slice | | Chip, memory, interconnect, and aggregate system profile | `system/` | Implemented limited profile adapter | -| IDs, expressions, codec, frozen values | `synthesizer/{ids,expr,codec,frozen}.py` | Implemented | +| IDs, expressions, lineage | `synthesizer/{ids,expr}.py` | Implemented | | Canonical formal representations (`*IR`) | `synthesizer/ir/` | Implemented contracts | | Bindings and sessions | `synthesizer/{bindings,session}.py` | Implemented | | Analysis/transformation transactions | `synthesizer/passes/base.py` | Implemented | | Workload-to-IR/session frontends | `synthesizer/frontend/` | Implemented Transformer slice | -| Workload and cost analysis | `analysis/` | Implemented slice | -| Transformer derivation passes | `synthesizer/lowering/transformer.py` | Implemented through portable plan | +| Transformer exact-work dialect | `synthesizer/dialects/transformer/` | Implemented training/inference slice | +| Transformer derivation passes | `synthesizer/lowering/` | Implemented through portable plan | | Current system cost adapters | `analysis/cost_model.py`, `analysis/cost/` | Implemented slice | +| Framework-neutral orchestration and reports | `application/` | Implemented static analysis slice | +| Calculon/Vidur comparisons and regression gates | `validation/` | Implemented offline gates | +| Optional external performance bundles | `data/evidence/` | Explicitly loaded; excluded from base package | | Architecture model/search, evidence service, simulation, emission | Accepted boundaries | Planned | + +`validation/legacy/` contains retained Calculon-only reproductions of historical SeqSel figures. They are compatibility checks, not evidence that the canonical Blueprinting derivation path is correct; the strict gates are `validation/calculon.py`, `validation/vidur.py`, and `validation/regression.py`. diff --git a/docs/design/modules.zh.md b/docs/design/modules.zh.md index 01de6d0..77e70e8 100644 --- a/docs/design/modules.zh.md +++ b/docs/design/modules.zh.md @@ -26,13 +26,17 @@ result.sensitivity 内部会为每个 candidate 创建 immutable typed derivation context,用于 workload mapping、architecture binding 与 analysis addressing。当前实现把这个对象命名为 `SynthesisSession`;该 class 及其 workload/strategy binding 已实现,而 `ExplorationSession` 与 end-to-end product facade 仍为 planned。Global mutable configuration 被禁止,因为它会破坏 experiment reproducibility。 -## Workload 与 System 领域模型 +## Workload、Mapping 与 System 领域模型 -`blueprinting.workload` 拥有 target-neutral model semantic、request scenario 与 logical mapping intent。Workload object 不得包含 chip name、peak rate、empirical latency、kernel identity 或 physical placement。当前 slice 提供 typed Transformer training/inference contract。 +`blueprinting.workload` 拥有 target-neutral model semantic 与 request/training scenario。Workload object 不得包含 parallel placement、chip name、peak rate、empirical latency、kernel identity 或 physical placement。当前 slice 提供 typed Transformer model、training-workload 与 inference-request contract。 + +`blueprinting.mapping` 拥有 TP/PP/DP、recomputation、collective form 等 target-neutral logical strategy,也拥有 `NetworkTierBinding` 这类显式 deployment-side association。后者只在 portable planning 之后提供给 evaluation,绝不嵌入 workload fact 或 `PortablePlanIR`。因此同一份 portable plan 可以在实质不同的 system 上评估。 + +边界 importer 仍接受保留的 Calculon 风格字段名,但 alias 不是第二套 schema:canonical 与 legacy 拼写同时出现时必须一致,否则在 derivation 前拒绝导入。新构造的 domain object 与 report 只使用 canonical ownership 和 typed field。 `blueprinting.system` 拥有 immutable chip-local compute engine、memory capacity/bandwidth、interconnect tier、collective volume rule 与导入 evidence revision。`SystemProfile` 是当前有限的 compute/memory/network adapter;它还不是产品设计中的 hierarchical `ArchitectureBlueprint`、physical deployment 或 target binding。Cost policy 继续属于 `analysis`:system contract 暴露 peak 与 evidence-bearing facts,但不选择 calibration mode。 -这两个 package 是权威 domain input,不是另一套 IR hierarchy。只有 synthesizer frontend 把 workload contract 导入 `ModelIR` 后,canonical derivation 才开始;system profile 继续位于 canonical workload state 之外,只能被显式 analysis 或后续 target binding 消费。 +这三个 package 是权威 domain input,不是另一套 IR hierarchy。只有 synthesizer frontend 把 workload 与 logical-strategy contract 导入 `ModelIR` 和 typed `SynthesisSession` 后,canonical derivation 才开始;system profile 与 deployment-side network binding 继续位于 canonical workload state 之外,只能被显式 analysis 或后续 target binding 消费。 ## Frontend @@ -44,7 +48,7 @@ Frontend 不读取 peak throughput、kernel catalog、physical topology 或 runt ## Canonical 形式化表示基础设施 -Representation core 的具体类型目前使用 `*IR` 后缀;它提供 immutable value、`NodeId`/`ValueId`、typed lineage、exact scalar expression、canonical JSON、schema version、feature set、deterministic digest 和 verifier diagnostic。 +`blueprinting.schema` 提供所有 typed contract 共享且无领域依赖的 canonical codec、frozen map 与 serialization error。`blueprinting.synthesizer` 中的 representation core 具体类型目前使用 `*IR` 后缀;它提供 `NodeId`/`ValueId`、typed lineage、exact scalar expression、schema header、feature set、deterministic digest 和 verifier diagnostic。 它不依赖 Transformer-specific derivation、target plugin、performance provider 或 simulation。Typed extension 可以承载 namespaced semantic;free-form metadata 不具有 compatibility meaning。 @@ -115,31 +119,37 @@ Profiler adapter 把 runtime event 关联到 machine instruction 与 concrete co ## 依赖方向 ```text -workload ──► synthesizer/frontend ──► ModelIR - │ - lowering/passes ──► portable planning - │ │ -system ─────────────────────────► analysis ▼ - │ architecture binding -evidence ──────────────────────────────┘ │ +schema ──► workload ──► mapping + │ │ │ + ├──────────┴───────────┴──► synthesizer ──► PortablePlanIR + │ │ │ + └──► system ───────────────────────────┼──► analysis ◄── evidence + NetworkTierBinding │ ▼ - simulation / emission + application / validation ``` -依赖方向是显式的:workload contract 不依赖 system description;system description 不依赖 analysis policy;analysis 不构造 canonical plan。Synthesizer 物化 workload/plan facts,analysis 再使用 system description 与外部 evidence 评估这些事实。调用方不能把 cost evidence 当作隐式 lowering 决策。 +依赖方向是显式的:workload contract 不依赖 mapping 或 system description;logical mapping 可以针对 workload shape 做验证,但不能读取 system;system description 不依赖 analysis policy;analysis 不构造 canonical plan。Synthesizer 物化 workload/plan fact,analysis 再使用显式 system、deployment mapping 与外部 evidence 评估这些事实。Validation 可以消费整个 supported stack,但 production layer 不得依赖 validation 或外部 oracle。 ## 当前源码映射 | 关注点 | 源码 | 状态 | |---|---|---| -| Workload semantic 与 logical mapping intent | `workload/` | Implemented Transformer slice | +| Canonical codec 与 frozen schema value | `schema/` | Implemented | +| Model 与 workload semantic | `workload/` | Implemented Transformer slice | +| Logical strategy 与显式 deployment mapping | `mapping/` | Implemented Transformer/network slice | | Chip、memory、interconnect 与聚合 system profile | `system/` | Implemented limited profile adapter | -| ID、expression、codec、frozen value | `synthesizer/{ids,expr,codec,frozen}.py` | Implemented | +| ID、expression、lineage | `synthesizer/{ids,expr}.py` | Implemented | | Canonical 形式化表示(`*IR`) | `synthesizer/ir/` | Implemented contracts | | Binding 与 session | `synthesizer/{bindings,session}.py` | Implemented | | Analysis/transformation transaction | `synthesizer/passes/base.py` | Implemented | | Workload-to-IR/session frontend | `synthesizer/frontend/` | Implemented Transformer slice | -| Workload 与 cost analysis | `analysis/` | Implemented slice | -| Transformer derivation pass | `synthesizer/lowering/transformer.py` | Implemented through portable plan | +| Transformer exact-work dialect | `synthesizer/dialects/transformer/` | Implemented training/inference slice | +| Transformer derivation pass | `synthesizer/lowering/` | Implemented through portable plan | | 当前 system cost adapter | `analysis/cost_model.py`、`analysis/cost/` | Implemented slice | +| Framework-neutral orchestration 与 report | `application/` | Implemented static analysis slice | +| Calculon/Vidur comparison 与 regression gate | `validation/` | Implemented offline gate | +| Optional external performance bundle | `data/evidence/` | 显式加载;从 base package 排除 | | Architecture model/search、evidence service、simulation、emission | Accepted boundary | Planned | + +`validation/legacy/` 保留 Calculon-only 的历史 SeqSel 图表复现。它们是 compatibility check,不构成 canonical Blueprinting derivation 正确性的证据;严格 gate 位于 `validation/calculon.py`、`validation/vidur.py` 与 `validation/regression.py`。 diff --git a/docs/design/passes/transformer.en.md b/docs/design/passes/transformer.en.md index 4efa5ee..8422ff9 100644 --- a/docs/design/passes/transformer.en.md +++ b/docs/design/passes/transformer.en.md @@ -9,7 +9,7 @@ The implemented Transformer slice is deliberately narrow and auditable: it impor The production path is: ```text -TransformerModelSpec + TransformerExecutionSpec +TransformerModelSpec + TransformerTrainingWorkloadSpec + TransformerTrainingMappingSpec -> ModelIR -> DistributeTransformerTrainingPass -> DistributedTaskIR @@ -23,7 +23,9 @@ This slice currently models decoder-only training at block scope. Full-model PP/ ## Typed semantic import -`TransformerModelSpec` owns dimensions and model semantics. `TransformerExecutionSpec` owns micro-batching, TP/PP/DP, recomputation, datatype, and tensor-parallel communication mode. `synthesis_session_for()` turns those execution choices into explicit workload and strategy bindings. +`TransformerModelSpec` owns dimensions and model semantics. `TransformerTrainingWorkloadSpec` owns global/micro batch size and datatype. `TransformerTrainingMappingSpec` owns TP/PP/DP, recomputation, pipeline interleaving, optimizer sharding, and tensor-parallel communication mode. `synthesis_session_for()` converts these independent contracts into explicit workload and strategy bindings. + +Physical network-tier selection is deliberately absent. `NetworkTierBinding` is supplied only when a portable plan is evaluated against a `SystemProfile`; changing it cannot change the model, distributed, or portable-plan digest. The importer rejects invalid dimensions, head divisibility, parallel topology, and inconsistent workload facts before a pass runs. `build_transformer_model_ir()` then creates a coarse, target-neutral `transformer.decoder_training` operation. No target name, peak rate, kernel ID, or latency enters this snapshot. @@ -86,11 +88,13 @@ The derivation does not compensate for a discrepancy by reading a reference late | Concern | Source | Tests | |---|---|---| -| Typed Transformer specifications | `src/blueprinting/workload/transformer.py` | binding and calibration tests | +| Model and training-workload contracts | `src/blueprinting/workload/transformer.py` | binding and validation tests | +| Logical mapping contract | `src/blueprinting/mapping/transformer.py` | boundary and validation tests | | Workload-to-IR frontend | `src/blueprinting/synthesizer/frontend/transformer.py` | canonical representation and calibration tests | -| Workload algebra | `src/blueprinting/analysis/transformer_workload.py` | `tests/synthesizer/test_calculon_calibration.py` | +| Workload algebra | `src/blueprinting/synthesizer/dialects/transformer/training.py` | `tests/validation/test_calculon.py` | | Two derivation passes | `src/blueprinting/synthesizer/lowering/transformer.py` | canonical representation and calibration tests | | Transaction/checkpoints | `src/blueprinting/synthesizer/passes/base.py` | `tests/synthesizer/test_pass_manager.py` | -| Evidence-derived estimates | `src/blueprinting/analysis/cost_model.py` | calibration tests | +| Evidence-derived estimates | `src/blueprinting/analysis/cost_model.py` | validation tests | +| Calculon/SeqSel oracle gate | `src/blueprinting/validation/calculon.py` | `tests/validation/test_calculon.py` | The [Calculon calibration experiment](../../experiments/calculon-calibration.md) is the end-to-end audit of this implemented slice. diff --git a/docs/design/passes/transformer.zh.md b/docs/design/passes/transformer.zh.md index e78ebaf..da89c7c 100644 --- a/docs/design/passes/transformer.zh.md +++ b/docs/design/passes/transformer.zh.md @@ -9,7 +9,7 @@ 当前 production path 是: ```text -TransformerModelSpec + TransformerExecutionSpec +TransformerModelSpec + TransformerTrainingWorkloadSpec + TransformerTrainingMappingSpec -> ModelIR -> DistributeTransformerTrainingPass -> DistributedTaskIR @@ -23,7 +23,9 @@ TransformerModelSpec + TransformerExecutionSpec ## 强类型语义导入 -`TransformerModelSpec` 拥有模型维度与语义,`TransformerExecutionSpec` 拥有 micro-batching、TP/PP/DP、重计算、数据类型和 tensor-parallel 通信模式。`synthesis_session_for()` 把这些执行选择转换成显式 workload 与 strategy binding。 +`TransformerModelSpec` 拥有模型维度与语义,`TransformerTrainingWorkloadSpec` 拥有 global/micro batch size 与 datatype,`TransformerTrainingMappingSpec` 拥有 TP/PP/DP、重计算、pipeline interleaving、optimizer sharding 与 tensor-parallel 通信模式。`synthesis_session_for()` 把这些彼此独立的 contract 转换成显式 workload 与 strategy binding。 + +Physical network tier 的选择被刻意排除。只有在用 `SystemProfile` 评估 portable plan 时才会提供 `NetworkTierBinding`;改变它不能改变 model、distributed 或 portable-plan digest。 Importer 会在 pass 运行前拒绝非法维度、head 不可整除、错误并行拓扑以及互相矛盾的 workload facts。随后 `build_transformer_model_ir()` 创建一个粗粒度、target-neutral 的 `transformer.decoder_training` operation。这个 snapshot 中不存在 target 名称、峰值性能、kernel ID 或 latency。 @@ -86,11 +88,13 @@ Observer 可以把这些 facts 与 framework trace 或 reference model 对比并 | 关注点 | 源码 | 测试 | |---|---|---| -| 强类型 Transformer specification | `src/blueprinting/workload/transformer.py` | binding 与 calibration tests | +| 模型与训练 workload contract | `src/blueprinting/workload/transformer.py` | binding 与 validation tests | +| 逻辑 mapping contract | `src/blueprinting/mapping/transformer.py` | boundary 与 validation tests | | Workload-to-IR frontend | `src/blueprinting/synthesizer/frontend/transformer.py` | canonical representation 与 calibration tests | -| 工作量代数 | `src/blueprinting/analysis/transformer_workload.py` | `tests/synthesizer/test_calculon_calibration.py` | +| 工作量代数 | `src/blueprinting/synthesizer/dialects/transformer/training.py` | `tests/validation/test_calculon.py` | | 两个 derivation pass | `src/blueprinting/synthesizer/lowering/transformer.py` | canonical representation 与 calibration tests | | 事务与 checkpoint | `src/blueprinting/synthesizer/passes/base.py` | `tests/synthesizer/test_pass_manager.py` | -| Evidence-derived estimate | `src/blueprinting/analysis/cost_model.py` | calibration tests | +| Evidence-derived estimate | `src/blueprinting/analysis/cost_model.py` | validation tests | +| Calculon/SeqSel oracle gate | `src/blueprinting/validation/calculon.py` | `tests/validation/test_calculon.py` | [Calculon 校准实验](../../experiments/calculon-calibration.md)是这条已实现纵向切片的端到端审计。 diff --git a/docs/experiments/calculon-calibration.en.md b/docs/experiments/calculon-calibration.en.md index 0ca0435..ef5a64d 100644 --- a/docs/experiments/calculon-calibration.en.md +++ b/docs/experiments/calculon-calibration.en.md @@ -19,13 +19,13 @@ model.json | semantic import v ModelIR: transformer.decoder_training - | transformer-distribute-v1 + | transformer-distribute-v2 | - decompose Transformer primitives | - insert explicit TP collectives | - clone selective/full recomputation primitives v DistributedTaskIR: local TP block task DAG - | transformer-plan-work-v1 + | transformer-plan-work-v2 | - derive operations/read/write/message bytes | - do not bind GPU/LPU or write duration v @@ -150,10 +150,10 @@ Implementation map: - `workload/transformer.py`: typed workload and execution facts; - `synthesizer/frontend/transformer.py`: canonical import and binding adapter; -- `analysis/transformer_workload.py`: static operation/byte analysis; +- `synthesizer/dialects/transformer/training.py`: static operation/byte derivation; - `synthesizer/lowering/transformer.py`: the two canonical derivation passes; - `analysis/cost_model.py`: peak-only and evidence-backed views; -- `synthesizer/experiments/calculon.py`: oracle adapter, audit, and report. -- `synthesizer/experiments/regression.py`: strict cross-domain baseline gate and diagnostics. +- `validation/calculon.py`: oracle adapter, audit, and report. +- `validation/regression.py`: strict cross-domain baseline gate and diagnostics. This is the repository's single Blueprinting/Calculon calibration path. Future comparisons must keep oracle data unavailable until workload construction and estimation complete. See [Transformer workload derivation](../design/passes/transformer.md) for the internal transformation contracts and [performance evidence](../design/performance/index.md) for the intended provider migration. diff --git a/docs/experiments/calculon-calibration.zh.md b/docs/experiments/calculon-calibration.zh.md index 4a7de77..ed5abdc 100644 --- a/docs/experiments/calculon-calibration.zh.md +++ b/docs/experiments/calculon-calibration.zh.md @@ -19,13 +19,13 @@ model.json | semantic import v ModelIR: transformer.decoder_training - | transformer-distribute-v1 + | transformer-distribute-v2 | - decompose Transformer primitives | - insert explicit TP collectives | - clone selective/full recomputation primitives v DistributedTaskIR: local TP block task DAG - | transformer-plan-work-v1 + | transformer-plan-work-v2 | - derive operations/read/write/message bytes | - do not bind GPU/LPU or write duration v @@ -150,10 +150,10 @@ uv run pytest -m baseline_regression tests/regression - `workload/transformer.py`:typed workload 与 execution facts; - `synthesizer/frontend/transformer.py`:canonical import 与 binding adapter; -- `analysis/transformer_workload.py`:静态 operation/byte analysis; +- `synthesizer/dialects/transformer/training.py`:静态 operation/byte derivation; - `synthesizer/lowering/transformer.py`:两个 canonical derivation pass; - `analysis/cost_model.py`:peak-only 与 evidence-backed view; -- `synthesizer/experiments/calculon.py`:oracle adapter、audit 与 report。 -- `synthesizer/experiments/regression.py`:严格的跨域 baseline gate 与诊断。 +- `validation/calculon.py`:oracle adapter、audit 与 report。 +- `validation/regression.py`:严格的跨域 baseline gate 与诊断。 这是仓库唯一的 Blueprinting/Calculon calibration path。未来对比仍必须保证 workload construction 与 estimation 完成前无法访问 oracle data。内部 transformation contract 参见 [Transformer 工作负载推导](../design/passes/transformer.md),未来 provider 迁移参见 [performance evidence](../design/performance/index.md)。 diff --git a/docs/modeling/inference.en.md b/docs/modeling/inference.en.md index c6680ac..213bf16 100644 --- a/docs/modeling/inference.en.md +++ b/docs/modeling/inference.en.md @@ -29,20 +29,22 @@ The model frontend emits one phase-neutral `transformer.decoder_inference` opera ```text TransformerModelSpec - + TransformerInferenceExecutionSpec(TP, PP, replicas, dtype, network tiers) - + WorkloadBinding(INFERENCE, PREFILL | DECODE, batch, context) + + TransformerInferenceRequestSpec(batch, prompt, generated, dtype) + + TransformerInferenceMappingSpec(TP, PP, replicas) + + WorkloadBinding(INFERENCE, PREFILL | DECODE, batch, context, dtype) -> ModelIR -> DistributeTransformerInferencePass -> DistributedTaskIR -> PlanTransformerInferencePass -> PortablePlanIR + -> [SystemProfile + NetworkTierBinding] -> estimate_inference_phase(Blueprinting cost provider | analytical model) -> optional post-hoc Vidur comparison ``` The component tasks are input norm, QKV projection, RoPE, KV save, attention core, output projection, TP all-reduce, residual, post-attention norm, MLP up/activation/down, a second all-reduce, and the final residual. This boundary is fine enough to inspect work conservation and broad enough to match observable kernel families in profiling systems. -Prefill binds `query_tokens = context_tokens = prompt_tokens`. Decode binds `query_tokens = 1` and treats `context_tokens` as the number of keys visible after the current token is appended. Every phase plan carries exact operations, read/write bytes, collective volume, phase, primitive, source layer, query length, context length, block weight capacity, KV capacity, and a conservative workspace buffer. It carries no duration. Costing reconstructs its task view from `PlanTask.workload` and explicit buffers; a hidden lowering object is not allowed to become a second workload truth. +Prefill binds `query_tokens = context_tokens = prompt_tokens`. Decode binds `query_tokens = 1` and treats `context_tokens` as the number of keys visible after the current token is appended. Every phase plan carries exact operations, read/write bytes, logical collective volume, phase, primitive, source layer, query length, context length, datatype, block weight capacity, KV capacity, and a conservative workspace buffer. It carries no duration or physical network tier. Costing reconstructs its task view from `PlanTask.workload` and explicit buffers, then applies an explicit `NetworkTierBinding`; neither a hidden lowering object nor deployment placement may become a second workload truth. ## Request composition semantics @@ -71,10 +73,11 @@ It is multiplied by the number of blocks in one pipeline stage. Weight storage i ```python from blueprinting.analysis import VidurProfileBaseline +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec from blueprinting.system import SystemProfile from blueprinting.synthesizer.bindings import InferencePhase -from blueprinting.synthesizer.experiments import VidurExperimentCase, run_vidur_experiment -from blueprinting.workload import TransformerInferenceExecutionSpec, TransformerModelSpec +from blueprinting.validation import VidurExperimentCase, run_vidur_experiment +from blueprinting.workload import TransformerModelSpec baseline = VidurProfileBaseline.from_csv( attention_csv="/profiles/attention.csv", @@ -88,7 +91,9 @@ baseline = VidurProfileBaseline.from_csv( case = VidurExperimentCase( name="decode/context-128", model=TransformerModelSpec(...), - execution=TransformerInferenceExecutionSpec(...), + mapping=TransformerInferenceMappingSpec(...), + network_binding=NetworkTierBinding(...), + datatype="float16", hardware=SystemProfile(...), phase=InferencePhase.DECODE, batch_size=1, diff --git a/docs/modeling/inference.zh.md b/docs/modeling/inference.zh.md index 0c8214f..7ba423f 100644 --- a/docs/modeling/inference.zh.md +++ b/docs/modeling/inference.zh.md @@ -29,20 +29,22 @@ Frontend 生成一个 phase-neutral 的 `transformer.decoder_inference` operatio ```text TransformerModelSpec - + TransformerInferenceExecutionSpec(TP, PP, replicas, dtype, network tiers) - + WorkloadBinding(INFERENCE, PREFILL | DECODE, batch, context) + + TransformerInferenceRequestSpec(batch, prompt, generated, dtype) + + TransformerInferenceMappingSpec(TP, PP, replicas) + + WorkloadBinding(INFERENCE, PREFILL | DECODE, batch, context, dtype) -> ModelIR -> DistributeTransformerInferencePass -> DistributedTaskIR -> PlanTransformerInferencePass -> PortablePlanIR + -> [SystemProfile + NetworkTierBinding] -> estimate_inference_phase(Blueprinting cost provider | analytical model) -> optional post-hoc Vidur comparison ``` Component task 包括 input norm、QKV projection、RoPE、KV save、attention core、output projection、TP all-reduce、residual、post-attention norm、MLP up/activation/down、第二次 all-reduce 与最终 residual。这个边界既足以检查 work conservation,也能与 profiling system 中可观测的 kernel family 对齐。 -Prefill 绑定 `query_tokens = context_tokens = prompt_tokens`。Decode 绑定 `query_tokens = 1`,并把 `context_tokens` 定义为追加当前 token 后 attention 可见的 key 数。每个 phase plan 保存精确 operations、read/write bytes、collective volume、phase、primitive、source layer、query/context length、block weight capacity、KV capacity 与保守 workspace buffer;它不携带 duration。Costing 只从 `PlanTask.workload` 与显式 buffer 重建 task view,不允许隐藏 lowering object 成为第二份 workload 真值。 +Prefill 绑定 `query_tokens = context_tokens = prompt_tokens`。Decode 绑定 `query_tokens = 1`,并把 `context_tokens` 定义为追加当前 token 后 attention 可见的 key 数。每个 phase plan 保存精确 operations、read/write bytes、逻辑 collective volume、phase、primitive、source layer、query/context length、datatype、block weight capacity、KV capacity 与保守 workspace buffer;它不携带 duration 或 physical network tier。Costing 只从 `PlanTask.workload` 与显式 buffer 重建 task view,再应用显式 `NetworkTierBinding`,不允许隐藏 lowering object 或 deployment placement 成为第二份 workload 真值。 ## Request composition 语义 @@ -71,10 +73,11 @@ mean decode-step model time = decode total / (O-1), when O > 1 ```python from blueprinting.analysis import VidurProfileBaseline +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec from blueprinting.system import SystemProfile from blueprinting.synthesizer.bindings import InferencePhase -from blueprinting.synthesizer.experiments import VidurExperimentCase, run_vidur_experiment -from blueprinting.workload import TransformerInferenceExecutionSpec, TransformerModelSpec +from blueprinting.validation import VidurExperimentCase, run_vidur_experiment +from blueprinting.workload import TransformerModelSpec baseline = VidurProfileBaseline.from_csv( attention_csv="/profiles/attention.csv", @@ -88,7 +91,9 @@ baseline = VidurProfileBaseline.from_csv( case = VidurExperimentCase( name="decode/context-128", model=TransformerModelSpec(...), - execution=TransformerInferenceExecutionSpec(...), + mapping=TransformerInferenceMappingSpec(...), + network_binding=NetworkTierBinding(...), + datatype="float16", hardware=SystemProfile(...), phase=InferencePhase.DECODE, batch_size=1, diff --git a/docs/project/status.en.md b/docs/project/status.en.md index f580584..346ad44 100644 --- a/docs/project/status.en.md +++ b/docs/project/status.en.md @@ -50,18 +50,19 @@ The five current IR classes use `1.0.0` as an internal canonical serialization v The current runnable slices are: ```text -TransformerModelSpec + TransformerExecutionSpec +TransformerModelSpec + TransformerTrainingWorkloadSpec + TransformerTrainingMappingSpec -> exact workload decomposition -> ModelIR -> DistributedTaskIR -> PortablePlanIR - -> SystemProfile analytical estimate + -> [SystemProfile + NetworkTierBinding] analytical estimate -> Calculon / paper comparison report -TransformerModelSpec + inference mapping + request cohort +TransformerModelSpec + inference request cohort + inference mapping -> phase-neutral inference ModelIR -> independently bound prefill and decode DistributedTaskIR -> phase-local PortablePlanIR with KV state/capacity + -> [SystemProfile + NetworkTierBinding] -> CostResolver(exact imported evidence -> explicit roofline fallback) -> optional post-hoc Vidur baseline comparison -> static prefill / decode-step model time and analytical memory report @@ -79,18 +80,18 @@ It cannot yet claim serving-system SLO accuracy: arrivals, queueing, continuous | Foundation | Status | Source of truth | |---|---|---| -| Immutable values, stable IDs, lineage, codec, digests | **Implemented** | `src/blueprinting/synthesizer/{frozen,ids,codec}.py` | +| Immutable values, stable IDs, lineage, codec, digests | **Implemented** | `src/blueprinting/schema/`, `src/blueprinting/synthesizer/ids.py` | | Five progressive formal-representation schemas (`*IR`) and verifiers | **Experimental Contract** | `src/blueprinting/synthesizer/ir/`; only the first three have a production derivation slice | | Typed workload/strategy/target/deployment bindings | **Implemented** | `bindings.py`, `session.py` | | Chip, memory, interconnect, and aggregate system profile | **Implemented adapter** | `src/blueprinting/system/`; evidence-bearing profile, not the planned `ArchitectureBlueprint` | | Transactional analyses/transformations, checkpoints, observers | **Implemented** | `passes/base.py` | -| Transformer workload contracts, frontend, and workload algebra | **Implemented slice** | `workload/transformer.py`, `synthesizer/frontend/transformer.py`, `analysis/transformer_workload.py` | +| Transformer workload/mapping contracts, frontend, and workload algebra | **Implemented slice** | `workload/transformer.py`, `mapping/transformer.py`, `synthesizer/frontend/transformer.py`, `synthesizer/dialects/transformer/` | | Distributed and portable mapping derivations | **Implemented slice** | `lowering/transformer.py` | | Cost protocol, resolver, roofline, database, and external importers | **Implemented slice** | `analysis/cost/`, `analysis/vidur.py`; exact task latency only, not plan simulation | -| Static inference frontend, lowering, cost, and request composition | **Implemented slice** | `workload/transformer_inference.py`, `synthesizer/frontend/transformer_inference.py`, `analysis/{transformer_inference,inference_cost}.py`, `synthesizer/lowering/transformer_inference.py`, `application/inference.py` | -| Vidur raw component-profile alignment | **Implemented experiment** | `analysis/vidur.py` + `experiments/vidur.py`; a minimal licensed CI slice is pinned locally and the full upstream corpus remains external | -| Calculon experiment | **Implemented experiment** | `experiments/calculon.py` | -| External-baseline regression gate | **Implemented** | frozen contract and licensed offline fixtures under `data/validation/`; `experiments/regression.py`; `.github/workflows/quality.yml` | +| Static inference frontend, lowering, cost, and request composition | **Implemented slice** | `workload/transformer_inference.py`, `mapping/transformer.py`, `synthesizer/{frontend,lowering}/transformer_inference.py`, `synthesizer/dialects/transformer/inference.py`, `analysis/inference_cost.py`, `application/inference.py` | +| Vidur raw component-profile alignment | **Implemented experiment** | `analysis/vidur.py` + `validation/vidur.py`; a minimal licensed CI slice is pinned locally and the full upstream corpus remains external | +| Calculon experiment | **Implemented experiment** | `validation/calculon.py` | +| External-baseline regression gate | **Implemented** | frozen contract and licensed offline fixtures under `data/validation/`; `validation/regression.py`; `.github/workflows/quality.yml` | These typed representations, verifiers, derivation transactions, and analyses are the formal foundation for hardware exploration. New architecture models, simulator providers, and analysis products should extend this one semantic foundation rather than establish parallel workload truth. diff --git a/docs/project/status.zh.md b/docs/project/status.zh.md index 33d68aa..dd00637 100644 --- a/docs/project/status.zh.md +++ b/docs/project/status.zh.md @@ -50,18 +50,19 @@ 当前存在两条可运行切片: ```text -TransformerModelSpec + TransformerExecutionSpec +TransformerModelSpec + TransformerTrainingWorkloadSpec + TransformerTrainingMappingSpec -> exact workload decomposition -> ModelIR -> DistributedTaskIR -> PortablePlanIR - -> SystemProfile analytical estimate + -> [SystemProfile + NetworkTierBinding] analytical estimate -> Calculon / paper comparison report -TransformerModelSpec + inference mapping + request cohort +TransformerModelSpec + inference request cohort + inference mapping -> phase-neutral inference ModelIR -> 分别绑定的 prefill/decode DistributedTaskIR -> 携带 KV state/capacity 的 phase-local PortablePlanIR + -> [SystemProfile + NetworkTierBinding] -> CostResolver(exact imported evidence -> 显式 roofline fallback) -> optional post-hoc Vidur baseline comparison -> 静态 prefill / decode-step model time 与解析 memory report @@ -79,18 +80,18 @@ TransformerModelSpec + inference mapping + request cohort | 基础 | 状态 | Source of truth | |---|---|---| -| Immutable value、stable ID、lineage、codec、digest | **Implemented** | `src/blueprinting/synthesizer/{frozen,ids,codec}.py` | +| Immutable value、stable ID、lineage、codec、digest | **Implemented** | `src/blueprinting/schema/`、`src/blueprinting/synthesizer/ids.py` | | 五层 progressive formal-representation schema(`*IR`)与 verifier | **Experimental Contract** | `src/blueprinting/synthesizer/ir/`;只有前三层存在 production derivation slice | | Typed workload/strategy/target/deployment binding | **Implemented** | `bindings.py`、`session.py` | | Chip、memory、interconnect 与聚合 system profile | **Implemented adapter** | `src/blueprinting/system/`;是 evidence-bearing profile,不是计划中的 `ArchitectureBlueprint` | | Transactional analysis/transformation、checkpoint、observer | **Implemented** | `passes/base.py` | -| Transformer workload contract、frontend 与 workload algebra | **Implemented slice** | `workload/transformer.py`、`synthesizer/frontend/transformer.py`、`analysis/transformer_workload.py` | +| Transformer workload/mapping contract、frontend 与 workload algebra | **Implemented slice** | `workload/transformer.py`、`mapping/transformer.py`、`synthesizer/frontend/transformer.py`、`synthesizer/dialects/transformer/` | | Distributed/portable mapping derivation | **Implemented slice** | `lowering/transformer.py` | | Cost protocol、resolver、roofline、database 与外部 importer | **Implemented slice** | `analysis/cost/`、`analysis/vidur.py`;仅覆盖 exact task latency,不是 plan simulation | -| Static inference frontend、lowering、cost 与 request composition | **Implemented slice** | `workload/transformer_inference.py`、`synthesizer/frontend/transformer_inference.py`、`analysis/{transformer_inference,inference_cost}.py`、`synthesizer/lowering/transformer_inference.py`、`application/inference.py` | -| Vidur raw component-profile 对齐 | **Implemented experiment** | `analysis/vidur.py` + `experiments/vidur.py`;最小带许可证 CI slice 固定在本地,完整 upstream corpus 仍保持外部依赖 | -| Calculon experiment | **Implemented experiment** | `experiments/calculon.py` | -| 外部 baseline 回归门禁 | **Implemented** | `data/validation/` 下的冻结 contract 与带许可证离线 fixture、`experiments/regression.py`、`.github/workflows/quality.yml` | +| Static inference frontend、lowering、cost 与 request composition | **Implemented slice** | `workload/transformer_inference.py`、`mapping/transformer.py`、`synthesizer/{frontend,lowering}/transformer_inference.py`、`synthesizer/dialects/transformer/inference.py`、`analysis/inference_cost.py`、`application/inference.py` | +| Vidur raw component-profile 对齐 | **Implemented experiment** | `analysis/vidur.py` + `validation/vidur.py`;最小带许可证 CI slice 固定在本地,完整 upstream corpus 仍保持外部依赖 | +| Calculon experiment | **Implemented experiment** | `validation/calculon.py` | +| 外部 baseline 回归门禁 | **Implemented** | `data/validation/` 下的冻结 contract 与带许可证离线 fixture、`validation/regression.py`、`.github/workflows/quality.yml` | 这些 typed representation、verifier、derivation transaction 与 analysis 构成 hardware exploration 的形式化基础。新的 architecture model、simulator provider 与 analysis product 应扩展这一份 semantic foundation,而不是建立平行 workload truth。 diff --git a/examples/calculon_calibration.py b/examples/calculon_calibration.py index 8a543d0..f4f189d 100644 --- a/examples/calculon_calibration.py +++ b/examples/calculon_calibration.py @@ -15,7 +15,7 @@ ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) -from blueprinting.synthesizer.experiments import ( # noqa: E402 +from blueprinting.validation import ( # noqa: E402 discover_seqsel_tab5_cases, run_calculon_experiment, ) diff --git a/examples/calculon_calibration_result.json b/examples/calculon_calibration_result.json index a1a86a5..d8cef44 100644 --- a/examples/calculon_calibration_result.json +++ b/examples/calculon_calibration_result.json @@ -43,21 +43,21 @@ "tensor_parallel": 0.19742618860307692 }, "ir": { - "distributed_digest": "3bcb5e87b8efac3cd21caf0d3eeb7c4a1e17a4af", + "distributed_digest": "1230592973066408f9b10ce90a054bc9bf3b86c6", "model_digest": "6ca20f14d69e2a0eea633ac6339f498a0a50a563", "pass_checkpoints": [ { - "digest": "3bcb5e87b8efac3cd21caf0d3eeb7c4a1e17a4af", - "pass": "transformer-distribute-v1", + "digest": "1230592973066408f9b10ce90a054bc9bf3b86c6", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "9ce66dd1c115a1efbb0cad1cc3921c3f28ffc58f", - "pass": "transformer-plan-work-v1", + "digest": "a702e06f09e0e7ed49b1d06371fdc5f01df40ead", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "9ce66dd1c115a1efbb0cad1cc3921c3f28ffc58f" + "portable_digest": "a702e06f09e0e7ed49b1d06371fdc5f01df40ead" }, "memory_bytes": { "calculon": 51705331712.0, @@ -203,21 +203,21 @@ "tensor_parallel": 0.3070430097723077 }, "ir": { - "distributed_digest": "437435a6805306add32e0557a4be1197e1d966c3", + "distributed_digest": "5623e9ff5660faaf3d66f3c3791b19f61abc5877", "model_digest": "6ca20f14d69e2a0eea633ac6339f498a0a50a563", "pass_checkpoints": [ { - "digest": "437435a6805306add32e0557a4be1197e1d966c3", - "pass": "transformer-distribute-v1", + "digest": "5623e9ff5660faaf3d66f3c3791b19f61abc5877", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "873c82dde8f06f97a619855bf7fa74d5730c7d55", - "pass": "transformer-plan-work-v1", + "digest": "d8c1a76517458d5bd92af95c125225a00c1b6a61", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "873c82dde8f06f97a619855bf7fa74d5730c7d55" + "portable_digest": "d8c1a76517458d5bd92af95c125225a00c1b6a61" }, "memory_bytes": { "calculon": 55920607232.0, @@ -363,21 +363,21 @@ "tensor_parallel": 1.5947695088246152 }, "ir": { - "distributed_digest": "4e300183da79f3354da1ced305b36a7e9c25bde6", + "distributed_digest": "878896018e2080321a2998174a5a91c269247bce", "model_digest": "e2a097e24aa28d6bc3fbfc731880d6a766ab9db9", "pass_checkpoints": [ { - "digest": "4e300183da79f3354da1ced305b36a7e9c25bde6", - "pass": "transformer-distribute-v1", + "digest": "878896018e2080321a2998174a5a91c269247bce", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "2c9566d969073f2983b2ca3e35d840c986063058", - "pass": "transformer-plan-work-v1", + "digest": "b9ed7a9c81a7cbd02066e000478d3488e31c3321", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "2c9566d969073f2983b2ca3e35d840c986063058" + "portable_digest": "b9ed7a9c81a7cbd02066e000478d3488e31c3321" }, "memory_bytes": { "calculon": 51649806336.0, @@ -523,21 +523,21 @@ "tensor_parallel": 2.487064078178462 }, "ir": { - "distributed_digest": "c237f8f3c66adea3643b1886605fc1becfc44ff6", + "distributed_digest": "1f2ac9b45bd791e4a5032c0a969270633cbd5926", "model_digest": "e2a097e24aa28d6bc3fbfc731880d6a766ab9db9", "pass_checkpoints": [ { - "digest": "c237f8f3c66adea3643b1886605fc1becfc44ff6", - "pass": "transformer-distribute-v1", + "digest": "1f2ac9b45bd791e4a5032c0a969270633cbd5926", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "d3f1826b4e0b24ee91fc5920c95c1dd3b3ca6782", - "pass": "transformer-plan-work-v1", + "digest": "bf7be9b50d482232aaee649c506f4afca5c9dfcb", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "d3f1826b4e0b24ee91fc5920c95c1dd3b3ca6782" + "portable_digest": "bf7be9b50d482232aaee649c506f4afca5c9dfcb" }, "memory_bytes": { "calculon": 58060800000.0, @@ -683,21 +683,21 @@ "tensor_parallel": 2.8847319171282053 }, "ir": { - "distributed_digest": "2bf52253b604335356e4daf86bbd80f1a0a6a9a6", + "distributed_digest": "bdc95102dbe14479526bf537c29e5a37dcd99e36", "model_digest": "941505070aec9373c78d390e55facfadc3966960", "pass_checkpoints": [ { - "digest": "2bf52253b604335356e4daf86bbd80f1a0a6a9a6", - "pass": "transformer-distribute-v1", + "digest": "bdc95102dbe14479526bf537c29e5a37dcd99e36", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "1eca776c1c27807964278cf5e768193a6f06c4cc", - "pass": "transformer-plan-work-v1", + "digest": "3988fe962f1ccecca2cee3a08b98da5e177f8c65", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "1eca776c1c27807964278cf5e768193a6f06c4cc" + "portable_digest": "3988fe962f1ccecca2cee3a08b98da5e177f8c65" }, "memory_bytes": { "calculon": 45386465280.0, @@ -843,21 +843,21 @@ "tensor_parallel": 4.4889105591794864 }, "ir": { - "distributed_digest": "83056fda70e2d0efac0e1f59982002888f8f7d48", + "distributed_digest": "37a93bf5ccd120dfcd7288b2e928d9160db2cfb5", "model_digest": "941505070aec9373c78d390e55facfadc3966960", "pass_checkpoints": [ { - "digest": "83056fda70e2d0efac0e1f59982002888f8f7d48", - "pass": "transformer-distribute-v1", + "digest": "37a93bf5ccd120dfcd7288b2e928d9160db2cfb5", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "8095358ace4a3a62466c13b0895e03545c6f5621", - "pass": "transformer-plan-work-v1", + "digest": "2e9047b02f855c3527c07cfae116bbc41631eaea", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "8095358ace4a3a62466c13b0895e03545c6f5621" + "portable_digest": "2e9047b02f855c3527c07cfae116bbc41631eaea" }, "memory_bytes": { "calculon": 57487032320.0, @@ -1003,21 +1003,21 @@ "tensor_parallel": 4.3855419689572654 }, "ir": { - "distributed_digest": "cc434813b1a0071df740e4135ec5b25a6c511729", + "distributed_digest": "b1a289cda7c0b71b92c7ff3328a6a0193aa396e6", "model_digest": "f293e3af1366c93c861a1107ac21c88e394fecc2", "pass_checkpoints": [ { - "digest": "cc434813b1a0071df740e4135ec5b25a6c511729", - "pass": "transformer-distribute-v1", + "digest": "b1a289cda7c0b71b92c7ff3328a6a0193aa396e6", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "5ffc23392d5a374f442ef2f448366e3d9f6118ce", - "pass": "transformer-plan-work-v1", + "digest": "d4a11ba89f39f36f002ee9c0179bbc02dc62a542", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "5ffc23392d5a374f442ef2f448366e3d9f6118ce" + "portable_digest": "d4a11ba89f39f36f002ee9c0179bbc02dc62a542" }, "memory_bytes": { "calculon": 49679769600.0, @@ -1163,21 +1163,21 @@ "tensor_parallel": 6.819764661606838 }, "ir": { - "distributed_digest": "e2d2c7497ff079a09c1fb88aab5e9fd0cd40f084", + "distributed_digest": "d6abe8eac1b0974539e127ccc0b0b148e9645ccc", "model_digest": "f293e3af1366c93c861a1107ac21c88e394fecc2", "pass_checkpoints": [ { - "digest": "e2d2c7497ff079a09c1fb88aab5e9fd0cd40f084", - "pass": "transformer-distribute-v1", + "digest": "d6abe8eac1b0974539e127ccc0b0b148e9645ccc", + "pass": "transformer-distribute-v2", "schema": "blueprinting.distributed-task" }, { - "digest": "377375178daedbd9655bdccb618eab11ba8b68f3", - "pass": "transformer-plan-work-v1", + "digest": "7549a08665566ef4187e9a37ca50d860308c8800", + "pass": "transformer-plan-work-v2", "schema": "blueprinting.portable-plan" } ], - "portable_digest": "377375178daedbd9655bdccb618eab11ba8b68f3" + "portable_digest": "7549a08665566ef4187e9a37ca50d860308c8800" }, "memory_bytes": { "calculon": 63507865600.0, diff --git a/pages/LLM_Calc/distexp.py b/pages/LLM_Calc/distexp.py index 77c31d1..2a8c164 100755 --- a/pages/LLM_Calc/distexp.py +++ b/pages/LLM_Calc/distexp.py @@ -9,7 +9,7 @@ from calculon.llm import Llm from calculon.system import System -from blueprinting import Execution, Model +from blueprinting.types import Execution, Model from blueprinting.ui import ( setup_page, setup_sidebar, diff --git a/pages/LLM_Calc/overview.py b/pages/LLM_Calc/overview.py index b7a6a2c..f622187 100755 --- a/pages/LLM_Calc/overview.py +++ b/pages/LLM_Calc/overview.py @@ -13,7 +13,7 @@ from calculon.llm import Llm from calculon.system import System -from blueprinting import Execution, Model +from blueprinting.types import Execution, Model from blueprinting.ui import ( human_readable_flops, human_readable_num, diff --git a/pyproject.toml b/pyproject.toml index c986b4b..afae894 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ Issues = "https://github.com/DeepLink-org/Blueprinting/issues" Changelog = "https://github.com/DeepLink-org/Blueprinting/releases" [project.scripts] -blueprinting = "blueprinting:__main__" +blueprinting = "blueprinting.cli:main" blueprinting-workbench = "blueprinting.workbench.nicegui_app:main" # ============================================================================ @@ -122,11 +122,16 @@ exclude = [ "/docs", "/examples", "/pages", + "/data/evidence", ] [tool.hatch.build.targets.wheel] packages = ["src/blueprinting", "src/calculon", "src/simfloat"] +[tool.hatch.build.targets.wheel.force-include] +"data/models" = "blueprinting/presets/models" +"data/systems" = "blueprinting/presets/systems" + [tool.hatch.envs.default] features = ["dev", "full"] @@ -249,7 +254,7 @@ show_missing = true # ============================================================================ [tool.mypy] -python_version = "3.8" +python_version = "3.10" warn_return_any = true warn_unused_configs = true ignore_missing_imports = true diff --git a/scripts/check_wheel_contract.py b/scripts/check_wheel_contract.py new file mode 100644 index 0000000..fbf0dc6 --- /dev/null +++ b/scripts/check_wheel_contract.py @@ -0,0 +1,44 @@ +"""Verify that the base wheel contains presets but excludes optional evidence.""" + +from __future__ import annotations + +import sys +import zipfile +from pathlib import Path + +MAX_UNCOMPRESSED_BYTES = 5 * 1024 * 1024 + + +def main(argv: list[str]) -> int: + if len(argv) != 1: + raise SystemExit("usage: check_wheel_contract.py DIST.whl") + wheel = Path(argv[0]) + with zipfile.ZipFile(wheel) as archive: + members = archive.infolist() + names = tuple(item.filename for item in members) + required_prefixes = ( + "blueprinting/presets/models/", + "blueprinting/presets/systems/", + ) + for prefix in required_prefixes: + if not any(name.startswith(prefix) and name.endswith(".json") for name in names): + raise SystemExit(f"wheel is missing JSON presets under {prefix}") + forbidden_prefixes = ( + "blueprinting/systems/", + "data/evidence/", + "blueprinting/presets/evidence/", + ) + leaked = tuple(name for name in names if name.startswith(forbidden_prefixes)) + if leaked: + raise SystemExit(f"wheel contains optional evidence: {leaked[:3]!r}") + uncompressed_bytes = sum(item.file_size for item in members) + if uncompressed_bytes > MAX_UNCOMPRESSED_BYTES: + raise SystemExit( + f"wheel expands to {uncompressed_bytes} bytes; base-wheel budget is {MAX_UNCOMPRESSED_BYTES} bytes" + ) + print(f"wheel contract ok: {wheel.name}, {len(names)} files, {uncompressed_bytes} bytes uncompressed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/src/blueprinting/__init__.py b/src/blueprinting/__init__.py old mode 100755 new mode 100644 index 3d6e07f..76b49c5 --- a/src/blueprinting/__init__.py +++ b/src/blueprinting/__init__.py @@ -1,99 +1,12 @@ -"""Blueprinting: Heterogeneous Computing and Large-Scale Distributed Computing Simulators""" +"""Blueprinting public package boundary. -import logging -import sys -from typing import List, Optional +Domain contracts live in explicit packages such as :mod:`blueprinting.workload`, +:mod:`blueprinting.mapping`, :mod:`blueprinting.system`, and +:mod:`blueprinting.synthesizer`. The package root intentionally avoids broad +re-exports so importing Blueprinting does not initialize a legacy simulation +stack or hide domain ownership. +""" -import fire -import fire.decorators -import hyperparameter as hp +from .__about__ import __version__ -from . import io -from .types import ( - CommCounter, - DType, - Execution, - Memory, - Model, - ModelComm, - ModelFlops, - ModelParams, - Network, - Processor, - System, - TensorDef, -) - -__all__ = [ - "io", - "Execution", - "Model", - "ModelParams", - "ModelFlops", - "ModelComm", - "System", - "Memory", - "Processor", - "Network", - "CommCounter", - "DType", - "TensorDef", -] - - -class LLM: - """support for large language models""" - - @fire.decorators.SetParseFns(define=lambda x: x) - def train( - model, - execution, - system, - stats=None, - peers=False, - layers=False, - define: Optional[List] = None, - ): - """analysis llm training""" - if define is None: - define = [] - print(f"blueprinting train {model}", define, type(define), len(define)) - app_json = io.read_json_file(model) - exe_json = io.read_json_file(execution) - sys_json = io.read_json_file(system) - - logger = logging.getLogger() - logger.addHandler(logging.StreamHandler(stream=sys.stdout)) - logger.setLevel("INFO") - with hp.scope(app=app_json, sys=sys_json, exe=exe_json) as ps, hp.scope(*define) as ps: - app = Model(ps.app) - Execution(ps.exe) - syst = System(ps.sys) - - # TODO: Implement blueprinting's own Llm simulator - # For now, this is a placeholder - print(f"Model: {app.hidden}x{app.num_blocks} blocks") - print(f"System: {syst.proc_mode} mode") - - if stats is not None and io.is_json_extension(stats): - # TODO: Implement stats collection - pass - - def infer(self): - """analysis llm inference""" - print("blueprinting infer") - - def megatron(self, *args, **kwargs): - from blueprinting.megatron import execute_megatron_worker - - execute_megatron_worker() - - -def __main__(): - from .patch import _ParseKeywordArgs - - fire.core.Display = lambda lines, out: print(*lines, file=out) - fire.core._ParseKeywordArgs = _ParseKeywordArgs - fire.Fire( - {"llm": LLM, "train": LLM.train, "infer": LLM.infer, "megatron": LLM.megatron} - ) +__all__ = ["__version__"] diff --git a/src/blueprinting/__main__.py b/src/blueprinting/__main__.py new file mode 100644 index 0000000..b1e3fcb --- /dev/null +++ b/src/blueprinting/__main__.py @@ -0,0 +1,5 @@ +"""Execute Blueprinting's supported command-line interface.""" + +from blueprinting.cli import main + +raise SystemExit(main()) diff --git a/src/blueprinting/analysis/__init__.py b/src/blueprinting/analysis/__init__.py index 62138cd..36efc77 100644 --- a/src/blueprinting/analysis/__init__.py +++ b/src/blueprinting/analysis/__init__.py @@ -1,4 +1,4 @@ -"""Exact workload analyses and evidence-backed cost models.""" +"""Evidence protocols, cost projections, and architecture-facing analyses.""" from .cost import ( AIConfiguratorPerformanceImporter, @@ -45,24 +45,10 @@ InferenceEvidenceQuery, InferenceEvidenceResult, ) -from .transformer_inference import ( - InferenceBlockMemoryFacts, - InferenceInvocation, - derive_transformer_inference_block, -) -from .transformer_workload import ( - BlockMemoryFacts, - EngineKind, - PhaseWork, - PrimitiveInvocation, - TrainingPhase, - derive_transformer_block, -) from .vidur import VidurProfileBaseline, VidurProfileImporter __all__ = [ "BlockEstimate", - "BlockMemoryFacts", "CalibrationMode", "AIConfiguratorPerformanceImporter", "AIConfiguratorTable", @@ -74,34 +60,26 @@ "CostResolution", "CostResolver", "CostSubject", - "EngineKind", "EstimateMatch", "EstimateMethod", "EvidenceProvenance", - "InferenceBlockMemoryFacts", "InferenceBaseline", "InferenceCostProvider", "InferenceEvidenceQuery", "InferenceEvidenceResult", - "InferenceInvocation", "InferencePhaseEstimate", "InferencePhaseMemory", "InferenceTaskEstimate", "IterationEstimate", "IterationMemory", "LatencyUnit", - "PhaseWork", "PerformanceDatabase", "PerformanceDatabaseProvider", "PerformanceRecord", - "PrimitiveInvocation", "RooflineCostProvider", "SimulatorPerformanceImporter", "TabularImportSpec", "TabularPerformanceImporter", - "TrainingPhase", - "derive_transformer_block", - "derive_transformer_inference_block", "cost_query_for_inference_task", "estimate_block", "estimate_inference_phase", diff --git a/src/blueprinting/analysis/cost/aiconfigurator.py b/src/blueprinting/analysis/cost/aiconfigurator.py index 0c303a0..6dacb94 100644 --- a/src/blueprinting/analysis/cost/aiconfigurator.py +++ b/src/blueprinting/analysis/cost/aiconfigurator.py @@ -13,8 +13,9 @@ from pathlib import Path from typing import Any -from ...synthesizer.codec import content_digest -from ...synthesizer.frozen import FrozenDict +from blueprinting.schema.codec import content_digest +from blueprinting.schema.frozen import FrozenDict + from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .importers import read_tabular_rows from .protocol import CostSubject, EstimateMethod diff --git a/src/blueprinting/analysis/cost/database.py b/src/blueprinting/analysis/cost/database.py index 8826840..e64f23c 100644 --- a/src/blueprinting/analysis/cost/database.py +++ b/src/blueprinting/analysis/cost/database.py @@ -8,8 +8,9 @@ from dataclasses import dataclass, field from functools import cached_property -from ...synthesizer.codec import canonical_dumps, canonical_loads, content_digest, record_type -from ...synthesizer.frozen import FrozenDict +from blueprinting.schema.codec import canonical_dumps, canonical_loads, content_digest, record_type +from blueprinting.schema.frozen import FrozenDict + from .protocol import ( CostEstimate, CostProvider, diff --git a/src/blueprinting/analysis/cost/importers.py b/src/blueprinting/analysis/cost/importers.py index 85c3823..fea1f7d 100644 --- a/src/blueprinting/analysis/cost/importers.py +++ b/src/blueprinting/analysis/cost/importers.py @@ -11,8 +11,9 @@ from pathlib import Path from typing import Any -from ...synthesizer.codec import content_digest -from ...synthesizer.frozen import FrozenDict +from blueprinting.schema.codec import content_digest +from blueprinting.schema.frozen import FrozenDict + from .database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .protocol import CostSubject, EstimateMethod diff --git a/src/blueprinting/analysis/cost/protocol.py b/src/blueprinting/analysis/cost/protocol.py index 2e11d46..2ee5ff6 100644 --- a/src/blueprinting/analysis/cost/protocol.py +++ b/src/blueprinting/analysis/cost/protocol.py @@ -13,8 +13,8 @@ from enum import Enum from typing import Protocol, runtime_checkable -from ...synthesizer.codec import content_digest, enum_type, record_type -from ...synthesizer.frozen import FrozenDict +from blueprinting.schema.codec import content_digest, enum_type, record_type +from blueprinting.schema.frozen import FrozenDict # Keep the legacy codec namespace as a stable serialized identity. diff --git a/src/blueprinting/analysis/cost/roofline.py b/src/blueprinting/analysis/cost/roofline.py index 726fe25..3643473 100644 --- a/src/blueprinting/analysis/cost/roofline.py +++ b/src/blueprinting/analysis/cost/roofline.py @@ -2,8 +2,9 @@ from __future__ import annotations -from ...synthesizer.codec import content_digest -from ...synthesizer.frozen import FrozenDict +from blueprinting.schema.codec import content_digest +from blueprinting.schema.frozen import FrozenDict + from ...system import SystemProfile from ..cost_model import CalibrationMode from .protocol import ( diff --git a/src/blueprinting/analysis/cost_model.py b/src/blueprinting/analysis/cost_model.py index b02115c..184f317 100644 --- a/src/blueprinting/analysis/cost_model.py +++ b/src/blueprinting/analysis/cost_model.py @@ -18,21 +18,24 @@ from dataclasses import dataclass from enum import Enum -from ..synthesizer.codec import enum_type -from ..synthesizer.ir import CollectiveKind, PortablePlanIR -from ..system import SystemProfile -from ..workload import ( +from blueprinting.mapping import ( + NetworkTierBinding, RecomputePolicy, TensorParallelCommunication, - TransformerExecutionSpec, - TransformerModelSpec, + TransformerTrainingMappingSpec, ) -from .transformer_workload import ( +from blueprinting.schema.codec import enum_type +from blueprinting.synthesizer.dialects.transformer import ( BlockMemoryFacts, EngineKind, + PhaseWork, PrimitiveInvocation, TrainingPhase, ) +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec + +from ..synthesizer.ir import CollectiveKind, PlanTask, PortablePlanIR +from ..system import SystemProfile # Codec tags are stable wire identities; the legacy namespace survives the # Python package move so existing snapshots and performance evidence still load. @@ -109,6 +112,7 @@ def _task_estimate( invocation: PrimitiveInvocation, hardware: SystemProfile, participants: int, + network_binding: NetworkTierBinding, mode: CalibrationMode, ) -> TaskEstimate: work = invocation.work @@ -127,9 +131,12 @@ def _task_estimate( local_seconds = hardware.processing_time(compute_seconds, memory_seconds) network_seconds = 0.0 if invocation.engine is EngineKind.COLLECTIVE: - if invocation.network_tier is None or invocation.collective is None: - raise ValueError("collective invocation is missing network facts") - network = hardware.networks[invocation.network_tier] + if invocation.collective is None: + raise ValueError("collective invocation is missing its collective kind") + try: + network = hardware.networks[network_binding.tensor_parallel] + except IndexError as error: + raise ValueError("tensor_parallel network tier is not defined by the bound system") from error network_seconds = network.time( invocation.collective.value, work.message_bytes, @@ -145,24 +152,72 @@ def _task_estimate( ) +def _invocation_from_plan_task(task: PlanTask) -> PrimitiveInvocation: + """Reconstruct a cost view from canonical portable workload facts.""" + + attributes = task.workload.attributes + try: + phase = TrainingPhase(attributes["phase"]) + engine = EngineKind(attributes["engine"]) + name = attributes["name"] + primitive = attributes["primitive"] + source_layer = attributes["source_layer"] + except (KeyError, ValueError) as error: + raise ValueError(f"portable training task {task.id} has invalid semantic workload metadata") from error + for field_name, value in (("name", name), ("primitive", primitive), ("source_layer", source_layer)): + if not isinstance(value, str) or not value: + raise ValueError(f"portable training task {task.id} has invalid {field_name}") + collective_value = attributes.get("collective", "") + collective = None + if engine is EngineKind.COLLECTIVE: + try: + collective = CollectiveKind(collective_value) + except ValueError as error: + raise ValueError(f"portable training task {task.id} has invalid collective metadata") from error + elif collective_value != "": + raise ValueError(f"local portable training task {task.id} carries collective metadata") + return PrimitiveInvocation( + name=name, + source_layer=source_layer, + primitive=primitive, + phase=phase, + engine=engine, + work=PhaseWork( + operations=task.workload.operations, + read_bytes=task.workload.read_bytes, + write_bytes=task.workload.write_bytes, + message_bytes=task.workload.message_bytes, + ), + collective=collective, + ) + + def estimate_block( plan: PortablePlanIR, hardware: SystemProfile, mode: CalibrationMode, + *, + network_binding: NetworkTierBinding, ) -> BlockEstimate: - execution = plan.attributes.get("execution_spec") - if not isinstance(execution, TransformerExecutionSpec): - raise TypeError("portable plan is missing TransformerExecutionSpec") + mapping = plan.attributes.get("mapping_spec") + if not isinstance(mapping, TransformerTrainingMappingSpec): + raise TypeError("portable plan is missing TransformerTrainingMappingSpec") + if not isinstance(network_binding, NetworkTierBinding): + raise TypeError("network_binding must be NetworkTierBinding") tasks = [] totals = dict.fromkeys(TrainingPhase, 0.0) tp_forward = 0.0 tp_backward = 0.0 recommunication = 0.0 for task in plan.tasks: - invocation = task.attributes.get("invocation") - if not isinstance(invocation, PrimitiveInvocation): - raise TypeError("plan task is missing PrimitiveInvocation") - estimate = _task_estimate(invocation, hardware, execution.tensor_parallel, mode) + invocation = _invocation_from_plan_task(task) + estimate = _task_estimate( + invocation, + hardware, + mapping.tensor_parallel, + network_binding, + mode, + ) tasks.append(estimate) if invocation.engine is EngineKind.COLLECTIVE: if invocation.phase is TrainingPhase.FORWARD: @@ -191,19 +246,21 @@ def estimate_block( def _iteration_memory( model: TransformerModelSpec, - execution: TransformerExecutionSpec, + workload: TransformerTrainingWorkloadSpec, + mapping: TransformerTrainingMappingSpec, block: BlockMemoryFacts, blocks_per_processor: int, ) -> IterationMemory: - memory_microbatches = min(execution.microbatch_count, execution.pipeline_parallel) - if execution.pipeline_interleaving > 1: + microbatch_count = mapping.microbatch_count(workload) + memory_microbatches = min(microbatch_count, mapping.pipeline_parallel) + if mapping.pipeline_interleaving > 1: pipeline_factor = memory_microbatches * ( - 1 + (execution.pipeline_parallel - 1) / (execution.pipeline_interleaving * execution.pipeline_parallel) + 1 + (mapping.pipeline_parallel - 1) / (mapping.pipeline_interleaving * mapping.pipeline_parallel) ) else: pipeline_factor = memory_microbatches - if execution.recompute is RecomputePolicy.FULL: + if mapping.recompute is RecomputePolicy.FULL: activation_bytes = block.activation_working checkpoint_bytes = round(blocks_per_processor * block.activation_checkpoint * pipeline_factor) else: @@ -230,37 +287,47 @@ def estimate_iteration( plan: PortablePlanIR, hardware: SystemProfile, mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, + *, + network_binding: NetworkTierBinding, ) -> IterationEstimate: """Apply an explicit 1F1B/interleaved schedule to a derived block plan.""" model = plan.attributes.get("model_spec") - execution = plan.attributes.get("execution_spec") + workload = plan.attributes.get("workload_spec") + mapping = plan.attributes.get("mapping_spec") block_memory = plan.attributes.get("block_memory") if not isinstance(model, TransformerModelSpec): raise TypeError("portable plan is missing TransformerModelSpec") - if not isinstance(execution, TransformerExecutionSpec): - raise TypeError("portable plan is missing TransformerExecutionSpec") + if not isinstance(workload, TransformerTrainingWorkloadSpec): + raise TypeError("portable plan is missing TransformerTrainingWorkloadSpec") + if not isinstance(mapping, TransformerTrainingMappingSpec): + raise TypeError("portable plan is missing TransformerTrainingMappingSpec") if not isinstance(block_memory, BlockMemoryFacts): raise TypeError("portable plan is missing BlockMemoryFacts") - if hardware.datatype != execution.datatype: - raise ValueError("system profile datatype does not match execution datatype") + if hardware.datatype != workload.datatype: + raise ValueError("system profile datatype does not match workload datatype") + if not isinstance(network_binding, NetworkTierBinding): + raise TypeError("network_binding must be NetworkTierBinding") - blocks_per_processor = math.ceil(model.block_count / execution.pipeline_parallel) - if execution.pipeline_interleaving > blocks_per_processor: + blocks_per_processor = math.ceil(model.block_count / mapping.pipeline_parallel) + if mapping.pipeline_interleaving > blocks_per_processor: raise ValueError("pipeline_interleaving cannot exceed blocks per processor") - if blocks_per_processor % execution.pipeline_interleaving: + if blocks_per_processor % mapping.pipeline_interleaving: raise ValueError("pipeline_interleaving must divide blocks per processor") - blocks_per_chunk = blocks_per_processor // execution.pipeline_interleaving - chunks_per_processor = execution.pipeline_interleaving + blocks_per_chunk = blocks_per_processor // mapping.pipeline_interleaving + chunks_per_processor = mapping.pipeline_interleaving base_blocks_per_chunk = blocks_per_chunk - 1 - block = estimate_block(plan, hardware, mode) - - activation_elements = execution.microbatch_size * model.sequence_length * model.hidden_size - if execution.tensor_parallel_communication is TensorParallelCommunication.REDUCE_SCATTER_ALL_GATHER: - activation_elements //= execution.tensor_parallel - pipeline_message = activation_elements * execution.bytes_per_element - if execution.pipeline_parallel > 1: - pipeline_network = hardware.networks[execution.pipeline_parallel_network] + block = estimate_block(plan, hardware, mode, network_binding=network_binding) + + activation_elements = workload.microbatch_size * model.sequence_length * model.hidden_size + if mapping.tensor_parallel_communication is TensorParallelCommunication.REDUCE_SCATTER_ALL_GATHER: + activation_elements //= mapping.tensor_parallel + pipeline_message = activation_elements * workload.bytes_per_element + if mapping.pipeline_parallel > 1: + try: + pipeline_network = hardware.networks[network_binding.pipeline_parallel] + except IndexError as error: + raise ValueError("pipeline_parallel network tier is not defined by the bound system") from error pipeline_point_to_point = pipeline_network.time( "p2p", pipeline_message, @@ -285,23 +352,24 @@ def estimate_iteration( chunk_time = chunk_forward + chunk_backward missing_blocks = ( - execution.pipeline_parallel - model.block_count % execution.pipeline_parallel - if model.block_count % execution.pipeline_parallel + mapping.pipeline_parallel - model.block_count % mapping.pipeline_parallel + if model.block_count % mapping.pipeline_parallel else 0 ) if base_blocks_per_chunk > 0: bubble_reduction = missing_blocks * (base_forward + edge_forward + base_backward + edge_backward) / 2 else: bubble_reduction = missing_blocks * (edge_forward + edge_backward) - bubble_chunks = execution.pipeline_parallel - 1 - if execution.microbatch_count % execution.pipeline_parallel: - shortage = execution.pipeline_parallel - execution.microbatch_count % execution.pipeline_parallel - extra_bubbles = (execution.pipeline_interleaving - 1) * shortage + microbatch_count = mapping.microbatch_count(workload) + bubble_chunks = mapping.pipeline_parallel - 1 + if microbatch_count % mapping.pipeline_parallel: + shortage = mapping.pipeline_parallel - microbatch_count % mapping.pipeline_parallel + extra_bubbles = (mapping.pipeline_interleaving - 1) * shortage else: extra_bubbles = 0 pipeline_bubble = bubble_chunks * chunk_time + extra_bubbles * chunk_time - bubble_reduction - multiplicity = blocks_per_processor * execution.microbatch_count + multiplicity = blocks_per_processor * microbatch_count forward = multiplicity * block.forward backward = multiplicity * (block.activation_gradient + block.weight_gradient) recompute = multiplicity * block.recompute @@ -309,31 +377,32 @@ def estimate_iteration( tensor_parallel = multiplicity * (block.tensor_parallel_forward + block.tensor_parallel_backward) recommunication = multiplicity * block.recommunication pipeline_parallel = ( - execution.microbatch_count * chunks_per_processor * pipeline_point_to_point * 2 - if execution.pipeline_parallel > 1 - else 0.0 + microbatch_count * chunks_per_processor * pipeline_point_to_point * 2 if mapping.pipeline_parallel > 1 else 0.0 ) data_parallel = 0.0 - if execution.data_parallel > 1: - network = hardware.networks[execution.data_parallel_network] - if execution.optimizer_sharding: + if mapping.data_parallel > 1: + try: + network = hardware.networks[network_binding.data_parallel] + except IndexError as error: + raise ValueError("data_parallel network tier is not defined by the bound system") from error + if mapping.optimizer_sharding: per_block = network.time( CollectiveKind.REDUCE_SCATTER.value, block_memory.weights, - execution.data_parallel, + mapping.data_parallel, apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, ) + network.time( CollectiveKind.ALL_GATHER.value, block_memory.weights, - execution.data_parallel, + mapping.data_parallel, apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, ) else: per_block = network.time( CollectiveKind.ALL_REDUCE.value, block_memory.weights, - execution.data_parallel, + mapping.data_parallel, apply_efficiency=mode is CalibrationMode.SYSTEM_EVIDENCE, ) data_parallel = blocks_per_processor * per_block @@ -352,7 +421,7 @@ def estimate_iteration( return IterationEstimate( mode=mode, block=block, - memory=_iteration_memory(model, execution, block_memory, blocks_per_processor), + memory=_iteration_memory(model, workload, mapping, block_memory, blocks_per_processor), forward=forward, backward=backward, optimizer=optimizer, diff --git a/src/blueprinting/analysis/inference_cost.py b/src/blueprinting/analysis/inference_cost.py index 66335af..eae82c0 100644 --- a/src/blueprinting/analysis/inference_cost.py +++ b/src/blueprinting/analysis/inference_cost.py @@ -4,16 +4,17 @@ from dataclasses import dataclass +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec +from blueprinting.schema.frozen import FrozenDict +from blueprinting.synthesizer.dialects.transformer import EngineKind, InferenceInvocation, PhaseWork +from blueprinting.workload import TransformerModelSpec + from ..synthesizer.bindings import InferencePhase -from ..synthesizer.frozen import FrozenDict from ..synthesizer.ir import CollectiveKind, PlanBuffer, PlanTask, PortablePlanIR from ..system import SystemProfile -from ..workload import TransformerInferenceExecutionSpec, TransformerModelSpec from .cost import CostQuery, CostQueryContext, CostResolver, CostSubject from .cost_model import CalibrationMode from .inference_evidence import InferenceCostProvider, InferenceEvidenceQuery -from .transformer_inference import InferenceInvocation -from .transformer_workload import EngineKind, PhaseWork @dataclass(frozen=True) @@ -64,7 +65,8 @@ def inference_evidence_query_for( invocation: InferenceInvocation, *, hardware: SystemProfile, - execution: TransformerInferenceExecutionSpec, + mapping: TransformerInferenceMappingSpec, + datatype: str, model: TransformerModelSpec, batch_size: int, query_tokens: int, @@ -85,8 +87,8 @@ def inference_evidence_query_for( batch_size=batch_size, query_tokens=query_tokens, context_tokens=context_tokens, - tensor_parallel=execution.tensor_parallel, - datatype=execution.datatype, + tensor_parallel=mapping.tensor_parallel, + datatype=datatype, ) @@ -113,7 +115,9 @@ def cost_query_for_inference_task( task: PlanTask, *, hardware: SystemProfile, - execution: TransformerInferenceExecutionSpec, + mapping: TransformerInferenceMappingSpec, + network_binding: NetworkTierBinding, + datatype: str, model: TransformerModelSpec, batch_size: int, query_tokens: int, @@ -128,7 +132,7 @@ def cost_query_for_inference_task( primitive = invocation.primitive operation = "gemm" if primitive in _GEMM_PRIMITIVES else primitive subject = CostSubject.COMMUNICATION if invocation.engine is EngineKind.COLLECTIVE else CostSubject.OPERATOR - tensor_parallel = execution.tensor_parallel + tensor_parallel = mapping.tensor_parallel dimensions: dict[str, object] = { "semantic_operation": primitive, "source_layer": invocation.source_layer, @@ -150,7 +154,7 @@ def cost_query_for_inference_task( "use_gated_mlp": False, "beam_width": 1, "window_size": 0, - "kv_cache_datatype": execution.datatype, + "kv_cache_datatype": datatype, } if primitive in _GEMM_PRIMITIVES: tokens = batch_size * query_tokens @@ -171,13 +175,13 @@ def cost_query_for_inference_task( subject=subject, operation=operation, hardware=hardware.name, - datatype=execution.datatype, + datatype=datatype, operations=task.workload.operations, read_bytes=task.workload.read_bytes, write_bytes=task.workload.write_bytes, message_bytes=task.workload.message_bytes, participants=tensor_parallel if subject is CostSubject.COMMUNICATION else 1, - network_tier=invocation.network_tier or 0, + network_tier=network_binding.tensor_parallel, engine=invocation.engine.value, hardware_revision=hardware.evidence_revision, implementation=context.implementation_for(primitive, operation), @@ -194,7 +198,9 @@ def _task_estimate( task: PlanTask, *, hardware: SystemProfile, - execution: TransformerInferenceExecutionSpec, + mapping: TransformerInferenceMappingSpec, + network_binding: NetworkTierBinding, + datatype: str, model: TransformerModelSpec, batch_size: int, query_tokens: int, @@ -220,16 +226,16 @@ def _task_estimate( ) network_seconds = 0.0 if invocation.engine is EngineKind.COLLECTIVE: - if invocation.network_tier is None or invocation.collective is None: - raise ValueError("collective invocation is missing network facts") + if invocation.collective is None: + raise ValueError("collective invocation is missing its collective kind") try: - network = hardware.networks[invocation.network_tier] + network = hardware.networks[network_binding.tensor_parallel] except IndexError as error: - raise ValueError(f"system profile does not define network tier {invocation.network_tier}") from error + raise ValueError("tensor_parallel network tier is not defined by the bound system") from error network_seconds = network.time( invocation.collective.value, work.message_bytes, - execution.tensor_parallel, + mapping.tensor_parallel, apply_efficiency=apply_efficiency, ) analytical_seconds = hardware.processing_time(compute_seconds, memory_seconds) + network_seconds @@ -245,7 +251,9 @@ def _task_estimate( cost_query_for_inference_task( task, model=model, - execution=execution, + mapping=mapping, + network_binding=network_binding, + datatype=datatype, hardware=hardware, batch_size=batch_size, query_tokens=query_tokens, @@ -266,7 +274,8 @@ def _task_estimate( inference_evidence_query_for( invocation, model=model, - execution=execution, + mapping=mapping, + datatype=datatype, hardware=hardware, batch_size=batch_size, query_tokens=query_tokens, @@ -318,18 +327,13 @@ def _invocation_from_plan_task(task: PlanTask) -> InferenceInvocation: raise ValueError(f"portable inference task {task.id} has invalid {field_name}") collective_value = attributes.get("collective", "") - network_tier_value = attributes.get("network_tier", -1) collective = None - network_tier = None if engine is EngineKind.COLLECTIVE: try: collective = CollectiveKind(collective_value) except ValueError as error: raise ValueError(f"portable inference task {task.id} has invalid collective metadata") from error - if isinstance(network_tier_value, bool) or not isinstance(network_tier_value, int) or network_tier_value < 0: - raise ValueError(f"portable inference task {task.id} has invalid network tier") - network_tier = network_tier_value - elif collective_value != "" or network_tier_value != -1: + elif collective_value != "": raise ValueError(f"local portable inference task {task.id} carries collective metadata") return InferenceInvocation( @@ -345,7 +349,6 @@ def _invocation_from_plan_task(task: PlanTask) -> InferenceInvocation: message_bytes=task.workload.message_bytes, ), collective=collective, - network_tier=network_tier, ) @@ -368,6 +371,7 @@ def estimate_inference_phase( hardware: SystemProfile, mode: CalibrationMode = CalibrationMode.SYSTEM_EVIDENCE, *, + network_binding: NetworkTierBinding, cost_provider: InferenceCostProvider | None = None, cost_resolver: CostResolver | None = None, cost_context: CostQueryContext = CostQueryContext(), @@ -375,16 +379,21 @@ def estimate_inference_phase( """Cost one prefill or decode phase point without queueing assumptions.""" model = plan.attributes.get("model_spec") - execution = plan.attributes.get("inference_execution_spec") + mapping = plan.attributes.get("inference_mapping_spec") phase = plan.attributes.get("inference_phase") + datatype = plan.attributes.get("datatype") if not isinstance(model, TransformerModelSpec): raise TypeError("portable inference plan is missing TransformerModelSpec") - if not isinstance(execution, TransformerInferenceExecutionSpec): - raise TypeError("portable inference plan is missing TransformerInferenceExecutionSpec") + if not isinstance(mapping, TransformerInferenceMappingSpec): + raise TypeError("portable inference plan is missing TransformerInferenceMappingSpec") if not isinstance(phase, InferencePhase): raise TypeError("portable inference plan is missing InferencePhase") - if hardware.datatype != execution.datatype: - raise ValueError("system profile datatype does not match inference execution datatype") + if not isinstance(datatype, str): + raise TypeError("portable inference plan is missing its datatype") + if hardware.datatype != datatype: + raise ValueError("system profile datatype does not match inference workload datatype") + if not isinstance(network_binding, NetworkTierBinding): + raise TypeError("network_binding must be NetworkTierBinding") if cost_provider is not None and cost_resolver is not None: raise ValueError("cost_provider and cost_resolver are mutually exclusive") if not isinstance(cost_context, CostQueryContext): @@ -401,7 +410,9 @@ def estimate_inference_phase( _task_estimate( task, hardware=hardware, - execution=execution, + mapping=mapping, + network_binding=network_binding, + datatype=datatype, model=model, batch_size=batch_size, query_tokens=query_tokens, @@ -417,15 +428,13 @@ def estimate_inference_phase( transformer_seconds = block_seconds * model.block_count pipeline_seconds = 0.0 pipeline_estimate = None - if execution.pipeline_parallel > 1: + if mapping.pipeline_parallel > 1: boundary_bytes = _concrete_buffer_size(next(buffer for buffer in plan.buffers if buffer.id == plan.inputs[0])) if cost_resolver is None: try: - network = hardware.networks[execution.pipeline_parallel_network] + network = hardware.networks[network_binding.pipeline_parallel] except IndexError as error: - raise ValueError( - f"system profile does not define network tier {execution.pipeline_parallel_network}" - ) from error + raise ValueError("pipeline_parallel network tier is not defined by the bound system") from error one_hop_seconds = network.time( "p2p", boundary_bytes, @@ -440,7 +449,7 @@ def estimate_inference_phase( "batch_size": batch_size, "query_tokens": query_tokens, "context_tokens": context_tokens, - "pipeline_parallel": execution.pipeline_parallel, + "pipeline_parallel": mapping.pipeline_parallel, } pipeline_dimensions = _merge_dimensions( pipeline_dimensions, @@ -451,10 +460,10 @@ def estimate_inference_phase( subject=CostSubject.COMMUNICATION, operation="p2p", hardware=hardware.name, - datatype=execution.datatype, + datatype=datatype, message_bytes=boundary_bytes, participants=2, - network_tier=execution.pipeline_parallel_network, + network_tier=network_binding.pipeline_parallel, engine=EngineKind.COLLECTIVE.value, hardware_revision=hardware.evidence_revision, implementation=cost_context.implementation_for("p2p", "p2p"), @@ -467,15 +476,15 @@ def estimate_inference_phase( ) ).estimate one_hop_seconds = pipeline_estimate.seconds - pipeline_seconds = (execution.pipeline_parallel - 1) * one_hop_seconds + pipeline_seconds = (mapping.pipeline_parallel - 1) * one_hop_seconds - blocks_per_stage = model.block_count // execution.pipeline_parallel + blocks_per_stage = model.block_count // mapping.pipeline_parallel boundary_bytes = _concrete_buffer_size(next(buffer for buffer in plan.buffers if buffer.id == plan.inputs[0])) memory = InferencePhaseMemory( weights=_semantic_buffer_size(plan, "block_weights") * blocks_per_stage, kv_cache=_semantic_buffer_size(plan, "kv_cache") * blocks_per_stage, working_upper_bound=_semantic_buffer_size(plan, "block_working_upper_bound"), - pipeline_buffers=boundary_bytes * (2 if execution.pipeline_parallel > 1 else 1), + pipeline_buffers=boundary_bytes * (2 if mapping.pipeline_parallel > 1 else 1), ) revisions = {hardware.evidence_revision: "analytical-system-profile"} for item in task_estimates: diff --git a/src/blueprinting/analysis/vidur.py b/src/blueprinting/analysis/vidur.py index 20816d5..bd5a0c4 100644 --- a/src/blueprinting/analysis/vidur.py +++ b/src/blueprinting/analysis/vidur.py @@ -13,9 +13,10 @@ import statistics from pathlib import Path +from blueprinting.schema.codec import content_digest +from blueprinting.schema.frozen import FrozenDict + from ..synthesizer.bindings import InferencePhase -from ..synthesizer.codec import content_digest -from ..synthesizer.frozen import FrozenDict from .cost.database import EvidenceProvenance, PerformanceDatabase, PerformanceRecord from .cost.protocol import CostSubject, EstimateMethod from .inference_evidence import InferenceEvidenceQuery, InferenceEvidenceResult diff --git a/src/blueprinting/application/__init__.py b/src/blueprinting/application/__init__.py index 42668c4..15dba74 100644 --- a/src/blueprinting/application/__init__.py +++ b/src/blueprinting/application/__init__.py @@ -1,17 +1,13 @@ """Framework-neutral application services for Blueprinting clients.""" from .analysis import ( - AnalysisDiagnostic, AnalysisDraft, AnalysisOutcome, AnalysisReport, BlueprintingService, - DiagnosticLevel, - IRStageReport, SweepCase, SweepReport, SweepRequest, - TaskReport, ) from .inference import ( DecodeStepReport, @@ -20,6 +16,7 @@ InferenceAnalysisReport, InferenceAnalysisService, ) +from .reporting import AnalysisDiagnostic, DiagnosticLevel, IRStageReport, TaskReport __all__ = [ "AnalysisDiagnostic", diff --git a/src/blueprinting/application/analysis.py b/src/blueprinting/application/analysis.py index 2d1be31..51c48ea 100644 --- a/src/blueprinting/application/analysis.py +++ b/src/blueprinting/application/analysis.py @@ -12,24 +12,26 @@ import time from collections.abc import Callable, Mapping from dataclasses import dataclass, replace -from enum import Enum from itertools import product from typing import TYPE_CHECKING, Any from blueprinting.analysis import CalibrationMode, estimate_iteration -from blueprinting.synthesizer.codec import content_digest +from blueprinting.mapping import NetworkTierBinding, TransformerTrainingMappingSpec +from blueprinting.schema.codec import content_digest +from blueprinting.schema.frozen import FrozenDict, freeze, thaw from blueprinting.synthesizer.errors import ( IRVerificationError, PassExecutionError, SynthesisError, ) from blueprinting.synthesizer.frontend import build_transformer_model_ir, synthesis_session_for -from blueprinting.synthesizer.frozen import FrozenDict, freeze, thaw -from blueprinting.synthesizer.ir import DistributedTaskIR, ModelIR, PortablePlanIR +from blueprinting.synthesizer.ir import PortablePlanIR from blueprinting.synthesizer.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass from blueprinting.synthesizer.passes import AnalysisStore, PassManager, PassPipeline from blueprinting.system import SystemProfile -from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec + +from .reporting import AnalysisDiagnostic, DiagnosticLevel, IRStageReport, TaskReport, stage_report LOGGER = logging.getLogger(__name__) @@ -39,34 +41,6 @@ from .inference import InferenceAnalysisDraft, InferenceAnalysisOutcome, InferenceAnalysisService -class DiagnosticLevel(Enum): - """Presentation-neutral diagnostic severity.""" - - INFO = "info" - WARNING = "warning" - ERROR = "error" - - -@dataclass(frozen=True) -class AnalysisDiagnostic: - """A stable diagnostic that can be rendered by any client.""" - - code: str - message: str - level: DiagnosticLevel = DiagnosticLevel.ERROR - path: tuple[str, ...] = () - hint: str | None = None - - def to_dict(self) -> dict[str, Any]: - return { - "code": self.code, - "message": self.message, - "level": self.level.value, - "path": list(self.path), - "hint": self.hint, - } - - @dataclass(frozen=True) class AnalysisDraft: """Immutable, client-supplied configuration before semantic validation.""" @@ -140,69 +114,31 @@ def normalized_execution(self) -> dict[str, Any]: """Make world size a derived fact instead of a second source of truth.""" data = thaw(self.execution_data) - data["num_procs"] = data["tensor_par"] * data["pipeline_par"] * data["data_par"] + data.pop("num_procs", None) + mapping = TransformerTrainingMappingSpec.from_mapping(data) + data["num_procs"] = mapping.world_size return data def with_parallelism(self, tensor_parallel: int, pipeline_parallel: int, data_parallel: int) -> AnalysisDraft: execution = thaw(self.execution_data) execution.update( { - "tensor_par": tensor_parallel, - "pipeline_par": pipeline_parallel, - "data_par": data_parallel, + "tensor_parallel": tensor_parallel, + "pipeline_parallel": pipeline_parallel, + "data_parallel": data_parallel, "num_procs": tensor_parallel * pipeline_parallel * data_parallel, } ) + for canonical, legacy in ( + ("tensor_parallel", "tensor_par"), + ("pipeline_parallel", "pipeline_par"), + ("data_parallel", "data_par"), + ): + if legacy in execution: + execution[legacy] = execution[canonical] return replace(self, execution_data=FrozenDict(execution)) -@dataclass(frozen=True) -class IRStageReport: - """One inspectable boundary in the formal derivation.""" - - stage: str - label: str - pass_name: str - schema: str - digest: str - parent_digests: tuple[str, ...] - node_count: int - value_count: int - duration_ns: int - diagnostics: tuple[AnalysisDiagnostic, ...] - snapshot_json: str - - @property - def valid(self) -> bool: - return not any(item.level is DiagnosticLevel.ERROR for item in self.diagnostics) - - -@dataclass(frozen=True) -class TaskReport: - """UI-safe work and timing facts for one portable-plan task.""" - - task_id: str - operation: str - kind: str - phase: str - engine: str - source_layer: str - dependencies: tuple[str, ...] - concurrency_group: str - operations: int - read_bytes: int - write_bytes: int - message_bytes: int - compute_seconds: float - memory_seconds: float - network_seconds: float - total_seconds: float - analytical_seconds: float = 0.0 - evidence_provider: str = "" - evidence_revision: str = "" - evidence_match: str = "" - - @dataclass(frozen=True) class AnalysisReport: """Successful, immutable result returned to an interactive client.""" @@ -319,47 +255,6 @@ def feasible_count(self) -> int: return sum(case.feasible for case in self.cases) -def _diagnostics_from_verification(ir: ModelIR | DistributedTaskIR | PortablePlanIR) -> tuple[AnalysisDiagnostic, ...]: - return tuple( - AnalysisDiagnostic( - code=item.code, - message=item.message, - level=DiagnosticLevel(item.severity.value), - path=item.path, - hint=item.hint, - ) - for item in ir.verify().diagnostics - ) - - -def _stage_report( - stage: str, - label: str, - pass_name: str, - ir: ModelIR | DistributedTaskIR | PortablePlanIR, - duration_ns: int, -) -> IRStageReport: - if isinstance(ir, ModelIR): - node_count, value_count = len(ir.operations), len(ir.values) - elif isinstance(ir, DistributedTaskIR): - node_count, value_count = len(ir.tasks), len(ir.values) - else: - node_count, value_count = len(ir.tasks), len(ir.buffers) - return IRStageReport( - stage=stage, - label=label, - pass_name=pass_name, - schema=f"{ir.header.schema_name}@{ir.header.schema_version}", - digest=ir.digest, - parent_digests=ir.header.parent_digests, - node_count=node_count, - value_count=value_count, - duration_ns=duration_ns, - diagnostics=_diagnostics_from_verification(ir), - snapshot_json=ir.to_json(), - ) - - class BlueprintingService: """Single supported orchestration entry point for Blueprinting clients.""" @@ -446,31 +341,38 @@ def _analyze(self, draft: AnalysisDraft) -> AnalysisOutcome: hardware_data = thaw(draft.hardware_data) model = TransformerModelSpec.from_mapping(draft.model_name, model_data) - execution = TransformerExecutionSpec.from_mapping(execution_data) + workload_spec = TransformerTrainingWorkloadSpec.from_mapping(execution_data) + mapping = TransformerTrainingMappingSpec.from_mapping(execution_data) + network_binding = NetworkTierBinding.from_mapping(execution_data) hardware = SystemProfile.from_mapping( draft.hardware_name, hardware_data, - datatype=execution.datatype, + datatype=workload_spec.datatype, ) frontend_started = time.perf_counter_ns() - source = build_transformer_model_ir(model, datatype=execution.datatype) + source = build_transformer_model_ir(model, datatype=workload_spec.datatype) frontend_duration = time.perf_counter_ns() - frontend_started - session = replace(synthesis_session_for(model, execution), seed=draft.seed) + session = replace(synthesis_session_for(model, workload_spec, mapping), seed=draft.seed) pipeline = self._manager.run(self._pipeline, source, session=session) plan = pipeline.ir if not isinstance(plan, PortablePlanIR): raise TypeError(f"analysis pipeline returned {type(plan).__name__}, expected PortablePlanIR") - estimate = estimate_iteration(plan, hardware, draft.calibration_mode) + estimate = estimate_iteration( + plan, + hardware, + draft.calibration_mode, + network_binding=network_binding, + ) - stages = [_stage_report("model", "模型语义", "frontend-import", source, frontend_duration)] + stages = [stage_report("model", "模型语义", "frontend-import", source, frontend_duration)] stage_metadata = ( ("distributed", "分布式任务", pipeline.checkpoints[0]), ("portable", "可移植计划", pipeline.checkpoints[1]), ) for stage, label, checkpoint in stage_metadata: stages.append( - _stage_report( + stage_report( stage, label, checkpoint.record.pass_name, @@ -537,7 +439,7 @@ def _analyze(self, draft: AnalysisDraft) -> AnalysisOutcome: } ) bottleneck = max(latency.items(), key=lambda item: item[1])[0] - total_tokens = model.sequence_length * execution.global_batch_size + total_tokens = model.sequence_length * workload_spec.global_batch_size feasible = estimate.memory.total <= hardware.memory.capacity_bytes diagnostics: tuple[AnalysisDiagnostic, ...] = () if not feasible: @@ -586,11 +488,11 @@ def _analyze(self, draft: AnalysisDraft) -> AnalysisOutcome: execution_name=draft.execution_name, hardware_name=draft.hardware_name, calibration_mode=draft.calibration_mode.value, - world_size=execution.world_size, + world_size=mapping.world_size, feasible=feasible, total_seconds=estimate.total, total_tokens_per_second=total_tokens / estimate.total, - tokens_per_second_per_device=total_tokens / estimate.total / execution.world_size, + tokens_per_second_per_device=total_tokens / estimate.total / mapping.world_size, bottleneck=bottleneck, latency=latency, memory=memory, diff --git a/src/blueprinting/application/inference.py b/src/blueprinting/application/inference.py index 8a60a7b..cb18d7d 100644 --- a/src/blueprinting/application/inference.py +++ b/src/blueprinting/application/inference.py @@ -20,31 +20,25 @@ InferencePhaseEstimate, estimate_inference_phase, ) +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec +from blueprinting.schema.codec import content_digest +from blueprinting.schema.frozen import FrozenDict, freeze, thaw from blueprinting.synthesizer.bindings import InferencePhase -from blueprinting.synthesizer.codec import content_digest from blueprinting.synthesizer.errors import IRVerificationError, PassExecutionError, SynthesisError from blueprinting.synthesizer.frontend import ( build_transformer_inference_model_ir, inference_synthesis_session_for, ) -from blueprinting.synthesizer.frozen import FrozenDict, freeze, thaw from blueprinting.synthesizer.ir import ModelIR, PortablePlanIR from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass from blueprinting.synthesizer.passes import AnalysisStore, PassCheckpoint, PassManager, PassPipeline from blueprinting.system import SystemProfile from blueprinting.workload import ( - TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, TransformerModelSpec, ) -from .analysis import ( - AnalysisDiagnostic, - DiagnosticLevel, - IRStageReport, - TaskReport, - _stage_report, -) +from .reporting import AnalysisDiagnostic, DiagnosticLevel, IRStageReport, TaskReport, stage_report LOGGER = logging.getLogger(__name__) @@ -124,9 +118,10 @@ def fingerprint(self) -> str: def normalized_execution(self) -> dict[str, Any]: data = thaw(self.execution_data) - replicas = data.get("replicas", data.get("data_par", 1)) - data["replicas"] = replicas - data["num_procs"] = data["tensor_par"] * data["pipeline_par"] * replicas + data.pop("num_procs", None) + mapping = TransformerInferenceMappingSpec.from_mapping(data) + data["replicas"] = mapping.replicas + data["num_procs"] = mapping.world_size return data @@ -307,7 +302,9 @@ def _derive_phase( self, source: ModelIR, model: TransformerModelSpec, - execution: TransformerInferenceExecutionSpec, + mapping: TransformerInferenceMappingSpec, + network_binding: NetworkTierBinding, + datatype: str, hardware: SystemProfile, draft: InferenceAnalysisDraft, *, @@ -318,10 +315,11 @@ def _derive_phase( session = replace( inference_synthesis_session_for( model, - execution, + mapping, phase=phase, batch_size=batch_size, context_tokens=context_tokens, + datatype=datatype, ), seed=draft.seed, ) @@ -333,6 +331,7 @@ def _derive_phase( plan, hardware, draft.calibration_mode, + network_binding=network_binding, cost_provider=self._cost_provider, ) return _DerivedPhase(session.fingerprint, plan, estimate, pipeline.checkpoints) @@ -343,23 +342,28 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: request_data = thaw(draft.request_data) hardware_data = thaw(draft.hardware_data) model = TransformerModelSpec.from_mapping(draft.model_name, model_data) - execution = TransformerInferenceExecutionSpec.from_mapping(execution_data) - request = TransformerInferenceRequestSpec.from_mapping(request_data) - execution.validate_model(model) + mapping = TransformerInferenceMappingSpec.from_mapping(execution_data) + network_binding = NetworkTierBinding.from_mapping(execution_data) + request = TransformerInferenceRequestSpec.from_mapping( + {**request_data, "datatype": request_data.get("datatype", execution_data.get("datatype", "float16"))} + ) + mapping.validate_model(model) request.validate_model(model) hardware = SystemProfile.from_mapping( draft.hardware_name, hardware_data, - datatype=execution.datatype, + datatype=request.datatype, ) frontend_started = time.perf_counter_ns() - source = build_transformer_inference_model_ir(model, datatype=execution.datatype) + source = build_transformer_inference_model_ir(model, datatype=request.datatype) frontend_duration = time.perf_counter_ns() - frontend_started prefill = self._derive_phase( source, model, - execution, + mapping, + network_binding, + request.datatype, hardware, draft, phase=InferencePhase.PREFILL, @@ -370,7 +374,9 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: self._derive_phase( source, model, - execution, + mapping, + network_binding, + request.datatype, hardware, draft, phase=InferencePhase.DECODE, @@ -391,15 +397,15 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: analytical_memory_fits = peak.memory.total <= hardware.memory.capacity_bytes stages = [ - _stage_report("model", "推理模型语义", "frontend-import", source, frontend_duration), - _stage_report( + stage_report("model", "推理模型语义", "frontend-import", source, frontend_duration), + stage_report( "prefill.distributed", "Prefill 分布式任务", prefill.checkpoints[0].record.pass_name, prefill.checkpoints[0].ir, prefill.checkpoints[0].record.duration_ns, ), - _stage_report( + stage_report( "prefill.portable", "Prefill 可移植计划", prefill.checkpoints[1].record.pass_name, @@ -411,14 +417,14 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: if representative is not None: stages.extend( ( - _stage_report( + stage_report( "decode.distributed", "Decode 分布式任务(最终 context)", representative.checkpoints[0].record.pass_name, representative.checkpoints[0].ir, representative.checkpoints[0].record.duration_ns, ), - _stage_report( + stage_report( "decode.portable", "Decode 可移植计划(最终 context)", representative.checkpoints[1].record.pass_name, @@ -523,7 +529,7 @@ def _analyze(self, draft: InferenceAnalysisDraft) -> InferenceAnalysisOutcome: execution_name=draft.execution_name, hardware_name=draft.hardware_name, calibration_mode=draft.calibration_mode.value, - world_size=execution.world_size, + world_size=mapping.world_size, analytical_memory_fits=analytical_memory_fits, prefill_seconds=prefill_seconds, mean_decode_step_seconds=mean_decode_step, diff --git a/src/blueprinting/application/reporting.py b/src/blueprinting/application/reporting.py new file mode 100644 index 0000000..0e97973 --- /dev/null +++ b/src/blueprinting/application/reporting.py @@ -0,0 +1,138 @@ +"""Presentation-neutral reports shared by Blueprinting application services.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from blueprinting.synthesizer.ir import DistributedTaskIR, ModelIR, PortablePlanIR + + +class DiagnosticLevel(Enum): + """Presentation-neutral diagnostic severity.""" + + INFO = "info" + WARNING = "warning" + ERROR = "error" + + +@dataclass(frozen=True) +class AnalysisDiagnostic: + """A stable diagnostic that can be rendered by any client.""" + + code: str + message: str + level: DiagnosticLevel = DiagnosticLevel.ERROR + path: tuple[str, ...] = () + hint: str | None = None + + def to_dict(self) -> dict[str, Any]: + return { + "code": self.code, + "message": self.message, + "level": self.level.value, + "path": list(self.path), + "hint": self.hint, + } + + +@dataclass(frozen=True) +class IRStageReport: + """One inspectable boundary in the formal derivation.""" + + stage: str + label: str + pass_name: str + schema: str + digest: str + parent_digests: tuple[str, ...] + node_count: int + value_count: int + duration_ns: int + diagnostics: tuple[AnalysisDiagnostic, ...] + snapshot_json: str + + @property + def valid(self) -> bool: + return not any(item.level is DiagnosticLevel.ERROR for item in self.diagnostics) + + +@dataclass(frozen=True) +class TaskReport: + """UI-safe work and timing facts for one portable-plan task.""" + + task_id: str + operation: str + kind: str + phase: str + engine: str + source_layer: str + dependencies: tuple[str, ...] + concurrency_group: str + operations: int + read_bytes: int + write_bytes: int + message_bytes: int + compute_seconds: float + memory_seconds: float + network_seconds: float + total_seconds: float + analytical_seconds: float = 0.0 + evidence_provider: str = "" + evidence_revision: str = "" + evidence_match: str = "" + + +def _diagnostics_from_verification( + ir: ModelIR | DistributedTaskIR | PortablePlanIR, +) -> tuple[AnalysisDiagnostic, ...]: + return tuple( + AnalysisDiagnostic( + code=item.code, + message=item.message, + level=DiagnosticLevel(item.severity.value), + path=item.path, + hint=item.hint, + ) + for item in ir.verify().diagnostics + ) + + +def stage_report( + stage: str, + label: str, + pass_name: str, + ir: ModelIR | DistributedTaskIR | PortablePlanIR, + duration_ns: int, +) -> IRStageReport: + """Build an inspectable report for one verified derivation boundary.""" + + if isinstance(ir, ModelIR): + node_count, value_count = len(ir.operations), len(ir.values) + elif isinstance(ir, DistributedTaskIR): + node_count, value_count = len(ir.tasks), len(ir.values) + else: + node_count, value_count = len(ir.tasks), len(ir.buffers) + return IRStageReport( + stage=stage, + label=label, + pass_name=pass_name, + schema=f"{ir.header.schema_name}@{ir.header.schema_version}", + digest=ir.digest, + parent_digests=ir.header.parent_digests, + node_count=node_count, + value_count=value_count, + duration_ns=duration_ns, + diagnostics=_diagnostics_from_verification(ir), + snapshot_json=ir.to_json(), + ) + + +__all__ = [ + "AnalysisDiagnostic", + "DiagnosticLevel", + "IRStageReport", + "TaskReport", + "stage_report", +] diff --git a/src/blueprinting/cli/__init__.py b/src/blueprinting/cli/__init__.py old mode 100755 new mode 100644 index e69de29..57cdc61 --- a/src/blueprinting/cli/__init__.py +++ b/src/blueprinting/cli/__init__.py @@ -0,0 +1,44 @@ +"""Command-line entry points for supported Blueprinting applications.""" + +from __future__ import annotations + +import argparse +from collections.abc import Sequence + +from blueprinting import __version__ + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="blueprinting", + description="Evidence-driven hardware architecture exploration", + ) + parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") + commands = parser.add_subparsers(dest="command") + commands.add_parser("version", help="print the installed Blueprinting version") + workbench = commands.add_parser("workbench", help="launch the NiceGUI architecture workbench") + workbench.add_argument("--host", default="127.0.0.1", help="interface to bind") + workbench.add_argument("--port", type=int, default=8080, help="TCP port") + workbench.add_argument("--no-open", action="store_true", help="do not open a browser automatically") + workbench.add_argument("--reload", action="store_true", help="reload when Python sources change") + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + """Dispatch supported commands without importing UI code eagerly.""" + + parser = build_parser() + args = parser.parse_args(argv) + if args.command == "version": + print(__version__) + return 0 + if args.command == "workbench": + from blueprinting.workbench.nicegui_app import run_workbench + + run_workbench(host=args.host, port=args.port, show=not args.no_open, reload=args.reload) + return 0 + parser.print_help() + return 0 + + +__all__ = ["build_parser", "main"] diff --git a/src/blueprinting/cli/llm.py b/src/blueprinting/cli/llm.py deleted file mode 100755 index b608a02..0000000 --- a/src/blueprinting/cli/llm.py +++ /dev/null @@ -1,58 +0,0 @@ -"""LLM CLI module for blueprinting.""" - -import logging -import sys -from typing import List, Optional - -import fire -import fire.decorators -import hyperparameter as hp - -from .. import io -from ..types import Execution, Model, System - - -class LLM: - """Support for large language models.""" - - @fire.decorators.SetParseFns(define=lambda x: x) - def train( - model, - execution, - system, - stats=None, - peers=False, - layers=False, - define: Optional[List] = None, - ): - """Analysis LLM training.""" - if define is None: - define = [] - print(f"blueprinting train {model}", define, type(define), len(define)) - app_json = io.read_json_file(model) - exe_json = io.read_json_file(execution) - sys_json = io.read_json_file(system) - - logger = logging.getLogger() - logger.addHandler(logging.StreamHandler(stream=sys.stdout)) - logger.setLevel("INFO") - - with hp.scope(app=app_json, sys=sys_json, exe=exe_json) as ps, hp.scope(*define) as ps: - app = Model(ps.app) - exe = Execution(ps.exe) - syst = System(ps.sys) - - # TODO: Implement blueprinting's own Llm simulator - print(f"Model: {app.hidden}x{app.num_blocks} blocks") - print( - f"Execution: TP={exe.tensor_par}, PP={exe.pipeline_par}, DP={exe.data_par}" - ) - print(f"System: {syst.proc_mode} mode") - - if stats is not None and io.is_json_extension(stats): - # TODO: Implement stats collection - pass - - if peers: - # TODO: Implement peers output - pass diff --git a/src/blueprinting/mapping/__init__.py b/src/blueprinting/mapping/__init__.py new file mode 100644 index 0000000..51e7919 --- /dev/null +++ b/src/blueprinting/mapping/__init__.py @@ -0,0 +1,17 @@ +"""Target-neutral strategies and explicit target-side mapping bindings.""" + +from .network import NetworkTierBinding +from .transformer import ( + RecomputePolicy, + TensorParallelCommunication, + TransformerInferenceMappingSpec, + TransformerTrainingMappingSpec, +) + +__all__ = [ + "NetworkTierBinding", + "RecomputePolicy", + "TensorParallelCommunication", + "TransformerInferenceMappingSpec", + "TransformerTrainingMappingSpec", +] diff --git a/src/blueprinting/mapping/network.py b/src/blueprinting/mapping/network.py new file mode 100644 index 0000000..417ba52 --- /dev/null +++ b/src/blueprinting/mapping/network.py @@ -0,0 +1,55 @@ +"""Late-bound communication placement for analytical system evaluation.""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass +from typing import Any + +from blueprinting.schema.codec import record_type + + +def _tier(value: Any, name: str) -> int: + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise ValueError(f"{name} must be a non-negative integer") + return value + + +def _aliased_tier(data: Mapping[str, Any], canonical: str, legacy: str) -> int: + if canonical in data and legacy in data and data[canonical] != data[legacy]: + raise ValueError(f"{canonical} conflicts with legacy alias {legacy}") + return data.get(canonical, data.get(legacy, 0)) + + +@record_type("blueprinting.mapping.network-tier-binding.v1") +@dataclass(frozen=True) +class NetworkTierBinding: + """Map logical parallel domains to ordered tiers of one bound system. + + This binding is intentionally absent from target-neutral workload and + portable-plan digests. Cost projection supplies it together with a concrete + :class:`~blueprinting.system.SystemProfile`. + """ + + tensor_parallel: int = 0 + pipeline_parallel: int = 0 + data_parallel: int = 0 + + def __post_init__(self) -> None: + for name in ("tensor_parallel", "pipeline_parallel", "data_parallel"): + _tier(getattr(self, name), name) + + @classmethod + def from_mapping(cls, data: Mapping[str, Any]) -> NetworkTierBinding: + return cls( + tensor_parallel=_aliased_tier(data, "tensor_parallel_network", "tensor_par_net"), + pipeline_parallel=_aliased_tier(data, "pipeline_parallel_network", "pipeline_par_net"), + data_parallel=_aliased_tier(data, "data_parallel_network", "data_par_net"), + ) + + def validate_capacity(self, tier_count: int) -> None: + if isinstance(tier_count, bool) or not isinstance(tier_count, int) or tier_count < 0: + raise ValueError("tier_count must be a non-negative integer") + for name in ("tensor_parallel", "pipeline_parallel", "data_parallel"): + if getattr(self, name) >= tier_count: + raise ValueError(f"{name} network tier is not defined by the bound system") diff --git a/src/blueprinting/mapping/transformer.py b/src/blueprinting/mapping/transformer.py new file mode 100644 index 0000000..8f1e13b --- /dev/null +++ b/src/blueprinting/mapping/transformer.py @@ -0,0 +1,166 @@ +"""Target-neutral Transformer strategy contracts.""" + +from __future__ import annotations + +from collections.abc import Mapping +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from blueprinting.schema.codec import enum_type, record_type +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec + + +def _positive_integer(value: Any, name: str) -> int: + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise ValueError(f"{name} must be a positive integer") + return value + + +_MISSING = object() + + +def _field(data: Mapping[str, Any], canonical: str, legacy: str, default: Any = _MISSING) -> Any: + if canonical in data and legacy in data and data[canonical] != data[legacy]: + raise ValueError(f"{canonical} conflicts with legacy alias {legacy}") + if canonical in data: + return data[canonical] + if legacy in data: + return data[legacy] + if default is _MISSING: + raise KeyError(canonical) + return default + + +@enum_type("compiler.transformer.recompute_policy") +class RecomputePolicy(Enum): + NONE = "none" + ATTENTION = "attn_only" + FULL = "full" + + +@enum_type("compiler.transformer.tp_communication") +class TensorParallelCommunication(Enum): + ALL_REDUCE = "ar" + REDUCE_SCATTER_ALL_GATHER = "rs_ag" + + +@record_type("blueprinting.mapping.transformer-training.v1") +@dataclass(frozen=True) +class TransformerTrainingMappingSpec: + """Target-neutral parallel and recomputation strategy for training.""" + + tensor_parallel: int + pipeline_parallel: int + data_parallel: int + recompute: RecomputePolicy + pipeline_interleaving: int + optimizer_sharding: bool + tensor_parallel_communication: TensorParallelCommunication + fused_activation: bool = False + sequence_parallel_all_gather_redo: bool = False + + def __post_init__(self) -> None: + for name in ("tensor_parallel", "pipeline_parallel", "data_parallel", "pipeline_interleaving"): + _positive_integer(getattr(self, name), name) + if not isinstance(self.recompute, RecomputePolicy): + raise TypeError("recompute must be a RecomputePolicy") + if not isinstance(self.tensor_parallel_communication, TensorParallelCommunication): + raise TypeError("tensor_parallel_communication must be TensorParallelCommunication") + if self.optimizer_sharding and self.data_parallel == 1: + raise ValueError("optimizer sharding requires data_parallel > 1") + + @property + def world_size(self) -> int: + return self.tensor_parallel * self.pipeline_parallel * self.data_parallel + + def validate_workload(self, workload: TransformerTrainingWorkloadSpec) -> None: + if not isinstance(workload, TransformerTrainingWorkloadSpec): + raise TypeError("workload must be TransformerTrainingWorkloadSpec") + if workload.global_batch_size % self.data_parallel: + raise ValueError("global_batch_size must be divisible by data_parallel") + if self.local_batch_size(workload) % workload.microbatch_size: + raise ValueError("local batch size must be divisible by microbatch_size") + + def local_batch_size(self, workload: TransformerTrainingWorkloadSpec) -> int: + return workload.global_batch_size // self.data_parallel + + def microbatch_count(self, workload: TransformerTrainingWorkloadSpec) -> int: + self.validate_workload(workload) + return self.local_batch_size(workload) // workload.microbatch_size + + @classmethod + def from_mapping(cls, data: Mapping[str, Any]) -> TransformerTrainingMappingSpec: + tensor_parallel = _field(data, "tensor_parallel", "tensor_par") + pipeline_parallel = _field(data, "pipeline_parallel", "pipeline_par") + data_parallel = _field(data, "data_parallel", "data_par") + expected_world_size = tensor_parallel * pipeline_parallel * data_parallel + if data.get("num_procs", expected_world_size) != expected_world_size: + raise ValueError("num_procs must equal tensor_parallel * pipeline_parallel * data_parallel") + if not data.get("training", True): + raise ValueError("this mapping describes training only") + if data.get("attention_type", "multihead") != "multihead": + raise ValueError("the current Transformer dialect supports multihead attention only") + if data.get("tensor_par_overlap", "none") != "none" or data.get("data_par_overlap", False): + raise ValueError("overlap requires concrete scheduling and is outside this mapping contract") + if any(data.get(name, False) for name in ("weight_offload", "activations_offload", "optimizer_offload")): + raise ValueError("offload strategies are outside this mapping contract") + return cls( + tensor_parallel=tensor_parallel, + pipeline_parallel=pipeline_parallel, + data_parallel=data_parallel, + recompute=RecomputePolicy(_field(data, "recompute", "activation_recompute")), + pipeline_interleaving=data["pipeline_interleaving"], + optimizer_sharding=data["optimizer_sharding"], + tensor_parallel_communication=TensorParallelCommunication( + _field(data, "tensor_parallel_communication", "tensor_par_comm_type") + ), + fused_activation=data.get("fused_activation", False), + sequence_parallel_all_gather_redo=data.get("seq_par_ag_redo", False), + ) + + +@record_type("blueprinting.mapping.transformer-inference.v1") +@dataclass(frozen=True) +class TransformerInferenceMappingSpec: + """Target-neutral logical mapping for an inference replica.""" + + tensor_parallel: int + pipeline_parallel: int + replicas: int + + def __post_init__(self) -> None: + for name in ("tensor_parallel", "pipeline_parallel", "replicas"): + _positive_integer(getattr(self, name), name) + + @property + def world_size(self) -> int: + return self.tensor_parallel * self.pipeline_parallel * self.replicas + + def validate_model(self, model: TransformerModelSpec) -> None: + divisibility = { + "hidden_size": model.hidden_size, + "feedforward_size": model.feedforward_size, + "attention_heads": model.attention_heads, + } + for name, value in divisibility.items(): + if value % self.tensor_parallel: + raise ValueError(f"{name} must be divisible by tensor_parallel") + if self.pipeline_parallel > model.block_count: + raise ValueError("pipeline_parallel cannot exceed block_count") + if model.block_count % self.pipeline_parallel: + raise ValueError("pipeline_parallel must divide block_count for static inference planning") + + @classmethod + def from_mapping(cls, data: Mapping[str, Any]) -> TransformerInferenceMappingSpec: + replicas = _field(data, "replicas", "data_par", 1) + tensor_parallel = _field(data, "tensor_parallel", "tensor_par") + pipeline_parallel = _field(data, "pipeline_parallel", "pipeline_par") + expected_world_size = tensor_parallel * pipeline_parallel * replicas + if data.get("num_procs", expected_world_size) != expected_world_size: + raise ValueError("num_procs must equal tensor_parallel * pipeline_parallel * replicas") + return cls( + tensor_parallel=tensor_parallel, + pipeline_parallel=pipeline_parallel, + replicas=replicas, + ) diff --git a/src/blueprinting/schema/__init__.py b/src/blueprinting/schema/__init__.py new file mode 100644 index 0000000..2f409af --- /dev/null +++ b/src/blueprinting/schema/__init__.py @@ -0,0 +1,24 @@ +"""Canonical immutable-value and serialization primitives. + +This package is deliberately domain-free. Workload, system, synthesis, and +analysis contracts may depend on it; it must not import any of those packages. +""" + +from blueprinting.schema.codec import canonical_dumps, canonical_loads, content_digest, enum_type, record_type +from blueprinting.schema.frozen import EMPTY_MAP, FrozenDict, freeze, thaw + +from .errors import SchemaError, SerializationError + +__all__ = [ + "EMPTY_MAP", + "FrozenDict", + "SchemaError", + "SerializationError", + "canonical_dumps", + "canonical_loads", + "content_digest", + "enum_type", + "freeze", + "record_type", + "thaw", +] diff --git a/src/blueprinting/synthesizer/codec.py b/src/blueprinting/schema/codec.py similarity index 99% rename from src/blueprinting/synthesizer/codec.py rename to src/blueprinting/schema/codec.py index 9645ce1..b524107 100644 --- a/src/blueprinting/synthesizer/codec.py +++ b/src/blueprinting/schema/codec.py @@ -1,4 +1,4 @@ -"""Closed-world canonical codec used by formal synthesis snapshots. +"""Closed-world canonical codec shared by Blueprinting contracts. The decoder only constructs explicitly registered record and enum types. It never imports a class named by an input payload, which keeps IR loading diff --git a/src/blueprinting/schema/errors.py b/src/blueprinting/schema/errors.py new file mode 100644 index 0000000..b7c38fa --- /dev/null +++ b/src/blueprinting/schema/errors.py @@ -0,0 +1,9 @@ +"""Failures raised by canonical schema primitives.""" + + +class SchemaError(Exception): + """Base class for immutable-schema and serialization failures.""" + + +class SerializationError(SchemaError): + """Raised when canonical serialization or deserialization fails.""" diff --git a/src/blueprinting/synthesizer/frozen.py b/src/blueprinting/schema/frozen.py similarity index 91% rename from src/blueprinting/synthesizer/frozen.py rename to src/blueprinting/schema/frozen.py index 285f665..8bf1e9a 100644 --- a/src/blueprinting/synthesizer/frozen.py +++ b/src/blueprinting/schema/frozen.py @@ -1,4 +1,4 @@ -"""Small immutable containers used at IR boundaries.""" +"""Small immutable containers used at typed contract boundaries.""" from __future__ import annotations @@ -9,9 +9,8 @@ def freeze(value: Any) -> Any: """Recursively freeze JSON-like extension data. - Registered immutable synthesis records pass through unchanged. Mutable - mappings and sequences are copied so callers cannot mutate an IR snapshot - through an alias retained outside the synthesizer. + Registered immutable records pass through unchanged. Mutable mappings and + sequences are copied so callers cannot mutate a snapshot through an alias. """ if isinstance(value, FrozenDict): diff --git a/src/blueprinting/synthesizer/__init__.py b/src/blueprinting/synthesizer/__init__.py index 2466f7f..1d3097b 100644 --- a/src/blueprinting/synthesizer/__init__.py +++ b/src/blueprinting/synthesizer/__init__.py @@ -12,7 +12,6 @@ WorkloadBinding, WorkloadMode, ) -from .codec import canonical_dumps, canonical_loads, content_digest from .errors import ( BindingError, Diagnostic, @@ -20,12 +19,10 @@ MissingAnalysisError, MissingBindingError, PassContractError, - SerializationError, SynthesisError, VerificationReport, ) from .expr import ExprOp, ScalarExpr, Symbol, ceil_div, free_symbols, maximum, minimum, substitute -from .frozen import FrozenDict, freeze, thaw from .ids import ( BufferId, CommandId, @@ -54,7 +51,6 @@ "DeviceId", "Diagnostic", "ExprOp", - "FrozenDict", "IRVerificationError", "InferencePhase", "InstructionId", @@ -68,7 +64,6 @@ "QueueId", "TokenId", "ScalarExpr", - "SerializationError", "StrategyBinding", "Symbol", "TargetProfile", @@ -77,14 +72,9 @@ "VerificationReport", "WorkloadBinding", "WorkloadMode", - "canonical_dumps", - "canonical_loads", "ceil_div", - "content_digest", "free_symbols", - "freeze", "maximum", "minimum", "substitute", - "thaw", ] diff --git a/src/blueprinting/synthesizer/axes.py b/src/blueprinting/synthesizer/axes.py index 14b215f..f92673d 100644 --- a/src/blueprinting/synthesizer/axes.py +++ b/src/blueprinting/synthesizer/axes.py @@ -2,7 +2,7 @@ from enum import Enum -from .codec import enum_type +from blueprinting.schema.codec import enum_type @enum_type("compiler.binding_axis") diff --git a/src/blueprinting/synthesizer/bindings.py b/src/blueprinting/synthesizer/bindings.py index 6c40f70..00167e3 100644 --- a/src/blueprinting/synthesizer/bindings.py +++ b/src/blueprinting/synthesizer/bindings.py @@ -7,11 +7,12 @@ from enum import Enum from typing import Any +from blueprinting.schema.codec import content_digest, enum_type, record_type +from blueprinting.schema.frozen import FrozenDict, freeze + from .axes import BindingAxis -from .codec import content_digest, enum_type, record_type from .errors import BindingError, MissingBindingError from .expr import Scalar, ScalarExpr, Symbol, free_symbols -from .frozen import FrozenDict, freeze def _frozen_map(value: Any) -> FrozenDict: diff --git a/src/blueprinting/synthesizer/dialects/__init__.py b/src/blueprinting/synthesizer/dialects/__init__.py new file mode 100644 index 0000000..d550bbb --- /dev/null +++ b/src/blueprinting/synthesizer/dialects/__init__.py @@ -0,0 +1 @@ +"""Workload-family dialects implemented by the formal synthesizer.""" diff --git a/src/blueprinting/synthesizer/dialects/transformer/__init__.py b/src/blueprinting/synthesizer/dialects/transformer/__init__.py new file mode 100644 index 0000000..6d81015 --- /dev/null +++ b/src/blueprinting/synthesizer/dialects/transformer/__init__.py @@ -0,0 +1,22 @@ +"""Exact Transformer work facts and their target-neutral derivation.""" + +from .common import EngineKind, PhaseWork +from .inference import InferenceBlockMemoryFacts, InferenceInvocation, derive_transformer_inference_block +from .training import ( + BlockMemoryFacts, + PrimitiveInvocation, + TrainingPhase, + derive_transformer_block, +) + +__all__ = [ + "BlockMemoryFacts", + "EngineKind", + "InferenceBlockMemoryFacts", + "InferenceInvocation", + "PhaseWork", + "PrimitiveInvocation", + "TrainingPhase", + "derive_transformer_block", + "derive_transformer_inference_block", +] diff --git a/src/blueprinting/synthesizer/dialects/transformer/common.py b/src/blueprinting/synthesizer/dialects/transformer/common.py new file mode 100644 index 0000000..fee22e6 --- /dev/null +++ b/src/blueprinting/synthesizer/dialects/transformer/common.py @@ -0,0 +1,42 @@ +"""Shared target-neutral work primitives for Transformer dialects.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum + +from blueprinting.schema.codec import enum_type, record_type + +# Codec tags retain their legacy namespace as stable serialized identities. + + +@enum_type("compiler.analysis.engine_kind") +class EngineKind(Enum): + MATRIX = "matrix" + VECTOR = "vector" + COLLECTIVE = "collective" + + +@record_type("compiler.analysis.phase_work.v1") +@dataclass(frozen=True) +class PhaseWork: + """Exact work for one invocation, before target binding.""" + + operations: int = 0 + read_bytes: int = 0 + write_bytes: int = 0 + message_bytes: int = 0 + + def __post_init__(self) -> None: + for field_name in ("operations", "read_bytes", "write_bytes", "message_bytes"): + value = getattr(self, field_name) + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise ValueError(f"{field_name} must be a non-negative integer") + + @property + def memory_bytes(self) -> int: + return self.read_bytes + self.write_bytes + + @property + def is_empty(self) -> bool: + return self.operations == 0 and self.memory_bytes == 0 and self.message_bytes == 0 diff --git a/src/blueprinting/analysis/transformer_inference.py b/src/blueprinting/synthesizer/dialects/transformer/inference.py similarity index 92% rename from src/blueprinting/analysis/transformer_inference.py rename to src/blueprinting/synthesizer/dialects/transformer/inference.py index 146cac3..474dfd3 100644 --- a/src/blueprinting/analysis/transformer_inference.py +++ b/src/blueprinting/synthesizer/dialects/transformer/inference.py @@ -10,16 +10,18 @@ from dataclasses import dataclass -from ..synthesizer.bindings import InferencePhase -from ..synthesizer.codec import record_type -from ..synthesizer.ir import CollectiveKind -from ..workload import TransformerInferenceExecutionSpec, TransformerModelSpec -from .transformer_workload import EngineKind, PhaseWork +from blueprinting.mapping import TransformerInferenceMappingSpec +from blueprinting.schema.codec import record_type +from blueprinting.workload import TransformerModelSpec + +from ...bindings import InferencePhase +from ...ir import CollectiveKind +from .common import EngineKind, PhaseWork # Keep the legacy codec namespace as a stable serialized identity. -@record_type("compiler.analysis.inference_invocation.v1") +@record_type("blueprinting.transformer.inference-invocation.v2") @dataclass(frozen=True) class InferenceInvocation: """One target-neutral component invocation for a single decoder block.""" @@ -31,7 +33,6 @@ class InferenceInvocation: engine: EngineKind work: PhaseWork collective: CollectiveKind | None = None - network_tier: int | None = None def __post_init__(self) -> None: for field_name in ("name", "source_layer", "primitive"): @@ -45,9 +46,9 @@ def __post_init__(self) -> None: if not isinstance(self.work, PhaseWork): raise TypeError("work must be PhaseWork") if self.engine is EngineKind.COLLECTIVE: - if self.collective is None or self.network_tier is None: - raise ValueError("collective invocations require kind and network tier") - elif self.collective is not None or self.network_tier is not None: + if self.collective is None: + raise ValueError("collective invocations require a collective kind") + elif self.collective is not None: raise ValueError("local invocations cannot carry collective metadata") @@ -74,11 +75,12 @@ def _work(*, operations: int = 0, read: int = 0, write: int = 0, message: int = def derive_transformer_inference_block( model: TransformerModelSpec, - execution: TransformerInferenceExecutionSpec, + mapping: TransformerInferenceMappingSpec, *, phase: InferencePhase, batch_size: int, context_tokens: int, + datatype: str, ) -> tuple[tuple[InferenceInvocation, ...], InferenceBlockMemoryFacts]: """Derive exact work for one local block at one inference phase point. @@ -87,23 +89,26 @@ def derive_transformer_inference_block( the newly appended token in the context. """ - execution.validate_model(model) + mapping.validate_model(model) if not isinstance(phase, InferencePhase): raise TypeError("phase must be InferencePhase") for name, value in (("batch_size", batch_size), ("context_tokens", context_tokens)): if isinstance(value, bool) or not isinstance(value, int) or value <= 0: raise ValueError(f"{name} must be a positive integer") + try: + element_bytes = {"float8": 1, "float16": 2, "bfloat16": 2, "float32": 4}[datatype] + except KeyError as error: + raise ValueError(f"unsupported datatype: {datatype!r}") from error b = batch_size q = context_tokens if phase is InferencePhase.PREFILL else 1 c = context_tokens h = model.hidden_size f = model.feedforward_size - tp = execution.tensor_parallel + tp = mapping.tensor_parallel heads = model.attention_heads // tp local_h = h // tp local_f = f // tp - element_bytes = execution.bytes_per_element token_elements = b * q * h local_token_elements = b * q * local_h score_elements = b * heads * q * c @@ -140,7 +145,6 @@ def all_reduce(name: str, source_layer: str) -> None: engine=EngineKind.COLLECTIVE, work=_work(message=token_elements * element_bytes), collective=CollectiveKind.ALL_REDUCE, - network_tier=execution.tensor_parallel_network, ) ) diff --git a/src/blueprinting/analysis/transformer_workload.py b/src/blueprinting/synthesizer/dialects/transformer/training.py similarity index 89% rename from src/blueprinting/analysis/transformer_workload.py rename to src/blueprinting/synthesizer/dialects/transformer/training.py index a2ba2f1..0d4dafd 100644 --- a/src/blueprinting/analysis/transformer_workload.py +++ b/src/blueprinting/synthesizer/dialects/transformer/training.py @@ -11,23 +11,14 @@ from dataclasses import dataclass, replace from enum import Enum -from ..synthesizer.codec import enum_type, record_type -from ..synthesizer.ir import CollectiveKind -from ..workload import ( - RecomputePolicy, - TensorParallelCommunication, - TransformerExecutionSpec, - TransformerModelSpec, -) - -# Keep the legacy codec namespace as a stable serialized identity. +from blueprinting.mapping import RecomputePolicy, TensorParallelCommunication, TransformerTrainingMappingSpec +from blueprinting.schema.codec import enum_type, record_type +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec +from ...ir import CollectiveKind +from .common import EngineKind, PhaseWork -@enum_type("compiler.analysis.engine_kind") -class EngineKind(Enum): - MATRIX = "matrix" - VECTOR = "vector" - COLLECTIVE = "collective" +# Keep the legacy codec namespace as a stable serialized identity. @enum_type("compiler.analysis.training_phase") @@ -40,32 +31,7 @@ class TrainingPhase(Enum): RECOMMUNICATION = "recommunication" -@record_type("compiler.analysis.phase_work.v1") -@dataclass(frozen=True) -class PhaseWork: - """Exact work for one invocation, before target binding.""" - - operations: int = 0 - read_bytes: int = 0 - write_bytes: int = 0 - message_bytes: int = 0 - - def __post_init__(self) -> None: - for field_name in ("operations", "read_bytes", "write_bytes", "message_bytes"): - value = getattr(self, field_name) - if isinstance(value, bool) or not isinstance(value, int) or value < 0: - raise ValueError(f"{field_name} must be a non-negative integer") - - @property - def memory_bytes(self) -> int: - return self.read_bytes + self.write_bytes - - @property - def is_empty(self) -> bool: - return self.operations == 0 and self.memory_bytes == 0 and self.message_bytes == 0 - - -@record_type("compiler.analysis.primitive_invocation.v1") +@record_type("blueprinting.transformer.primitive-invocation.v2") @dataclass(frozen=True) class PrimitiveInvocation: """One structurally selected operation in a local block program.""" @@ -77,7 +43,6 @@ class PrimitiveInvocation: engine: EngineKind work: PhaseWork collective: CollectiveKind | None = None - network_tier: int | None = None def __post_init__(self) -> None: for field_name in ("name", "source_layer", "primitive"): @@ -90,9 +55,9 @@ def __post_init__(self) -> None: if not isinstance(self.work, PhaseWork): raise TypeError("work must be PhaseWork") if self.engine is EngineKind.COLLECTIVE: - if self.collective is None or self.network_tier is None: - raise ValueError("collective invocations require kind and network tier") - elif self.collective is not None or self.network_tier is not None: + if self.collective is None: + raise ValueError("collective invocations require a collective kind") + elif self.collective is not None: raise ValueError("local invocations cannot carry collective metadata") @@ -121,7 +86,48 @@ def __post_init__(self) -> None: class _Communication: kind: CollectiveKind work: PhaseWork - network_tier: int + + +@dataclass(frozen=True) +class _TrainingContext: + workload: TransformerTrainingWorkloadSpec + mapping: TransformerTrainingMappingSpec + + @property + def bytes_per_element(self) -> int: + return self.workload.bytes_per_element + + @property + def microbatch_size(self) -> int: + return self.workload.microbatch_size + + @property + def tensor_parallel(self) -> int: + return self.mapping.tensor_parallel + + @property + def data_parallel(self) -> int: + return self.mapping.data_parallel + + @property + def optimizer_sharding(self) -> bool: + return self.mapping.optimizer_sharding + + @property + def fused_activation(self) -> bool: + return self.mapping.fused_activation + + @property + def recompute(self) -> RecomputePolicy: + return self.mapping.recompute + + @property + def sequence_parallel_all_gather_redo(self) -> bool: + return self.mapping.sequence_parallel_all_gather_redo + + @property + def tensor_parallel_communication(self) -> TensorParallelCommunication: + return self.mapping.tensor_parallel_communication @dataclass(frozen=True) @@ -231,7 +237,7 @@ def _linear( m: int, n: int, k: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, activation_reused: bool = False, @@ -266,7 +272,7 @@ def _batch_matmul( m: int, n: int, k: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, output_stored: bool = True, @@ -291,7 +297,7 @@ def _layer_norm( name: str, elements: int, hidden: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, ) -> _Layer: @@ -321,7 +327,7 @@ def _fork( name: str, elements: int, users: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, activation_stored: bool = True, @@ -348,7 +354,7 @@ def _fork( def _softmax( name: str, elements: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, output_stored: bool, @@ -374,7 +380,7 @@ def _softmax( def _dropout( name: str, elements: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, activation_stored: bool = True, @@ -404,7 +410,7 @@ def _dropout( def _gelu( name: str, elements: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, ) -> _Layer: @@ -430,7 +436,7 @@ def _gelu( def _residual( name: str, elements: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, recompute: bool, ) -> _Layer: @@ -454,7 +460,7 @@ def _residual( def _tp_communication( name: str, elements: int, - execution: TransformerExecutionSpec, + execution: _TrainingContext, *, conjugate: bool, recompute: bool, @@ -477,39 +483,19 @@ def _tp_communication( gradient_kind = CollectiveKind.ALL_GATHER if conjugate else CollectiveKind.REDUCE_SCATTER forward_ops = reduction_operations if conjugate else 0 gradient_ops = 0 if conjugate else reduction_operations - forward_comm = _Communication( - forward_kind, - _work(forward_ops, memory, 0, message), - execution.tensor_parallel_network, - ) - gradient_comm = _Communication( - gradient_kind, - _work(gradient_ops, memory, 0, message), - execution.tensor_parallel_network, - ) + forward_comm = _Communication(forward_kind, _work(forward_ops, memory, 0, message)) + gradient_comm = _Communication(gradient_kind, _work(gradient_ops, memory, 0, message)) elif conjugate: - forward_comm = _Communication( - CollectiveKind.ALL_REDUCE, - _work(reduction_operations, memory, 0, message), - execution.tensor_parallel_network, - ) + forward_comm = _Communication(CollectiveKind.ALL_REDUCE, _work(reduction_operations, memory, 0, message)) else: - gradient_comm = _Communication( - CollectiveKind.ALL_REDUCE, - _work(reduction_operations, memory, 0, message), - execution.tensor_parallel_network, - ) + gradient_comm = _Communication(CollectiveKind.ALL_REDUCE, _work(reduction_operations, memory, 0, message)) if recommunicate and (split or conjugate): if split: kind = CollectiveKind.REDUCE_SCATTER if conjugate else CollectiveKind.ALL_GATHER else: kind = CollectiveKind.ALL_REDUCE - recompute_comm = _Communication( - kind, - _work(message=message), - execution.tensor_parallel_network, - ) + recompute_comm = _Communication(kind, _work(message=message)) if split: activation_bytes = message // tp @@ -545,7 +531,7 @@ def _tp_communication( ) -def _build_layers(model: TransformerModelSpec, execution: TransformerExecutionSpec) -> tuple[_Layer, ...]: +def _build_layers(model: TransformerModelSpec, execution: _TrainingContext) -> tuple[_Layer, ...]: tp = execution.tensor_parallel if model.hidden_size % tp or model.feedforward_size % tp or model.attention_heads % tp: raise ValueError("hidden, feedforward, and attention heads must divide tensor parallelism") @@ -735,16 +721,18 @@ def _communication_invocation( engine=EngineKind.COLLECTIVE, work=communication.work, collective=communication.kind, - network_tier=communication.network_tier, ) def derive_transformer_block( model: TransformerModelSpec, - execution: TransformerExecutionSpec, + workload: TransformerTrainingWorkloadSpec, + mapping: TransformerTrainingMappingSpec, ) -> tuple[tuple[PrimitiveInvocation, ...], BlockMemoryFacts]: """Decompose a block into explicit forward/recompute/backward work.""" + mapping.validate_workload(workload) + execution = _TrainingContext(workload, mapping) layers = _build_layers(model, execution) invocations = [] diff --git a/src/blueprinting/synthesizer/errors.py b/src/blueprinting/synthesizer/errors.py index d2add59..6ce9c05 100644 --- a/src/blueprinting/synthesizer/errors.py +++ b/src/blueprinting/synthesizer/errors.py @@ -16,10 +16,6 @@ class SynthesisError(Exception): """Base class for formal-synthesis failures.""" -class SerializationError(SynthesisError): - """Raised when canonical serialization or deserialization fails.""" - - class InvalidIdError(SynthesisError, ValueError): """Raised when a stable synthesis identifier is malformed.""" diff --git a/src/blueprinting/synthesizer/expr.py b/src/blueprinting/synthesizer/expr.py index f0c1c33..1a40fcb 100644 --- a/src/blueprinting/synthesizer/expr.py +++ b/src/blueprinting/synthesizer/expr.py @@ -14,8 +14,9 @@ from numbers import Real from typing import Any, Union +from blueprinting.schema.codec import enum_type, record_type + from .axes import BindingAxis -from .codec import enum_type, record_type from .errors import BindingError Number = int | float diff --git a/src/blueprinting/synthesizer/frontend/transformer.py b/src/blueprinting/synthesizer/frontend/transformer.py index 0467578..fbfe7c1 100644 --- a/src/blueprinting/synthesizer/frontend/transformer.py +++ b/src/blueprinting/synthesizer/frontend/transformer.py @@ -6,12 +6,13 @@ from __future__ import annotations -from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec +from blueprinting.mapping import TransformerTrainingMappingSpec +from blueprinting.schema.frozen import FrozenDict +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec from ..axes import BindingAxis from ..bindings import BindingSet, StrategyBinding, WorkloadBinding, WorkloadMode from ..expr import Symbol -from ..frozen import FrozenDict from ..ids import Lineage, NodeId, ValueId from ..ir import ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole from ..session import SynthesisSession @@ -60,25 +61,28 @@ def build_transformer_model_ir(model: TransformerModelSpec, *, datatype: str = " def synthesis_session_for( model: TransformerModelSpec, - execution: TransformerExecutionSpec, + workload_spec: TransformerTrainingWorkloadSpec, + mapping: TransformerTrainingMappingSpec, ) -> SynthesisSession: """Create the explicit session consumed by Transformer lowering passes.""" + mapping.validate_workload(workload_spec) workload = WorkloadBinding( WorkloadMode.TRAINING, - batch_size=execution.microbatch_size, + batch_size=workload_spec.microbatch_size, sequence_length=model.sequence_length, - micro_batches=execution.microbatch_count, + micro_batches=mapping.microbatch_count(workload_spec), + attributes=FrozenDict({"workload_spec": workload_spec}), ) strategy = StrategyBinding( - tensor_parallel=execution.tensor_parallel, - pipeline_parallel=execution.pipeline_parallel, - data_parallel=execution.data_parallel, - recompute_policy=execution.recompute.value, - pipeline_policy=f"1f1b-interleaved-{execution.pipeline_interleaving}", - attributes=FrozenDict({"execution_spec": execution}), + tensor_parallel=mapping.tensor_parallel, + pipeline_parallel=mapping.pipeline_parallel, + data_parallel=mapping.data_parallel, + recompute_policy=mapping.recompute.value, + pipeline_policy=f"1f1b-interleaved-{mapping.pipeline_interleaving}", + attributes=FrozenDict({"mapping_spec": mapping}), ) return SynthesisSession( bindings=BindingSet(workload=workload, strategy=strategy), - features=frozenset({"transformer-training-analysis-v1"}), + features=frozenset({"transformer-training-analysis-v2"}), ) diff --git a/src/blueprinting/synthesizer/frontend/transformer_inference.py b/src/blueprinting/synthesizer/frontend/transformer_inference.py index c6f6728..30e09a5 100644 --- a/src/blueprinting/synthesizer/frontend/transformer_inference.py +++ b/src/blueprinting/synthesizer/frontend/transformer_inference.py @@ -4,12 +4,13 @@ from typing import Any -from blueprinting.workload import TransformerInferenceExecutionSpec, TransformerModelSpec +from blueprinting.mapping import TransformerInferenceMappingSpec +from blueprinting.schema.frozen import FrozenDict +from blueprinting.workload import TransformerModelSpec from ..axes import BindingAxis from ..bindings import BindingSet, InferencePhase, StrategyBinding, WorkloadBinding, WorkloadMode from ..expr import Symbol -from ..frozen import FrozenDict from ..ids import Lineage, NodeId, ValueId from ..ir import Effect, EffectKind, ModelIR, ModelOperation, ModelValue, OperationName, TensorType, ValueRole from ..session import SynthesisSession @@ -86,17 +87,20 @@ def build_transformer_inference_model_ir( def inference_synthesis_session_for( model: TransformerModelSpec, - execution: TransformerInferenceExecutionSpec, + mapping: TransformerInferenceMappingSpec, *, phase: InferencePhase, batch_size: int, context_tokens: int, + datatype: str = "float16", ) -> SynthesisSession: """Create an explicit phase binding for static inference specialization.""" - execution.validate_model(model) + mapping.validate_model(model) _positive_integer(batch_size, "batch_size") _positive_integer(context_tokens, "context_tokens") + if datatype not in _SUPPORTED_DATATYPES: + raise ValueError(f"unsupported datatype: {datatype!r}") if not isinstance(phase, InferencePhase): raise TypeError("phase must be InferencePhase") query_tokens = context_tokens if phase is InferencePhase.PREFILL else 1 @@ -109,18 +113,19 @@ def inference_synthesis_session_for( { "query_tokens": query_tokens, "context_tokens": context_tokens, + "datatype": datatype, } ), ) strategy = StrategyBinding( - tensor_parallel=execution.tensor_parallel, - pipeline_parallel=execution.pipeline_parallel, - data_parallel=execution.replicas, + tensor_parallel=mapping.tensor_parallel, + pipeline_parallel=mapping.pipeline_parallel, + data_parallel=mapping.replicas, recompute_policy="none", pipeline_policy="static-inference", - attributes=FrozenDict({"inference_execution_spec": execution}), + attributes=FrozenDict({"inference_mapping_spec": mapping}), ) return SynthesisSession( bindings=BindingSet(workload=workload, strategy=strategy), - features=frozenset({"transformer-inference-analysis-v1", f"inference-{phase.value}"}), + features=frozenset({"transformer-inference-analysis-v2", f"inference-{phase.value}"}), ) diff --git a/src/blueprinting/synthesizer/ids.py b/src/blueprinting/synthesizer/ids.py index e4dbc73..6cd1c76 100644 --- a/src/blueprinting/synthesizer/ids.py +++ b/src/blueprinting/synthesizer/ids.py @@ -10,7 +10,8 @@ from enum import Enum from typing import Any, ClassVar, TypeVar -from .codec import canonical_dumps, enum_type, record_type +from blueprinting.schema.codec import canonical_dumps, enum_type, record_type + from .errors import InvalidIdError _ID_RE = re.compile(r"^[0-9a-f]{32}$") diff --git a/src/blueprinting/synthesizer/ir/common.py b/src/blueprinting/synthesizer/ir/common.py index 0b97b11..73b09e7 100644 --- a/src/blueprinting/synthesizer/ir/common.py +++ b/src/blueprinting/synthesizer/ir/common.py @@ -15,10 +15,12 @@ from enum import Enum from typing import Any, ClassVar, TypeVar -from ..codec import canonical_dumps, canonical_loads, content_digest, enum_type, record_type -from ..errors import DiagnosticBag, SerializationError, VerificationReport +from blueprinting.schema.codec import canonical_dumps, canonical_loads, content_digest, enum_type, record_type +from blueprinting.schema.errors import SerializationError +from blueprinting.schema.frozen import FrozenDict, freeze + +from ..errors import DiagnosticBag, VerificationReport from ..expr import Scalar, ScalarExpr, Symbol -from ..frozen import FrozenDict, freeze from ..ids import StableId _NAME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9_.-]*$") diff --git a/src/blueprinting/synthesizer/ir/concrete_plan.py b/src/blueprinting/synthesizer/ir/concrete_plan.py index ea7944d..ff7a381 100644 --- a/src/blueprinting/synthesizer/ir/concrete_plan.py +++ b/src/blueprinting/synthesizer/ir/concrete_plan.py @@ -6,9 +6,10 @@ from enum import Enum from typing import ClassVar -from ..codec import enum_type, record_type +from blueprinting.schema.codec import enum_type, record_type +from blueprinting.schema.frozen import FrozenDict + from ..errors import DiagnosticBag, VerificationReport -from ..frozen import FrozenDict from ..ids import ( BufferId, CommandId, diff --git a/src/blueprinting/synthesizer/ir/distributed.py b/src/blueprinting/synthesizer/ir/distributed.py index b8c6060..961e7cf 100644 --- a/src/blueprinting/synthesizer/ir/distributed.py +++ b/src/blueprinting/synthesizer/ir/distributed.py @@ -6,10 +6,11 @@ from enum import Enum from typing import ClassVar -from ..codec import enum_type, record_type +from blueprinting.schema.codec import enum_type, record_type +from blueprinting.schema.frozen import FrozenDict + from ..errors import DiagnosticBag, VerificationReport from ..expr import Scalar -from ..frozen import FrozenDict from ..ids import Lineage, NodeId, ValueId from .common import ( CanonicalIRMixin, diff --git a/src/blueprinting/synthesizer/ir/machine.py b/src/blueprinting/synthesizer/ir/machine.py index 34eab5e..c1ca4be 100644 --- a/src/blueprinting/synthesizer/ir/machine.py +++ b/src/blueprinting/synthesizer/ir/machine.py @@ -6,9 +6,10 @@ from enum import Enum from typing import ClassVar -from ..codec import enum_type, record_type +from blueprinting.schema.codec import enum_type, record_type +from blueprinting.schema.frozen import FrozenDict + from ..errors import DiagnosticBag, VerificationReport -from ..frozen import FrozenDict from ..ids import CommandId, InstructionId, Lineage from .common import ( CanonicalIRMixin, diff --git a/src/blueprinting/synthesizer/ir/model.py b/src/blueprinting/synthesizer/ir/model.py index 63f73a3..3ba8a11 100644 --- a/src/blueprinting/synthesizer/ir/model.py +++ b/src/blueprinting/synthesizer/ir/model.py @@ -6,9 +6,10 @@ from enum import Enum from typing import ClassVar -from ..codec import enum_type, record_type +from blueprinting.schema.codec import enum_type, record_type +from blueprinting.schema.frozen import FrozenDict + from ..errors import DiagnosticBag, VerificationReport -from ..frozen import FrozenDict from ..ids import Lineage, NodeId, ValueId from .common import ( CanonicalIRMixin, diff --git a/src/blueprinting/synthesizer/ir/portable_plan.py b/src/blueprinting/synthesizer/ir/portable_plan.py index 3ed6347..dd97bdc 100644 --- a/src/blueprinting/synthesizer/ir/portable_plan.py +++ b/src/blueprinting/synthesizer/ir/portable_plan.py @@ -12,10 +12,11 @@ from numbers import Real from typing import ClassVar -from ..codec import enum_type, record_type +from blueprinting.schema.codec import enum_type, record_type +from blueprinting.schema.frozen import FrozenDict + from ..errors import DiagnosticBag, VerificationReport from ..expr import Scalar -from ..frozen import FrozenDict from ..ids import BufferId, Lineage, NodeId from .common import ( CanonicalIRMixin, diff --git a/src/blueprinting/synthesizer/lowering/transformer.py b/src/blueprinting/synthesizer/lowering/transformer.py index 465513a..54e624b 100644 --- a/src/blueprinting/synthesizer/lowering/transformer.py +++ b/src/blueprinting/synthesizer/lowering/transformer.py @@ -2,18 +2,12 @@ from __future__ import annotations -from ...analysis.transformer_workload import ( - EngineKind, - PrimitiveInvocation, - derive_transformer_block, -) -from ...workload import ( - TensorParallelCommunication, - TransformerExecutionSpec, - TransformerModelSpec, -) +from blueprinting.mapping import TensorParallelCommunication, TransformerTrainingMappingSpec +from blueprinting.schema.frozen import FrozenDict +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec + from ..axes import BindingAxis -from ..frozen import FrozenDict +from ..dialects.transformer import EngineKind, PrimitiveInvocation, derive_transformer_block from ..ids import BufferId, Lineage, NodeId, ValueId from ..ir import ( AbstractStorageClass, @@ -48,58 +42,67 @@ from ..passes import DerivationPass, PassContext, PassContract -def _semantic_specs(ir: ModelIR, context: PassContext) -> tuple[TransformerModelSpec, TransformerExecutionSpec]: +def _semantic_specs( + ir: ModelIR, + context: PassContext, +) -> tuple[TransformerModelSpec, TransformerTrainingWorkloadSpec, TransformerTrainingMappingSpec]: if len(ir.operations) != 1 or ir.operations[0].operation != OperationName("transformer", "decoder_training"): raise ValueError("Transformer distribution expects one transformer.decoder_training operation") model = ir.operations[0].attributes.get("model_spec") - strategy = context.session.bindings.strategy - if strategy is None: - raise ValueError("Transformer distribution requires a strategy binding") - execution = strategy.attributes.get("execution_spec") if not isinstance(model, TransformerModelSpec): raise TypeError("model operation is missing a typed TransformerModelSpec") - if not isinstance(execution, TransformerExecutionSpec): - raise TypeError("strategy binding is missing a typed TransformerExecutionSpec") workload = context.session.bindings.workload - if workload is None: - raise ValueError("Transformer distribution requires a workload binding") - expected = (execution.microbatch_size, model.sequence_length, execution.microbatch_count) + strategy = context.session.bindings.strategy + if workload is None or strategy is None: + raise ValueError("Transformer distribution requires workload and strategy bindings") + workload_spec = workload.attributes.get("workload_spec") + mapping = strategy.attributes.get("mapping_spec") + if not isinstance(workload_spec, TransformerTrainingWorkloadSpec): + raise TypeError("workload binding is missing a typed TransformerTrainingWorkloadSpec") + if not isinstance(mapping, TransformerTrainingMappingSpec): + raise TypeError("strategy binding is missing a typed TransformerTrainingMappingSpec") + mapping.validate_workload(workload_spec) + expected = ( + workload_spec.microbatch_size, + model.sequence_length, + mapping.microbatch_count(workload_spec), + ) actual = (workload.batch_size, workload.sequence_length, workload.micro_batches) if actual != expected: - raise ValueError(f"workload binding {actual!r} is inconsistent with execution facts {expected!r}") + raise ValueError(f"workload binding {actual!r} is inconsistent with workload facts {expected!r}") if ( - strategy.tensor_parallel != execution.tensor_parallel - or strategy.pipeline_parallel != execution.pipeline_parallel - or strategy.data_parallel != execution.data_parallel + strategy.tensor_parallel != mapping.tensor_parallel + or strategy.pipeline_parallel != mapping.pipeline_parallel + or strategy.data_parallel != mapping.data_parallel ): - raise ValueError("strategy binding is inconsistent with Transformer execution facts") - return model, execution + raise ValueError("strategy binding is inconsistent with Transformer mapping facts") + return model, workload_spec, mapping class DistributeTransformerTrainingPass(DerivationPass[ModelIR, DistributedTaskIR]): """Expand one semantic block into explicit local and collective tasks.""" contract = PassContract.create( - "transformer-distribute-v1", + "transformer-distribute-v2", ModelIR, DistributedTaskIR, required_bindings=frozenset({BindingAxis.WORKLOAD, BindingAxis.STRATEGY}), ) def run(self, ir: ModelIR, context: PassContext) -> DistributedTaskIR: - model, execution = _semantic_specs(ir, context) - invocations, block_memory = derive_transformer_block(model, execution) - ranks = tuple(range(execution.tensor_parallel)) - mesh = LogicalMesh("local-tensor-parallel-group", (MeshAxis("tp", execution.tensor_parallel),)) + model, workload, mapping = _semantic_specs(ir, context) + invocations, block_memory = derive_transformer_block(model, workload, mapping) + ranks = tuple(range(mapping.tensor_parallel)) + mesh = LogicalMesh("local-tensor-parallel-group", (MeshAxis("tp", mapping.tensor_parallel),)) source_input = ir.inputs[0] source_output = ir.outputs[0] input_id = ValueId.derive(ir.digest, "transformer-distributed", "input") output_id = ValueId.derive(ir.digest, "transformer-distributed", "output") tensor_type = TensorType( - (execution.microbatch_size, model.sequence_length, model.hidden_size), - execution.datatype, + (workload.microbatch_size, model.sequence_length, model.hidden_size), + workload.datatype, ) - if execution.tensor_parallel_communication is TensorParallelCommunication.REDUCE_SCATTER_ALL_GATHER: + if mapping.tensor_parallel_communication is TensorParallelCommunication.REDUCE_SCATTER_ALL_GATHER: sharding = ShardingSpec(((), ("tp",), ())) else: sharding = ShardingSpec.replicated(tensor_type.rank, ("tp",)) @@ -175,7 +178,8 @@ def run(self, ir: ModelIR, context: PassContext) -> DistributedTaskIR: attributes=FrozenDict( { "model_spec": model, - "execution_spec": execution, + "workload_spec": workload, + "mapping_spec": mapping, "block_memory": block_memory, "scope": "one-local-tensor-parallel-block", } @@ -208,7 +212,6 @@ def _plan_resources(invocation: PrimitiveInvocation) -> tuple[ResourceRequiremen ResourceKind.NETWORK, invocation.work.message_bytes, ResourceScope.PER_RANK, - FrozenDict({"network_tier": invocation.network_tier}), ) ) return tuple(resources) @@ -218,7 +221,7 @@ class PlanTransformerTrainingPass(DerivationPass[DistributedTaskIR, PortablePlan """Materialize exact WorkloadFacts without choosing a hardware target.""" contract = PassContract.create( - "transformer-plan-work-v1", + "transformer-plan-work-v2", DistributedTaskIR, PortablePlanIR, required_bindings=frozenset({BindingAxis.STRATEGY}), @@ -226,8 +229,13 @@ class PlanTransformerTrainingPass(DerivationPass[DistributedTaskIR, PortablePlan def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: model = ir.attributes.get("model_spec") - execution = ir.attributes.get("execution_spec") - if not isinstance(model, TransformerModelSpec) or not isinstance(execution, TransformerExecutionSpec): + workload = ir.attributes.get("workload_spec") + mapping = ir.attributes.get("mapping_spec") + if not isinstance(model, TransformerModelSpec): + raise TypeError("distributed Transformer IR is missing TransformerModelSpec") + if not isinstance(workload, TransformerTrainingWorkloadSpec): + raise TypeError("distributed Transformer IR is missing TransformerTrainingWorkloadSpec") + if not isinstance(mapping, TransformerTrainingMappingSpec): raise TypeError("distributed Transformer IR is missing typed semantic facts") strategy = context.session.bindings.strategy if strategy is None: @@ -235,10 +243,10 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: input_id = BufferId.derive(ir.digest, "transformer-portable", "input") output_id = BufferId.derive(ir.digest, "transformer-portable", "output") - boundary_elements = execution.microbatch_size * model.sequence_length * model.hidden_size - if execution.tensor_parallel_communication is TensorParallelCommunication.REDUCE_SCATTER_ALL_GATHER: - boundary_elements //= execution.tensor_parallel - boundary_bytes = boundary_elements * execution.bytes_per_element + boundary_elements = workload.microbatch_size * model.sequence_length * model.hidden_size + if mapping.tensor_parallel_communication is TensorParallelCommunication.REDUCE_SCATTER_ALL_GATHER: + boundary_elements //= mapping.tensor_parallel + boundary_bytes = boundary_elements * workload.bytes_per_element task_ids = tuple( NodeId.derive(ir.digest, "transformer-portable", index, task.id) for index, task in enumerate(ir.tasks) @@ -275,10 +283,14 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: message_bytes=invocation.work.message_bytes, attributes=FrozenDict( { + "name": invocation.name, "engine": invocation.engine.value, "phase": invocation.phase.value, "primitive": invocation.primitive, "source_layer": invocation.source_layer, + "collective": ( + invocation.collective.value if invocation.collective is not None else "" + ), } ), ), @@ -286,7 +298,6 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: resources=_plan_resources(invocation), implementations=(ImplementationRequirement(capability, alternatives=alternatives),), concurrency_group=("network" if invocation.engine is EngineKind.COLLECTIVE else "compute"), - attributes=FrozenDict({"invocation": invocation}), ) ) @@ -294,7 +305,7 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: name=f"{model.name}-local-tp-block-plan", source_distributed_digest=ir.digest, strategy_fingerprint=strategy.fingerprint, - planner_revision="transformer-work-analysis-v1", + planner_revision="transformer-work-analysis-v2", tasks=tuple(tasks), buffers=( PlanBuffer( @@ -325,7 +336,8 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: attributes=FrozenDict( { "model_spec": model, - "execution_spec": execution, + "workload_spec": workload, + "mapping_spec": mapping, "block_memory": ir.attributes["block_memory"], "scope": ir.attributes["scope"], } diff --git a/src/blueprinting/synthesizer/lowering/transformer_inference.py b/src/blueprinting/synthesizer/lowering/transformer_inference.py index e852b28..2b550e3 100644 --- a/src/blueprinting/synthesizer/lowering/transformer_inference.py +++ b/src/blueprinting/synthesizer/lowering/transformer_inference.py @@ -2,16 +2,18 @@ from __future__ import annotations -from ...analysis.transformer_inference import ( +from blueprinting.mapping import TransformerInferenceMappingSpec +from blueprinting.schema.frozen import FrozenDict +from blueprinting.workload import TransformerModelSpec + +from ..axes import BindingAxis +from ..bindings import InferencePhase, WorkloadMode +from ..dialects.transformer import ( + EngineKind, InferenceBlockMemoryFacts, InferenceInvocation, derive_transformer_inference_block, ) -from ...analysis.transformer_workload import EngineKind -from ...workload import TransformerInferenceExecutionSpec, TransformerModelSpec -from ..axes import BindingAxis -from ..bindings import InferencePhase, WorkloadMode -from ..frozen import FrozenDict from ..ids import BufferId, Lineage, NodeId, ValueId from ..ir import ( AbstractStorageClass, @@ -51,7 +53,7 @@ def _semantic_specs( ir: ModelIR, context: PassContext, -) -> tuple[TransformerModelSpec, TransformerInferenceExecutionSpec, InferencePhase, int, int, int]: +) -> tuple[TransformerModelSpec, TransformerInferenceMappingSpec, InferencePhase, int, int, int, str]: if len(ir.operations) != 1 or ir.operations[0].operation != OperationName("transformer", "decoder_inference"): raise ValueError("Transformer inference distribution expects one transformer.decoder_inference operation") model = ir.operations[0].attributes.get("model_spec") @@ -63,13 +65,13 @@ def _semantic_specs( raise ValueError("Transformer inference distribution requires workload and strategy bindings") if workload.mode is not WorkloadMode.INFERENCE or workload.inference_phase is None: raise ValueError("Transformer inference requires an explicit inference phase") - execution = strategy.attributes.get("inference_execution_spec") - if not isinstance(execution, TransformerInferenceExecutionSpec): - raise TypeError("strategy binding is missing a typed TransformerInferenceExecutionSpec") + mapping = strategy.attributes.get("inference_mapping_spec") + if not isinstance(mapping, TransformerInferenceMappingSpec): + raise TypeError("strategy binding is missing a typed TransformerInferenceMappingSpec") if ( - strategy.tensor_parallel != execution.tensor_parallel - or strategy.pipeline_parallel != execution.pipeline_parallel - or strategy.data_parallel != execution.replicas + strategy.tensor_parallel != mapping.tensor_parallel + or strategy.pipeline_parallel != mapping.pipeline_parallel + or strategy.data_parallel != mapping.replicas ): raise ValueError("strategy binding is inconsistent with inference execution facts") if any( @@ -84,39 +86,43 @@ def _semantic_specs( raise ValueError("workload query_tokens attribute is inconsistent with the inference phase") if workload.attributes.get("context_tokens") != context_tokens: raise ValueError("workload context_tokens attribute is inconsistent with sequence_length") - execution.validate_model(model) - return model, execution, workload.inference_phase, batch_size, query_tokens, context_tokens + datatype = workload.attributes.get("datatype") + if not isinstance(datatype, str): + raise TypeError("inference workload binding is missing a concrete datatype") + mapping.validate_model(model) + return model, mapping, workload.inference_phase, batch_size, query_tokens, context_tokens, datatype class DistributeTransformerInferencePass(DerivationPass[ModelIR, DistributedTaskIR]): """Expand one phase into observable local and collective components.""" contract = PassContract.create( - "transformer-inference-distribute-v1", + "transformer-inference-distribute-v2", ModelIR, DistributedTaskIR, required_bindings=frozenset({BindingAxis.WORKLOAD, BindingAxis.STRATEGY}), ) def run(self, ir: ModelIR, context: PassContext) -> DistributedTaskIR: - model, execution, phase, batch_size, query_tokens, context_tokens = _semantic_specs(ir, context) + model, mapping, phase, batch_size, query_tokens, context_tokens, datatype = _semantic_specs(ir, context) invocations, block_memory = derive_transformer_inference_block( model, - execution, + mapping, phase=phase, batch_size=batch_size, context_tokens=context_tokens, + datatype=datatype, ) - ranks = tuple(range(execution.tensor_parallel)) - mesh = LogicalMesh("local-tensor-parallel-group", (MeshAxis("tp", execution.tensor_parallel),)) + ranks = tuple(range(mapping.tensor_parallel)) + mesh = LogicalMesh("local-tensor-parallel-group", (MeshAxis("tp", mapping.tensor_parallel),)) source_input = ir.inputs[0] source_output = ir.outputs[0] source_cache = next(value.id for value in ir.values if value.role is ValueRole.KV_CACHE) input_id = ValueId.derive(ir.digest, phase.value, "transformer-distributed", "input") cache_id = ValueId.derive(ir.digest, phase.value, "transformer-distributed", "kv-cache") output_id = ValueId.derive(ir.digest, phase.value, "transformer-distributed", "output") - boundary_type = TensorType((batch_size, query_tokens, model.hidden_size), execution.datatype) - cache_type = TensorType((2, batch_size, context_tokens, model.hidden_size), execution.datatype) + boundary_type = TensorType((batch_size, query_tokens, model.hidden_size), datatype) + cache_type = TensorType((2, batch_size, context_tokens, model.hidden_size), datatype) boundary_sharding = ShardingSpec.replicated(boundary_type.rank, ("tp",)) cache_sharding = ShardingSpec(((), (), (), ("tp",))) @@ -211,11 +217,12 @@ def run(self, ir: ModelIR, context: PassContext) -> DistributedTaskIR: attributes=FrozenDict( { "model_spec": model, - "inference_execution_spec": execution, + "inference_mapping_spec": mapping, "inference_phase": phase, "batch_size": batch_size, "query_tokens": query_tokens, "context_tokens": context_tokens, + "datatype": datatype, "block_memory": block_memory, "scope": "one-local-tensor-parallel-block-phase", } @@ -244,7 +251,6 @@ def _plan_resources(invocation: InferenceInvocation) -> tuple[ResourceRequiremen ResourceKind.NETWORK, invocation.work.message_bytes, ResourceScope.PER_RANK, - FrozenDict({"network_tier": invocation.network_tier}), ) ) return tuple(resources) @@ -272,7 +278,7 @@ class PlanTransformerInferencePass(DerivationPass[DistributedTaskIR, PortablePla """Materialize a phase plan without target placement or measured time.""" contract = PassContract.create( - "transformer-inference-plan-work-v1", + "transformer-inference-plan-work-v2", DistributedTaskIR, PortablePlanIR, required_bindings=frozenset({BindingAxis.WORKLOAD, BindingAxis.STRATEGY}), @@ -280,13 +286,13 @@ class PlanTransformerInferencePass(DerivationPass[DistributedTaskIR, PortablePla def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: model = ir.attributes.get("model_spec") - execution = ir.attributes.get("inference_execution_spec") + mapping = ir.attributes.get("inference_mapping_spec") phase = ir.attributes.get("inference_phase") block_memory = ir.attributes.get("block_memory") if not isinstance(model, TransformerModelSpec): raise TypeError("distributed inference IR is missing TransformerModelSpec") - if not isinstance(execution, TransformerInferenceExecutionSpec): - raise TypeError("distributed inference IR is missing TransformerInferenceExecutionSpec") + if not isinstance(mapping, TransformerInferenceMappingSpec): + raise TypeError("distributed inference IR is missing TransformerInferenceMappingSpec") if not isinstance(phase, InferencePhase): raise TypeError("distributed inference IR is missing InferencePhase") if not isinstance(block_memory, InferenceBlockMemoryFacts): @@ -353,9 +359,6 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: "collective": ( invocation.collective.value if invocation.collective is not None else "" ), - "network_tier": ( - invocation.network_tier if invocation.network_tier is not None else -1 - ), } ), ), @@ -371,7 +374,7 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: name=f"{model.name}-{phase.value}-local-tp-block-plan", source_distributed_digest=ir.digest, strategy_fingerprint=strategy.fingerprint, - planner_revision="transformer-inference-work-analysis-v1", + planner_revision="transformer-inference-work-analysis-v2", tasks=tuple(tasks), buffers=( PlanBuffer( @@ -437,11 +440,12 @@ def run(self, ir: DistributedTaskIR, context: PassContext) -> PortablePlanIR: attributes=FrozenDict( { "model_spec": model, - "inference_execution_spec": execution, + "inference_mapping_spec": mapping, "inference_phase": phase, "batch_size": ir.attributes["batch_size"], "query_tokens": ir.attributes["query_tokens"], "context_tokens": ir.attributes["context_tokens"], + "datatype": ir.attributes["datatype"], "scope": ir.attributes["scope"], } ), diff --git a/src/blueprinting/synthesizer/passes/base.py b/src/blueprinting/synthesizer/passes/base.py index d0e90d6..a9b41dd 100644 --- a/src/blueprinting/synthesizer/passes/base.py +++ b/src/blueprinting/synthesizer/passes/base.py @@ -17,16 +17,17 @@ from enum import Enum from typing import Any, Generic, TypeVar +from blueprinting.schema.codec import content_digest +from blueprinting.schema.errors import SerializationError +from blueprinting.schema.frozen import freeze + from ..axes import BindingAxis -from ..codec import content_digest from ..errors import ( MissingAnalysisError, PassContractError, PassExecutionError, - SerializationError, SynthesisError, ) -from ..frozen import freeze from ..ir.common import CanonicalIRMixin, SchemaVersion from ..session import SynthesisSession diff --git a/src/blueprinting/synthesizer/session.py b/src/blueprinting/synthesizer/session.py index 4877bf7..228dc42 100644 --- a/src/blueprinting/synthesizer/session.py +++ b/src/blueprinting/synthesizer/session.py @@ -5,11 +5,12 @@ from dataclasses import dataclass, field, replace from typing import Any +from blueprinting.schema.codec import content_digest, record_type +from blueprinting.schema.frozen import FrozenDict, freeze + from .axes import BindingAxis from .bindings import BindingSet, BindingValue, TargetRequirements -from .codec import content_digest, record_type from .errors import BindingError -from .frozen import FrozenDict, freeze @record_type("compiler.session") diff --git a/src/blueprinting/system/chip.py b/src/blueprinting/system/chip.py index 7ea9a78..012ed19 100644 --- a/src/blueprinting/system/chip.py +++ b/src/blueprinting/system/chip.py @@ -5,7 +5,7 @@ import math from dataclasses import dataclass -from blueprinting.synthesizer.codec import record_type +from blueprinting.schema.codec import record_type def _positive_rate(value: float, name: str) -> None: diff --git a/src/blueprinting/system/interconnect.py b/src/blueprinting/system/interconnect.py index 9297a2d..c6854da 100644 --- a/src/blueprinting/system/interconnect.py +++ b/src/blueprinting/system/interconnect.py @@ -5,8 +5,8 @@ import math from dataclasses import dataclass -from blueprinting.synthesizer.codec import record_type -from blueprinting.synthesizer.frozen import FrozenDict +from blueprinting.schema.codec import record_type +from blueprinting.schema.frozen import FrozenDict @record_type("compiler.analysis.network_operation.v1") diff --git a/src/blueprinting/system/profile.py b/src/blueprinting/system/profile.py index ed1ac70..cda5eb9 100644 --- a/src/blueprinting/system/profile.py +++ b/src/blueprinting/system/profile.py @@ -6,8 +6,8 @@ from dataclasses import dataclass from typing import Any -from blueprinting.synthesizer.codec import content_digest, record_type -from blueprinting.synthesizer.frozen import FrozenDict +from blueprinting.schema.codec import content_digest, record_type +from blueprinting.schema.frozen import FrozenDict from .chip import EfficiencyCurve, EfficiencyPoint, MemoryProfile, ProcessorProfile from .interconnect import NetworkOperationProfile, NetworkProfile diff --git a/src/blueprinting/synthesizer/experiments/__init__.py b/src/blueprinting/validation/__init__.py similarity index 100% rename from src/blueprinting/synthesizer/experiments/__init__.py rename to src/blueprinting/validation/__init__.py diff --git a/src/blueprinting/synthesizer/experiments/calculon.py b/src/blueprinting/validation/calculon.py similarity index 89% rename from src/blueprinting/synthesizer/experiments/calculon.py rename to src/blueprinting/validation/calculon.py index bb1f4bf..1ea2370 100644 --- a/src/blueprinting/synthesizer/experiments/calculon.py +++ b/src/blueprinting/validation/calculon.py @@ -18,21 +18,21 @@ from pathlib import Path from typing import Any -from calculon.llm import Llm -from calculon.system import System - -from ...analysis.cost_model import ( +from blueprinting.analysis.cost_model import ( CalibrationMode, IterationEstimate, estimate_iteration, ) -from ...analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase -from ...system import SystemProfile -from ...workload import TransformerExecutionSpec, TransformerModelSpec -from ..frontend import build_transformer_model_ir, synthesis_session_for -from ..ir import PortablePlanIR -from ..lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass -from ..passes import PassManager, PassPipeline +from blueprinting.mapping import NetworkTierBinding, TransformerTrainingMappingSpec +from blueprinting.synthesizer.dialects.transformer import EngineKind, TrainingPhase +from blueprinting.synthesizer.frontend import build_transformer_model_ir, synthesis_session_for +from blueprinting.synthesizer.ir import PortablePlanIR +from blueprinting.synthesizer.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass +from blueprinting.synthesizer.passes import PassManager, PassPipeline +from blueprinting.system import SystemProfile +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec +from calculon.llm import Llm +from calculon.system import System SEQSEL_TABLE5_SECONDS = { "megatron-22B": {"full": 1.42, "seqsel": 1.10}, @@ -250,7 +250,7 @@ def _run_calculon( execution_data: dict[str, Any], system_data: dict[str, Any], ) -> dict[str, Any]: - logger = logging.getLogger("blueprinting.synthesizer.experiments.calculon") + logger = logging.getLogger("blueprinting.validation.calculon") application = Llm.Application(model_data) execution_fields = {field: execution_data[field] for field in Llm.Execution.fields()} execution = Llm.Execution.from_json(execution_fields) @@ -263,10 +263,11 @@ def _run_calculon( def _derive_plan( model: TransformerModelSpec, - execution: TransformerExecutionSpec, + workload: TransformerTrainingWorkloadSpec, + mapping: TransformerTrainingMappingSpec, ) -> tuple[PortablePlanIR, tuple[dict[str, Any], ...], str, str]: - source = build_transformer_model_ir(model) - session = synthesis_session_for(model, execution) + source = build_transformer_model_ir(model, datatype=workload.datatype) + session = synthesis_session_for(model, workload, mapping) result = PassManager().run( PassPipeline.of(DistributeTransformerTrainingPass(), PlanTransformerTrainingPass()), source, @@ -302,12 +303,11 @@ def _phase_work(plan: PortablePlanIR, phase: TrainingPhase) -> tuple[int, int, i memory_bytes = 0 message_bytes = 0 for task in plan.tasks: - invocation = task.attributes.get("invocation") - if not isinstance(invocation, PrimitiveInvocation) or invocation.phase is not phase: + if task.workload.attributes.get("phase") != phase.value: continue - operations += invocation.work.operations - memory_bytes += invocation.work.memory_bytes - message_bytes += invocation.work.message_bytes + operations += task.workload.operations + memory_bytes += task.workload.read_bytes + task.workload.write_bytes + message_bytes += task.workload.message_bytes return operations, memory_bytes, message_bytes @@ -362,9 +362,11 @@ def run_calculon_experiment(cases: tuple[CalculonCase, ...]) -> CalculonExperime execution_data = _read_json(case.execution_path) system_data = _read_json(case.system_path) model = TransformerModelSpec.from_mapping(case.model_path.stem, model_data) - execution = TransformerExecutionSpec.from_mapping(execution_data) - plan, checkpoints, model_digest, distributed_digest = _derive_plan(model, execution) - hardware = SystemProfile.from_mapping(case.system_path.stem, system_data, datatype=execution.datatype) + workload = TransformerTrainingWorkloadSpec.from_mapping(execution_data) + mapping = TransformerTrainingMappingSpec.from_mapping(execution_data) + network_binding = NetworkTierBinding.from_mapping(execution_data) + plan, checkpoints, model_digest, distributed_digest = _derive_plan(model, workload, mapping) + hardware = SystemProfile.from_mapping(case.system_path.stem, system_data, datatype=workload.datatype) if not evidence_revision: evidence_revision = hardware.evidence_revision elif evidence_revision != hardware.evidence_revision: @@ -378,8 +380,18 @@ def run_calculon_experiment(cases: tuple[CalculonCase, ...]) -> CalculonExperime portable_digest=plan.digest, pass_checkpoints=checkpoints, workload=_workload_audit(plan, calculon_stats), - peak_only=estimate_iteration(plan, hardware, CalibrationMode.PEAK_ONLY), - calibrated=estimate_iteration(plan, hardware, CalibrationMode.SYSTEM_EVIDENCE), + peak_only=estimate_iteration( + plan, + hardware, + CalibrationMode.PEAK_ONLY, + network_binding=network_binding, + ), + calibrated=estimate_iteration( + plan, + hardware, + CalibrationMode.SYSTEM_EVIDENCE, + network_binding=network_binding, + ), calculon_stats=calculon_stats, paper_seconds=case.paper_seconds, ) diff --git a/src/blueprinting/validation/legacy/__init__.py b/src/blueprinting/validation/legacy/__init__.py new file mode 100644 index 0000000..85cf98b --- /dev/null +++ b/src/blueprinting/validation/legacy/__init__.py @@ -0,0 +1 @@ +"""Retained oracle-only reproductions that do not exercise canonical derivation.""" diff --git a/src/blueprinting/validations/cases/seqsel_fig1.py b/src/blueprinting/validation/legacy/seqsel_fig1.py old mode 100755 new mode 100644 similarity index 80% rename from src/blueprinting/validations/cases/seqsel_fig1.py rename to src/blueprinting/validation/legacy/seqsel_fig1.py index b00b532..77832ac --- a/src/blueprinting/validations/cases/seqsel_fig1.py +++ b/src/blueprinting/validation/legacy/seqsel_fig1.py @@ -1,7 +1,7 @@ -"""Validation case for seqsel figure 1. +"""Legacy Calculon reproduction of SeqSel figure 1. -NOTE: This validation case intentionally uses calculon for comparison purposes. -It validates blueprinting's results against calculon's reference implementation. +This compatibility check does not exercise Blueprinting's canonical derivation +path; the strict production gate lives in :mod:`blueprinting.validation.calculon`. """ import logging @@ -10,7 +10,8 @@ import pandas as pd # Calculon is used here for validation comparison only -from blueprinting import Execution, Model, io +import blueprinting.io as io +from blueprinting.types import Execution, Model from calculon.llm import Llm from calculon.llm import System as CalculonSystem @@ -76,11 +77,7 @@ def seqsel_fig1(show=False): model.compile(syst, exe) model.run(syst) stats = model.get_stats_json(False) - act_par_opt = ( - stats["weight_space"] - + stats["weight_grad_space"] - + stats["optimizer_space"] - ) / (1024**3) + act_par_opt = (stats["weight_space"] + stats["weight_grad_space"] + stats["optimizer_space"]) / (1024**3) act_act = stats["act_space"] / (1024**3) records += [ { @@ -103,12 +100,12 @@ def seqsel_fig1(show=False): .reset_index() ) - result["w+opt mem/rtol"] = ( - result["w+opt mem(GiB)[act]"] - result["w+opt mem(GiB)[pred]"] - ).abs() / result["w+opt mem(GiB)[act]"] - result["act mem/rtol"] = ( - result["act mem(GiB)[act]"] - result["act mem(GiB)[pred]"] - ).abs() / result["act mem(GiB)[act]"] + result["w+opt mem/rtol"] = (result["w+opt mem(GiB)[act]"] - result["w+opt mem(GiB)[pred]"]).abs() / result[ + "w+opt mem(GiB)[act]" + ] + result["act mem/rtol"] = (result["act mem(GiB)[act]"] - result["act mem(GiB)[pred]"]).abs() / result[ + "act mem(GiB)[act]" + ] return ( result.style.format( diff --git a/src/blueprinting/validations/cases/seqsel_fig7.py b/src/blueprinting/validation/legacy/seqsel_fig7.py old mode 100755 new mode 100644 similarity index 82% rename from src/blueprinting/validations/cases/seqsel_fig7.py rename to src/blueprinting/validation/legacy/seqsel_fig7.py index 48ed927..fe2c63f --- a/src/blueprinting/validations/cases/seqsel_fig7.py +++ b/src/blueprinting/validation/legacy/seqsel_fig7.py @@ -1,7 +1,7 @@ -"""Validation case for seqsel figure 7. +"""Legacy Calculon reproduction of SeqSel figure 7. -NOTE: This validation case intentionally uses calculon for comparison purposes. -It validates blueprinting's results against calculon's reference implementation. +This compatibility check does not exercise Blueprinting's canonical derivation +path; the strict production gate lives in :mod:`blueprinting.validation.calculon`. """ import logging @@ -9,7 +9,8 @@ import hyperparameter as hp import pandas as pd -from blueprinting import Execution, Model, io +import blueprinting.io as io +from blueprinting.types import Execution, Model # Calculon is used here for validation comparison only from calculon.llm import Llm @@ -101,15 +102,11 @@ def seqsel_fig7(show=False): df = pd.DataFrame.from_records(records) selected = df[df["mode"] == "none"] for _, row in df.iterrows(): - x = selected[selected.model == row.model][selected.system == row.system] + x = selected[(selected.model == row.model) & (selected.system == row.system)] df.loc[ - (df.model == row.model) - & (df.system == row.system) - & (df["mode"] == row["mode"]), + (df.model == row.model) & (df.system == row.system) & (df["mode"] == row["mode"]), "act mem(%)", - ] = ( - 100 * row["act mem(%)"] / x.iloc[0]["act mem(%)"] - ) + ] = 100 * row["act mem(%)"] / x.iloc[0]["act mem(%)"] result = ( mem_usage.set_index(["model", "system", "mode"]) .join( @@ -121,9 +118,7 @@ def seqsel_fig7(show=False): .reset_index() ) - result["act mem/rtol"] = ( - result["act mem(%)[act]"] - result["act mem(%)[pred]"] - ).abs() / result["act mem(%)[act]"] + result["act mem/rtol"] = (result["act mem(%)[act]"] - result["act mem(%)[pred]"]).abs() / result["act mem(%)[act]"] return ( result.style.format( diff --git a/src/blueprinting/validations/cases/seqsel_tab5.py b/src/blueprinting/validation/legacy/seqsel_tab5.py old mode 100755 new mode 100644 similarity index 80% rename from src/blueprinting/validations/cases/seqsel_tab5.py rename to src/blueprinting/validation/legacy/seqsel_tab5.py index b767067..35f6ede --- a/src/blueprinting/validations/cases/seqsel_tab5.py +++ b/src/blueprinting/validation/legacy/seqsel_tab5.py @@ -1,7 +1,7 @@ -"""Validation case for seqsel table 5. +"""Legacy Calculon reproduction of SeqSel table 5. -NOTE: This validation case intentionally uses calculon for comparison purposes. -It validates blueprinting's results against calculon's reference implementation. +This compatibility check does not exercise Blueprinting's canonical derivation +path; the strict production gate lives in :mod:`blueprinting.validation.calculon`. """ import logging @@ -9,7 +9,8 @@ import hyperparameter as hp import pandas as pd -from blueprinting import Execution, Model, io +import blueprinting.io as io +from blueprinting.types import Execution, Model # Calculon is used here for validation comparison only from calculon.llm import Llm @@ -85,12 +86,8 @@ def seqsel_tab5(show=False): .reset_index() ) - result["iter time/rtol"] = ( - result["iter time(s)[act]"] - result["iter time(s)[pred]"] - ).abs() / result["iter time(s)[act]"] + result["iter time/rtol"] = (result["iter time(s)[act]"] - result["iter time(s)[pred]"]).abs() / result[ + "iter time(s)[act]" + ] - return ( - result.style.format({"iter time/rtol": lambda x: "%.2f%%" % (100 * x)}) - if show - else result - ) + return result.style.format({"iter time/rtol": lambda x: "%.2f%%" % (100 * x)}) if show else result diff --git a/src/blueprinting/synthesizer/experiments/regression.py b/src/blueprinting/validation/regression.py similarity index 96% rename from src/blueprinting/synthesizer/experiments/regression.py rename to src/blueprinting/validation/regression.py index 7b21510..ba1c0ae 100644 --- a/src/blueprinting/synthesizer/experiments/regression.py +++ b/src/blueprinting/validation/regression.py @@ -9,10 +9,12 @@ from pathlib import Path from typing import Any -from ...analysis import VidurProfileBaseline -from ...system import SystemProfile -from ...workload import TransformerInferenceExecutionSpec, TransformerModelSpec -from ..bindings import InferencePhase +from blueprinting.analysis import VidurProfileBaseline +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec +from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.system import SystemProfile +from blueprinting.workload import TransformerModelSpec + from .calculon import CalculonExperimentReport, discover_seqsel_tab5_cases, run_calculon_experiment from .vidur import VidurExperimentCase, VidurExperimentReport, run_vidur_experiment @@ -277,12 +279,15 @@ def _load_vidur_report( ) model = TransformerModelSpec(**manifest["blueprinting"]["model"]) - execution = TransformerInferenceExecutionSpec(**manifest["blueprinting"]["execution"]) + execution_data = manifest["blueprinting"]["execution"] + mapping = TransformerInferenceMappingSpec.from_mapping(execution_data) + network_binding = NetworkTierBinding.from_mapping(execution_data) + datatype = execution_data["datatype"] hardware_manifest = manifest["blueprinting"]["hardware"] hardware = SystemProfile.from_mapping( hardware_manifest["name"], _read_json(repository_root / hardware_manifest["profile"]), - datatype=execution.datatype, + datatype=datatype, ) baseline = VidurProfileBaseline.from_csv( attention_csv=_fixture_path(fixture_root, "attention.csv"), @@ -292,13 +297,15 @@ def _load_vidur_report( attention_backend=manifest["selection"]["attention_backend"], block_size=manifest["selection"]["block_size"], source_revision=manifest["source"]["revision"], - datatype=execution.datatype, + datatype=datatype, ) cases = tuple( VidurExperimentCase( name=(f"phi2-a100-tp1/{case_data['phase']}/b{case_data['batch_size']}-c{case_data['context_tokens']}"), model=model, - execution=execution, + mapping=mapping, + network_binding=network_binding, + datatype=datatype, hardware=hardware, phase=InferencePhase(case_data["phase"]), batch_size=case_data["batch_size"], diff --git a/src/blueprinting/synthesizer/experiments/vidur.py b/src/blueprinting/validation/vidur.py similarity index 89% rename from src/blueprinting/synthesizer/experiments/vidur.py rename to src/blueprinting/validation/vidur.py index b5eb672..9d0b5d3 100644 --- a/src/blueprinting/synthesizer/experiments/vidur.py +++ b/src/blueprinting/validation/vidur.py @@ -12,20 +12,21 @@ from dataclasses import dataclass from typing import Any -from ...analysis import ( +from blueprinting.analysis import ( CalibrationMode, InferenceBaseline, InferencePhaseEstimate, estimate_inference_phase, inference_evidence_query_for, ) -from ...system import SystemProfile -from ...workload import TransformerInferenceExecutionSpec, TransformerModelSpec -from ..bindings import InferencePhase -from ..frontend import build_transformer_inference_model_ir, inference_synthesis_session_for -from ..ir import PortablePlanIR -from ..lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass -from ..passes import PassManager, PassPipeline +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec +from blueprinting.synthesizer.bindings import InferencePhase +from blueprinting.synthesizer.frontend import build_transformer_inference_model_ir, inference_synthesis_session_for +from blueprinting.synthesizer.ir import PortablePlanIR +from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass +from blueprinting.synthesizer.passes import PassManager, PassPipeline +from blueprinting.system import SystemProfile +from blueprinting.workload import TransformerModelSpec @dataclass(frozen=True) @@ -161,7 +162,9 @@ class VidurExperimentCase: name: str model: TransformerModelSpec - execution: TransformerInferenceExecutionSpec + mapping: TransformerInferenceMappingSpec + network_binding: NetworkTierBinding + datatype: str hardware: SystemProfile phase: InferencePhase batch_size: int @@ -176,11 +179,13 @@ def __post_init__(self) -> None: value = getattr(self, field_name) if isinstance(value, bool) or not isinstance(value, int) or value <= 0: raise ValueError(f"{field_name} must be a positive integer") - self.execution.validate_model(self.model) + self.mapping.validate_model(self.model) if self.context_tokens > self.model.sequence_length: raise ValueError("context_tokens cannot exceed model sequence_length") - if self.hardware.datatype != self.execution.datatype: - raise ValueError("hardware and execution datatype must match") + if self.datatype not in {"float8", "float16", "bfloat16", "float32"}: + raise ValueError(f"unsupported datatype: {self.datatype!r}") + if self.hardware.datatype != self.datatype: + raise ValueError("hardware and workload datatype must match") @dataclass(frozen=True) @@ -296,11 +301,14 @@ def compare_inference_phase_to_vidur( """Compare an already-lowered and already-costed phase with Vidur.""" model = plan.attributes.get("model_spec") - execution = plan.attributes.get("inference_execution_spec") + mapping = plan.attributes.get("inference_mapping_spec") + datatype = plan.attributes.get("datatype") if not isinstance(model, TransformerModelSpec): raise TypeError("portable inference plan is missing TransformerModelSpec") - if not isinstance(execution, TransformerInferenceExecutionSpec): - raise TypeError("portable inference plan is missing TransformerInferenceExecutionSpec") + if not isinstance(mapping, TransformerInferenceMappingSpec): + raise TypeError("portable inference plan is missing TransformerInferenceMappingSpec") + if not isinstance(datatype, str): + raise TypeError("portable inference plan is missing its datatype") if len(plan.tasks) != len(estimate.tasks): raise ValueError("plan and estimate task counts differ") @@ -313,7 +321,8 @@ def compare_inference_phase_to_vidur( inference_evidence_query_for( invocation, hardware=hardware, - execution=execution, + mapping=mapping, + datatype=datatype, model=model, batch_size=estimate.batch_size, query_tokens=estimate.query_tokens, @@ -356,23 +365,34 @@ def run_vidur_experiment( pipeline = PassPipeline.of(DistributeTransformerInferencePass(), PlanTransformerInferencePass()) manager = PassManager() for case in cases: - source = build_transformer_inference_model_ir(case.model, datatype=case.execution.datatype) + source = build_transformer_inference_model_ir(case.model, datatype=case.datatype) result = manager.run( pipeline, source, session=inference_synthesis_session_for( case.model, - case.execution, + case.mapping, phase=case.phase, batch_size=case.batch_size, context_tokens=case.context_tokens, + datatype=case.datatype, ), ) plan = result.ir if not isinstance(plan, PortablePlanIR): raise TypeError(f"inference pipeline returned {type(plan).__name__}, expected PortablePlanIR") - peak = estimate_inference_phase(plan, case.hardware, CalibrationMode.PEAK_ONLY) - system = estimate_inference_phase(plan, case.hardware, CalibrationMode.SYSTEM_EVIDENCE) + peak = estimate_inference_phase( + plan, + case.hardware, + CalibrationMode.PEAK_ONLY, + network_binding=case.network_binding, + ) + system = estimate_inference_phase( + plan, + case.hardware, + CalibrationMode.SYSTEM_EVIDENCE, + network_binding=case.network_binding, + ) checkpoints = tuple( { "pass": checkpoint.record.pass_name, diff --git a/src/blueprinting/workbench/catalog.py b/src/blueprinting/workbench/catalog.py index eea5056..6193629 100644 --- a/src/blueprinting/workbench/catalog.py +++ b/src/blueprinting/workbench/catalog.py @@ -42,5 +42,11 @@ def preferred(self, category: str, preferred_name: str) -> str: @lru_cache(maxsize=1) def default_catalog() -> ConfigCatalog: + package_presets = Path(__file__).resolve().parents[1] / "presets" + if package_presets.is_dir(): + return ConfigCatalog(package_presets) repository_root = Path(__file__).resolve().parents[3] - return ConfigCatalog(repository_root / "data") + source_presets = repository_root / "data" + if source_presets.is_dir(): + return ConfigCatalog(source_presets) + raise FileNotFoundError("Blueprinting model and system presets are not installed") diff --git a/src/blueprinting/workload/__init__.py b/src/blueprinting/workload/__init__.py index 35453f1..341ebac 100644 --- a/src/blueprinting/workload/__init__.py +++ b/src/blueprinting/workload/__init__.py @@ -1,21 +1,10 @@ -"""Target-neutral workload and logical-mapping contracts.""" +"""Target-neutral model and workload contracts.""" -from .transformer import ( - RecomputePolicy, - TensorParallelCommunication, - TransformerExecutionSpec, - TransformerModelSpec, -) -from .transformer_inference import ( - TransformerInferenceExecutionSpec, - TransformerInferenceRequestSpec, -) +from .transformer import TransformerModelSpec, TransformerTrainingWorkloadSpec +from .transformer_inference import TransformerInferenceRequestSpec __all__ = [ - "RecomputePolicy", - "TensorParallelCommunication", - "TransformerExecutionSpec", - "TransformerInferenceExecutionSpec", "TransformerInferenceRequestSpec", "TransformerModelSpec", + "TransformerTrainingWorkloadSpec", ] diff --git a/src/blueprinting/workload/transformer.py b/src/blueprinting/workload/transformer.py index 6943ff7..429321c 100644 --- a/src/blueprinting/workload/transformer.py +++ b/src/blueprinting/workload/transformer.py @@ -1,6 +1,6 @@ """Target-neutral decoder-only Transformer workload contracts. -These immutable descriptions own model semantics and logical mapping intent. +These immutable descriptions own model semantics and scenario facts. They do not construct canonical IR, bind a hardware target, or estimate time; the synthesizer frontend and analysis packages own those responsibilities. """ @@ -9,10 +9,9 @@ from collections.abc import Mapping from dataclasses import dataclass -from enum import Enum from typing import Any -from blueprinting.synthesizer.codec import enum_type, record_type +from blueprinting.schema.codec import record_type def _positive_integer(value: Any, name: str) -> int: @@ -21,19 +20,6 @@ def _positive_integer(value: Any, name: str) -> int: return value -@enum_type("compiler.transformer.recompute_policy") -class RecomputePolicy(Enum): - NONE = "none" - ATTENTION = "attn_only" - FULL = "full" - - -@enum_type("compiler.transformer.tp_communication") -class TensorParallelCommunication(Enum): - ALL_REDUCE = "ar" - REDUCE_SCATTER_ALL_GATHER = "rs_ag" - - @record_type("compiler.transformer.model_spec.v1") @dataclass(frozen=True) class TransformerModelSpec: @@ -75,119 +61,33 @@ def from_mapping(cls, name: str, data: Mapping[str, Any]) -> TransformerModelSpe ) -@record_type("compiler.transformer.execution_spec.v1") +@record_type("blueprinting.workload.transformer-training.v1") @dataclass(frozen=True) -class TransformerExecutionSpec: - """Structurally relevant training strategy used by the comparison experiment. +class TransformerTrainingWorkloadSpec: + """Training scenario facts independent of parallel mapping and hardware.""" - Every field changes structure, multiplicity, storage, or communication. - There are intentionally no efficiency or correction-factor fields here. - """ - - world_size: int - tensor_parallel: int - pipeline_parallel: int - data_parallel: int global_batch_size: int microbatch_size: int datatype: str - recompute: RecomputePolicy - pipeline_interleaving: int - optimizer_sharding: bool - tensor_parallel_communication: TensorParallelCommunication - tensor_parallel_network: int - pipeline_parallel_network: int - data_parallel_network: int - fused_activation: bool = False - sequence_parallel_all_gather_redo: bool = False - data_parallel_overlap: bool = False - training: bool = True - attention_type: str = "multihead" - tensor_parallel_overlap: str = "none" - weight_offload: bool = False - activation_offload: bool = False - optimizer_offload: bool = False def __post_init__(self) -> None: - for field_name in ( - "world_size", - "tensor_parallel", - "pipeline_parallel", - "data_parallel", - "global_batch_size", - "microbatch_size", - "pipeline_interleaving", - ): + for field_name in ("global_batch_size", "microbatch_size"): _positive_integer(getattr(self, field_name), field_name) - for field_name in ( - "tensor_parallel_network", - "pipeline_parallel_network", - "data_parallel_network", - ): - value = getattr(self, field_name) - if isinstance(value, bool) or not isinstance(value, int) or value < 0: - raise ValueError(f"{field_name} must be a non-negative integer") - if self.world_size != self.tensor_parallel * self.pipeline_parallel * self.data_parallel: - raise ValueError("world_size must equal tensor_parallel * pipeline_parallel * data_parallel") - if self.global_batch_size % self.data_parallel: - raise ValueError("global_batch_size must be divisible by data_parallel") - if self.local_batch_size % self.microbatch_size: - raise ValueError("local batch size must be divisible by microbatch_size") - if not isinstance(self.recompute, RecomputePolicy): - raise TypeError("recompute must be a RecomputePolicy") - if not isinstance(self.tensor_parallel_communication, TensorParallelCommunication): - raise TypeError("tensor_parallel_communication must be TensorParallelCommunication") if self.datatype not in {"float16", "bfloat16", "float32", "float8"}: raise ValueError(f"unsupported datatype: {self.datatype!r}") - if self.attention_type != "multihead": - raise ValueError("the calibrated experiment currently supports multihead attention only") - if self.tensor_parallel_overlap != "none": - raise ValueError("overlapped TP requires concrete scheduling and is outside this experiment") - if self.data_parallel_overlap: - raise ValueError("overlapped DP requires concrete scheduling and is outside this experiment") - if self.weight_offload or self.activation_offload or self.optimizer_offload: - raise ValueError("offload strategies are outside this experiment") - if not self.training: - raise ValueError("this execution spec describes training only") - if self.optimizer_sharding and self.data_parallel == 1: - raise ValueError("optimizer sharding requires data_parallel > 1") - - @property - def local_batch_size(self) -> int: - return self.global_batch_size // self.data_parallel - - @property - def microbatch_count(self) -> int: - return self.local_batch_size // self.microbatch_size + if self.microbatch_size > self.global_batch_size: + raise ValueError("microbatch_size cannot exceed global_batch_size") @property def bytes_per_element(self) -> int: return {"float8": 1, "float16": 2, "bfloat16": 2, "float32": 4}[self.datatype] @classmethod - def from_mapping(cls, data: Mapping[str, Any]) -> TransformerExecutionSpec: + def from_mapping(cls, data: Mapping[str, Any]) -> TransformerTrainingWorkloadSpec: + if "global_batch_size" in data and "batch_size" in data and data["global_batch_size"] != data["batch_size"]: + raise ValueError("global_batch_size conflicts with legacy alias batch_size") return cls( - world_size=data["num_procs"], - tensor_parallel=data["tensor_par"], - pipeline_parallel=data["pipeline_par"], - data_parallel=data["data_par"], - global_batch_size=data["batch_size"], + global_batch_size=(data["global_batch_size"] if "global_batch_size" in data else data["batch_size"]), microbatch_size=data["microbatch_size"], datatype=data["datatype"], - recompute=RecomputePolicy(data["activation_recompute"]), - pipeline_interleaving=data["pipeline_interleaving"], - optimizer_sharding=data["optimizer_sharding"], - tensor_parallel_communication=TensorParallelCommunication(data["tensor_par_comm_type"]), - tensor_parallel_network=data["tensor_par_net"], - pipeline_parallel_network=data["pipeline_par_net"], - data_parallel_network=data["data_par_net"], - fused_activation=data.get("fused_activation", False), - sequence_parallel_all_gather_redo=data.get("seq_par_ag_redo", False), - data_parallel_overlap=data.get("data_par_overlap", False), - training=data.get("training", True), - attention_type=data.get("attention_type", "multihead"), - tensor_parallel_overlap=data.get("tensor_par_overlap", "none"), - weight_offload=data.get("weight_offload", False), - activation_offload=data.get("activations_offload", False), - optimizer_offload=data.get("optimizer_offload", False), ) diff --git a/src/blueprinting/workload/transformer_inference.py b/src/blueprinting/workload/transformer_inference.py index 9257656..83f4c81 100644 --- a/src/blueprinting/workload/transformer_inference.py +++ b/src/blueprinting/workload/transformer_inference.py @@ -3,10 +3,10 @@ Inference keeps three concerns separate: * :class:`TransformerModelSpec` describes model structure; -* :class:`TransformerInferenceExecutionSpec` describes a logical mapping; * :class:`TransformerInferenceRequestSpec` describes one request cohort. -The contracts describe logical mapping and one request cohort. Canonical IR +The contract describes one request cohort and its numerical representation. +Logical mapping is owned by :mod:`blueprinting.mapping`; canonical IR construction and phase binding are owned by the synthesizer frontend. """ @@ -16,12 +16,10 @@ from dataclasses import dataclass from typing import Any -from blueprinting.synthesizer.codec import record_type +from blueprinting.schema.codec import record_type from .transformer import TransformerModelSpec -_SUPPORTED_DATATYPES = frozenset({"float8", "float16", "bfloat16", "float32"}) - def _positive_integer(value: Any, name: str) -> int: if isinstance(value, bool) or not isinstance(value, int) or value <= 0: @@ -29,67 +27,6 @@ def _positive_integer(value: Any, name: str) -> int: return value -@record_type("compiler.transformer.inference_execution_spec.v1") -@dataclass(frozen=True) -class TransformerInferenceExecutionSpec: - """Target-neutral logical mapping for an inference replica.""" - - world_size: int - tensor_parallel: int - pipeline_parallel: int - replicas: int - datatype: str - tensor_parallel_network: int - pipeline_parallel_network: int - - def __post_init__(self) -> None: - for field_name in ("world_size", "tensor_parallel", "pipeline_parallel", "replicas"): - _positive_integer(getattr(self, field_name), field_name) - for field_name in ("tensor_parallel_network", "pipeline_parallel_network"): - value = getattr(self, field_name) - if isinstance(value, bool) or not isinstance(value, int) or value < 0: - raise ValueError(f"{field_name} must be a non-negative integer") - if self.world_size != self.tensor_parallel * self.pipeline_parallel * self.replicas: - raise ValueError("world_size must equal tensor_parallel * pipeline_parallel * replicas") - if self.datatype not in _SUPPORTED_DATATYPES: - raise ValueError(f"unsupported datatype: {self.datatype!r}") - - @property - def bytes_per_element(self) -> int: - return {"float8": 1, "float16": 2, "bfloat16": 2, "float32": 4}[self.datatype] - - def validate_model(self, model: TransformerModelSpec) -> None: - """Reject mappings whose local tensor shapes are not integral.""" - - divisibility = { - "hidden_size": model.hidden_size, - "feedforward_size": model.feedforward_size, - "attention_heads": model.attention_heads, - } - for name, value in divisibility.items(): - if value % self.tensor_parallel: - raise ValueError(f"{name} must be divisible by tensor_parallel") - if self.pipeline_parallel > model.block_count: - raise ValueError("pipeline_parallel cannot exceed block_count") - if model.block_count % self.pipeline_parallel: - raise ValueError("pipeline_parallel must divide block_count for static inference planning") - - @classmethod - def from_mapping(cls, data: Mapping[str, Any]) -> TransformerInferenceExecutionSpec: - replicas = data.get("replicas", data.get("data_par", 1)) - tensor_parallel = data["tensor_par"] - pipeline_parallel = data["pipeline_par"] - return cls( - world_size=data.get("num_procs", tensor_parallel * pipeline_parallel * replicas), - tensor_parallel=tensor_parallel, - pipeline_parallel=pipeline_parallel, - replicas=replicas, - datatype=data["datatype"], - tensor_parallel_network=data.get("tensor_par_net", 0), - pipeline_parallel_network=data.get("pipeline_par_net", 0), - ) - - @record_type("compiler.transformer.inference_request_spec.v1") @dataclass(frozen=True) class TransformerInferenceRequestSpec: @@ -98,10 +35,17 @@ class TransformerInferenceRequestSpec: batch_size: int prompt_tokens: int generated_tokens: int + datatype: str = "float16" def __post_init__(self) -> None: for field_name in ("batch_size", "prompt_tokens", "generated_tokens"): _positive_integer(getattr(self, field_name), field_name) + if self.datatype not in {"float8", "float16", "bfloat16", "float32"}: + raise ValueError(f"unsupported datatype: {self.datatype!r}") + + @property + def bytes_per_element(self) -> int: + return {"float8": 1, "float16": 2, "bfloat16": 2, "float32": 4}[self.datatype] @property def decode_iterations(self) -> int: @@ -128,4 +72,5 @@ def from_mapping(cls, data: Mapping[str, Any]) -> TransformerInferenceRequestSpe batch_size=data["batch_size"], prompt_tokens=data["prompt_tokens"], generated_tokens=data["generated_tokens"], + datatype=data.get("datatype", "float16"), ) diff --git a/tests/analysis/test_cost_model_providers.py b/tests/analysis/test_cost_model_providers.py index e00bf68..a2f5996 100644 --- a/tests/analysis/test_cost_model_providers.py +++ b/tests/analysis/test_cost_model_providers.py @@ -26,19 +26,17 @@ estimate_inference_phase, ) from blueprinting.analysis.cost import InvalidCostEvidenceError +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec +from blueprinting.schema.frozen import FrozenDict from blueprinting.synthesizer.bindings import InferencePhase from blueprinting.synthesizer.frontend import ( build_transformer_inference_model_ir, inference_synthesis_session_for, ) -from blueprinting.synthesizer.frozen import FrozenDict from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass from blueprinting.synthesizer.passes import PassManager, PassPipeline from blueprinting.system import SystemProfile -from blueprinting.workload import ( - TransformerInferenceExecutionSpec, - TransformerModelSpec, -) +from blueprinting.workload import TransformerModelSpec ROOT = Path(__file__).resolve().parents[2] @@ -398,14 +396,10 @@ def _inference_fixture(): attention_head_size=8, block_count=8, ) - execution = TransformerInferenceExecutionSpec( - world_size=4, + mapping = TransformerInferenceMappingSpec( tensor_parallel=2, pipeline_parallel=2, replicas=1, - datatype="float16", - tensor_parallel_network=0, - pipeline_parallel_network=0, ) plan = ( PassManager() @@ -414,25 +408,29 @@ def _inference_fixture(): build_transformer_inference_model_ir(model), session=inference_synthesis_session_for( model, - execution, + mapping, phase=InferencePhase.DECODE, batch_size=3, context_tokens=96, + datatype="float16", ), ) .ir ) - return model, execution, plan + return model, mapping, plan def test_inference_costing_uses_database_then_explicit_roofline_fallback(): - model, execution, plan = _inference_fixture() + model, mapping, plan = _inference_fixture() hardware = _hardware() + network_binding = NetworkTierBinding() attention_task = next(task for task in plan.tasks if task.workload.attributes.get("primitive") == "attention_core") query = cost_query_for_inference_task( attention_task, hardware=hardware, - execution=execution, + mapping=mapping, + network_binding=network_binding, + datatype="float16", model=model, batch_size=3, query_tokens=1, @@ -444,7 +442,7 @@ def test_inference_costing_uses_database_then_explicit_roofline_fallback(): CostSubject.OPERATOR, "attention_core", hardware.name, - execution.datatype, + "float16", 0.123, FrozenDict( { @@ -467,6 +465,7 @@ def test_inference_costing_uses_database_then_explicit_roofline_fallback(): plan, hardware, mode=CalibrationMode.PEAK_ONLY, + network_binding=network_binding, cost_resolver=resolver, ) attention = next(item for item in estimate.tasks if item.invocation.primitive == "attention_core") diff --git a/tests/analysis/test_domain_contracts.py b/tests/analysis/test_domain_contracts.py new file mode 100644 index 0000000..627c1c9 --- /dev/null +++ b/tests/analysis/test_domain_contracts.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import pytest + +from blueprinting.mapping import ( + NetworkTierBinding, + TransformerInferenceMappingSpec, + TransformerTrainingMappingSpec, +) +from blueprinting.workload import TransformerTrainingWorkloadSpec + + +def test_legacy_aliases_may_match_but_cannot_define_conflicting_truth() -> None: + training = { + "tensor_parallel": 2, + "tensor_par": 2, + "pipeline_parallel": 1, + "pipeline_par": 1, + "data_parallel": 2, + "data_par": 2, + "recompute": "none", + "activation_recompute": "none", + "pipeline_interleaving": 1, + "optimizer_sharding": False, + "tensor_parallel_communication": "ar", + "tensor_par_comm_type": "ar", + } + + assert TransformerTrainingMappingSpec.from_mapping(training).world_size == 4 + with pytest.raises(ValueError, match="tensor_parallel conflicts"): + TransformerTrainingMappingSpec.from_mapping({**training, "tensor_par": 4}) + + assert ( + TransformerInferenceMappingSpec.from_mapping( + {"tensor_parallel": 2, "pipeline_parallel": 1, "replicas": 2, "data_par": 2} + ).world_size + == 4 + ) + with pytest.raises(ValueError, match="replicas conflicts"): + TransformerInferenceMappingSpec.from_mapping( + {"tensor_parallel": 2, "pipeline_parallel": 1, "replicas": 2, "data_par": 4} + ) + + +def test_workload_and_network_aliases_reject_conflicts() -> None: + assert ( + TransformerTrainingWorkloadSpec.from_mapping( + {"global_batch_size": 8, "batch_size": 8, "microbatch_size": 1, "datatype": "float16"} + ).global_batch_size + == 8 + ) + with pytest.raises(ValueError, match="global_batch_size conflicts"): + TransformerTrainingWorkloadSpec.from_mapping( + {"global_batch_size": 8, "batch_size": 16, "microbatch_size": 1, "datatype": "float16"} + ) + + assert NetworkTierBinding.from_mapping({"tensor_parallel_network": 1, "tensor_par_net": 1}).tensor_parallel == 1 + with pytest.raises(ValueError, match="tensor_parallel_network conflicts"): + NetworkTierBinding.from_mapping({"tensor_parallel_network": 1, "tensor_par_net": 0}) diff --git a/tests/analysis/test_package_boundary.py b/tests/analysis/test_package_boundary.py index 4d479b9..24802b3 100644 --- a/tests/analysis/test_package_boundary.py +++ b/tests/analysis/test_package_boundary.py @@ -1,12 +1,15 @@ from __future__ import annotations import ast +import importlib import importlib.util from pathlib import Path import pytest import blueprinting.analysis as analysis +import blueprinting.mapping as mapping +import blueprinting.schema as schema import blueprinting.synthesizer as synthesizer import blueprinting.synthesizer.frontend as frontend import blueprinting.system as system @@ -15,18 +18,37 @@ PACKAGE_ROOT = Path(__file__).resolve().parents[2] / "src" / "blueprinting" -def _absolute_imports(package: str) -> frozenset[str]: +def _imports(package: str) -> frozenset[str]: imports = set() for source in (PACKAGE_ROOT / package).rglob("*.py"): + relative = source.relative_to(PACKAGE_ROOT).with_suffix("") + module_parts = ("blueprinting", *relative.parts) + if module_parts[-1] == "__init__": + module_parts = module_parts[:-1] + module = ".".join(module_parts) + package_context = module if source.name == "__init__.py" else module.rpartition(".")[0] tree = ast.parse(source.read_text(encoding="utf-8"), filename=str(source)) for node in ast.walk(tree): if isinstance(node, ast.Import): imports.update(alias.name for alias in node.names) - elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module: - imports.add(node.module) + elif isinstance(node, ast.ImportFrom): + if node.level: + relative_name = "." * node.level + (node.module or "") + imports.add(importlib.util.resolve_name(relative_name, package_context)) + elif node.module: + imports.add(node.module) return frozenset(imports) +def _assert_only_domain_dependencies(package: str, allowed: tuple[str, ...]) -> None: + illegal = sorted( + name + for name in _imports(package) + if name.startswith("blueprinting.") and not name.startswith(tuple(f"blueprinting.{item}" for item in allowed)) + ) + assert illegal == [] + + def test_analysis_is_a_top_level_blueprinting_package() -> None: assert analysis.__name__ == "blueprinting.analysis" assert importlib.util.find_spec("blueprinting.analysis") is not None @@ -51,20 +73,38 @@ def test_workload_and_system_are_top_level_domain_packages() -> None: assert system.__name__ == "blueprinting.system" assert importlib.util.find_spec("blueprinting.workload") is not None assert importlib.util.find_spec("blueprinting.system") is not None + assert mapping.__name__ == "blueprinting.mapping" + assert schema.__name__ == "blueprinting.schema" assert importlib.util.find_spec("blueprinting.synthesizer.models") is None def test_domain_ownership_is_not_hidden_by_compatibility_reexports() -> None: assert hasattr(workload, "TransformerModelSpec") + assert not hasattr(workload, "TransformerTrainingMappingSpec") + assert hasattr(mapping, "TransformerTrainingMappingSpec") + assert hasattr(mapping, "NetworkTierBinding") assert not hasattr(workload, "build_transformer_model_ir") assert hasattr(frontend, "build_transformer_model_ir") assert hasattr(system, "SystemProfile") assert not hasattr(analysis, "SystemProfile") - - -def test_domain_packages_do_not_depend_on_each_other_or_analysis_policy() -> None: - workload_imports = _absolute_imports("workload") - system_imports = _absolute_imports("system") - - assert not any(name.startswith(("blueprinting.analysis", "blueprinting.system")) for name in workload_imports) - assert not any(name.startswith(("blueprinting.analysis", "blueprinting.workload")) for name in system_imports) + assert not hasattr(analysis, "PrimitiveInvocation") + + +def test_supported_architecture_dependencies_are_acyclic_and_layered() -> None: + _assert_only_domain_dependencies("schema", ("schema",)) + _assert_only_domain_dependencies("workload", ("schema", "workload")) + _assert_only_domain_dependencies("mapping", ("schema", "workload", "mapping")) + _assert_only_domain_dependencies("system", ("schema", "system")) + _assert_only_domain_dependencies( + "synthesizer", + ("schema", "workload", "mapping", "synthesizer"), + ) + _assert_only_domain_dependencies( + "analysis", + ("schema", "workload", "mapping", "system", "synthesizer", "analysis"), + ) + + +def test_validation_is_outside_the_synthesizer_dependency_closure() -> None: + assert importlib.util.find_spec("blueprinting.validation") is not None + assert not any(name.startswith("blueprinting.validation") for name in _imports("synthesizer")) diff --git a/tests/analysis/test_system_profile.py b/tests/analysis/test_system_profile.py index 077d465..53b9283 100644 --- a/tests/analysis/test_system_profile.py +++ b/tests/analysis/test_system_profile.py @@ -5,7 +5,7 @@ import pytest -from blueprinting.synthesizer.codec import canonical_dumps, canonical_loads +from blueprinting.schema.codec import canonical_dumps, canonical_loads from blueprinting.system import SystemProfile ROOT = Path(__file__).resolve().parents[2] diff --git a/tests/application/test_analysis_service.py b/tests/application/test_analysis_service.py index 9eb636b..cee906d 100644 --- a/tests/application/test_analysis_service.py +++ b/tests/application/test_analysis_service.py @@ -4,8 +4,8 @@ from blueprinting.analysis import CalibrationMode from blueprinting.application import AnalysisDraft, BlueprintingService, SweepRequest +from blueprinting.schema.frozen import FrozenDict from blueprinting.synthesizer.frontend import build_transformer_model_ir -from blueprinting.synthesizer.frozen import FrozenDict from blueprinting.workbench import default_catalog from blueprinting.workload import TransformerModelSpec @@ -73,6 +73,34 @@ def test_analysis_service_is_the_complete_client_boundary() -> None: assert report.evidence_revision == report.evidence["revision"] +def test_analysis_service_accepts_canonical_mapping_names_and_sweeps_them() -> None: + legacy = _draft() + execution = dict(legacy.execution_data.items()) + for canonical, alias in ( + ("tensor_parallel", "tensor_par"), + ("pipeline_parallel", "pipeline_par"), + ("data_parallel", "data_par"), + ("recompute", "activation_recompute"), + ("tensor_parallel_communication", "tensor_par_comm_type"), + ): + execution[canonical] = execution.pop(alias) + canonical = AnalysisDraft.from_mappings( + model_name=legacy.model_name, + model_data=dict(legacy.model_data.items()), + execution_name=legacy.execution_name, + execution_data=execution, + hardware_name=legacy.hardware_name, + hardware_data=dict(legacy.hardware_data.items()), + ) + + outcome = BlueprintingService().analyze(canonical.with_parallelism(4, 1, 2)) + + assert outcome.ok + assert outcome.report is not None + assert outcome.report.world_size == 8 + assert outcome.report.configuration["execution"]["tensor_parallel"] == 4 + + def test_expected_configuration_failure_is_a_diagnostic() -> None: outcome = BlueprintingService().analyze(_draft(batch_size=7)) diff --git a/tests/application/test_cli.py b/tests/application/test_cli.py new file mode 100644 index 0000000..1efbf37 --- /dev/null +++ b/tests/application/test_cli.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import blueprinting +from blueprinting.cli import build_parser, main + + +def test_package_root_exposes_only_version_metadata() -> None: + assert blueprinting.__all__ == ["__version__"] + assert isinstance(blueprinting.__version__, str) + assert blueprinting.__version__ + assert not hasattr(blueprinting, "Analyzer") + assert not hasattr(blueprinting, "System") + + +def test_cli_version_command_has_no_application_side_effects(capsys) -> None: + assert main(["version"]) == 0 + + assert capsys.readouterr().out.strip() == blueprinting.__version__ + + +def test_cli_parses_workbench_binding_without_importing_the_ui() -> None: + arguments = build_parser().parse_args(["workbench", "--host", "0.0.0.0", "--port", "9000", "--no-open", "--reload"]) + + assert arguments.command == "workbench" + assert arguments.host == "0.0.0.0" + assert arguments.port == 9000 + assert arguments.no_open is True + assert arguments.reload is True diff --git a/tests/application/test_inference_analysis_service.py b/tests/application/test_inference_analysis_service.py index 21970d0..d85b9f2 100644 --- a/tests/application/test_inference_analysis_service.py +++ b/tests/application/test_inference_analysis_service.py @@ -66,6 +66,28 @@ def test_single_generated_token_stops_after_prefill(): assert outcome.report.model_execution_seconds == outcome.report.prefill_seconds +def test_service_accepts_canonical_inference_mapping_names(): + legacy = _draft(generated_tokens=1) + execution = dict(legacy.execution_data.items()) + execution["tensor_parallel"] = execution.pop("tensor_par") + execution["pipeline_parallel"] = execution.pop("pipeline_par") + canonical = InferenceAnalysisDraft.from_mappings( + model_name=legacy.model_name, + model_data=dict(legacy.model_data.items()), + execution_name=legacy.execution_name, + execution_data=execution, + request_data=dict(legacy.request_data.items()), + hardware_name=legacy.hardware_name, + hardware_data=dict(legacy.hardware_data.items()), + ) + + outcome = BlueprintingService().analyze_inference(canonical) + + assert outcome.ok + assert outcome.report is not None + assert outcome.report.world_size == 8 + + def test_request_past_model_context_returns_a_structured_diagnostic(): draft = _draft(generated_tokens=4) request = dict(draft.request_data.items()) diff --git a/tests/regression/test_baseline_quality_gate.py b/tests/regression/test_baseline_quality_gate.py index 481dce0..91fdcba 100644 --- a/tests/regression/test_baseline_quality_gate.py +++ b/tests/regression/test_baseline_quality_gate.py @@ -4,7 +4,7 @@ import pytest -from blueprinting.synthesizer.experiments import ( +from blueprinting.validation import ( BaselineRegressionGate, RegressionCheck, run_inference_baseline_regression, diff --git a/tests/synthesizer/conftest.py b/tests/synthesizer/conftest.py index 4e94ad0..a5b87ce 100644 --- a/tests/synthesizer/conftest.py +++ b/tests/synthesizer/conftest.py @@ -2,7 +2,7 @@ import pytest -from blueprinting.synthesizer import FrozenDict +from blueprinting.schema import FrozenDict from blueprinting.synthesizer.ids import ( BufferId, CommandId, diff --git a/tests/synthesizer/test_bindings.py b/tests/synthesizer/test_bindings.py index 92e48bd..021bf11 100644 --- a/tests/synthesizer/test_bindings.py +++ b/tests/synthesizer/test_bindings.py @@ -2,20 +2,17 @@ import pytest +from blueprinting.schema import FrozenDict, SerializationError, canonical_dumps, canonical_loads from blueprinting.synthesizer import ( BindingAxis, BindingError, DeploymentProfile, - FrozenDict, - SerializationError, Symbol, SynthesisSession, TargetProfile, TargetRequirements, WorkloadBinding, WorkloadMode, - canonical_dumps, - canonical_loads, ) from blueprinting.synthesizer.ir import PortablePlanIR diff --git a/tests/synthesizer/test_canonical_ir.py b/tests/synthesizer/test_canonical_ir.py index c59beca..43a9489 100644 --- a/tests/synthesizer/test_canonical_ir.py +++ b/tests/synthesizer/test_canonical_ir.py @@ -5,8 +5,8 @@ import pytest -from blueprinting.synthesizer import FrozenDict, NodeId, SerializationError -from blueprinting.synthesizer.codec import canonical_dumps, canonical_loads, record_type +from blueprinting.schema import FrozenDict, SerializationError, canonical_dumps, canonical_loads, record_type +from blueprinting.synthesizer import NodeId from blueprinting.synthesizer.ir import ( ConcretePlanIR, DistributedTaskIR, diff --git a/tests/synthesizer/test_pass_manager.py b/tests/synthesizer/test_pass_manager.py index ed85413..80c9b5e 100644 --- a/tests/synthesizer/test_pass_manager.py +++ b/tests/synthesizer/test_pass_manager.py @@ -4,9 +4,9 @@ import pytest +from blueprinting.schema import FrozenDict from blueprinting.synthesizer import ( BindingAxis, - FrozenDict, MissingAnalysisError, MissingBindingError, PassContractError, diff --git a/tests/synthesizer/test_transformer_inference.py b/tests/synthesizer/test_transformer_inference.py index 7597a32..6855571 100644 --- a/tests/synthesizer/test_transformer_inference.py +++ b/tests/synthesizer/test_transformer_inference.py @@ -11,12 +11,8 @@ VidurProfileBaseline, estimate_inference_phase, ) +from blueprinting.mapping import NetworkTierBinding, TransformerInferenceMappingSpec from blueprinting.synthesizer.bindings import InferencePhase -from blueprinting.synthesizer.experiments import ( - VidurExperimentCase, - compare_inference_phase_to_vidur, - run_vidur_experiment, -) from blueprinting.synthesizer.frontend import ( build_transformer_inference_model_ir, inference_synthesis_session_for, @@ -24,8 +20,12 @@ from blueprinting.synthesizer.lowering import DistributeTransformerInferencePass, PlanTransformerInferencePass from blueprinting.synthesizer.passes import PassManager, PassPipeline from blueprinting.system import SystemProfile +from blueprinting.validation import ( + VidurExperimentCase, + compare_inference_phase_to_vidur, + run_vidur_experiment, +) from blueprinting.workload import ( - TransformerInferenceExecutionSpec, TransformerInferenceRequestSpec, TransformerModelSpec, ) @@ -45,15 +45,11 @@ def _model() -> TransformerModelSpec: ) -def _execution() -> TransformerInferenceExecutionSpec: - return TransformerInferenceExecutionSpec( - world_size=4, +def _execution() -> TransformerInferenceMappingSpec: + return TransformerInferenceMappingSpec( tensor_parallel=2, pipeline_parallel=2, replicas=1, - datatype="float16", - tensor_parallel_network=0, - pipeline_parallel_network=0, ) @@ -70,6 +66,7 @@ def _derive(phase: InferencePhase, context_tokens: int): phase=phase, batch_size=3, context_tokens=context_tokens, + datatype="float16", ), ) return source, result.ir @@ -88,6 +85,8 @@ def test_inference_lowering_has_valid_auditable_phase_plans(phase: InferencePhas assert plan.attributes["inference_phase"] is phase assert all("invocation" not in task.attributes for task in plan.tasks) assert all(task.workload.attributes.get("phase") == phase.value for task in plan.tasks) + assert all("network_tier" not in task.workload.attributes for task in plan.tasks) + assert all("network_tier" not in resource.capabilities for task in plan.tasks for resource in task.resources) assert {buffer.attributes.get("semantic") for buffer in plan.buffers} >= { "block_weights", "block_working_upper_bound", @@ -123,6 +122,22 @@ def test_kv_cache_capacity_is_derived_from_shape_not_a_correction_factor(): assert workspace.size_bytes > 0 +def test_network_tier_binding_changes_cost_without_changing_portable_plan(): + _, plan = _derive(InferencePhase.DECODE, 96) + hardware = SystemProfile.from_mapping( + "fixture-hardware", + json.loads((ROOT / "data" / "systems" / "a100_80g.json").read_text(encoding="utf-8")), + datatype="float16", + ) + digest = plan.digest + + fast = estimate_inference_phase(plan, hardware, network_binding=NetworkTierBinding(tensor_parallel=0)) + slow = estimate_inference_phase(plan, hardware, network_binding=NetworkTierBinding(tensor_parallel=1)) + + assert fast.total_seconds != slow.total_seconds + assert plan.digest == digest + + def test_request_semantics_count_prefill_as_the_first_output_token(): one = TransformerInferenceRequestSpec(batch_size=1, prompt_tokens=64, generated_tokens=1) four = TransformerInferenceRequestSpec(batch_size=1, prompt_tokens=64, generated_tokens=4) @@ -135,14 +150,10 @@ def test_request_semantics_count_prefill_as_the_first_output_token(): def test_invalid_mapping_is_rejected_before_lowering(): model = _model() - invalid = TransformerInferenceExecutionSpec( - world_size=3, + invalid = TransformerInferenceMappingSpec( tensor_parallel=3, pipeline_parallel=1, replicas=1, - datatype="float16", - tensor_parallel_network=0, - pipeline_parallel_network=0, ) with pytest.raises(ValueError, match="hidden_size must be divisible"): @@ -221,7 +232,8 @@ def test_vidur_adapter_uses_only_exact_shape_matches(tmp_path: Path): datatype="float16", ) digest_before = plan.digest - estimate = estimate_inference_phase(plan, hardware) + network_binding = NetworkTierBinding() + estimate = estimate_inference_phase(plan, hardware, network_binding=network_binding) attention_estimate = next(item for item in estimate.tasks if item.invocation.primitive == "attention_core") comparison = compare_inference_phase_to_vidur(plan, estimate, hardware, baseline) attention_comparison = next(item for item in comparison.components if item.primitive == "attention_core") @@ -239,7 +251,9 @@ def test_vidur_adapter_uses_only_exact_shape_matches(tmp_path: Path): VidurExperimentCase( name="fixture/decode-96", model=_model(), - execution=_execution(), + mapping=_execution(), + network_binding=network_binding, + datatype="float16", hardware=hardware, phase=InferencePhase.DECODE, batch_size=3, diff --git a/tests/synthesizer/test_verifiers.py b/tests/synthesizer/test_verifiers.py index 18369c7..5a36f5d 100644 --- a/tests/synthesizer/test_verifiers.py +++ b/tests/synthesizer/test_verifiers.py @@ -2,7 +2,8 @@ from dataclasses import fields, replace -from blueprinting.synthesizer import BufferId, FrozenDict, NodeId, ValueId +from blueprinting.schema import FrozenDict +from blueprinting.synthesizer import BufferId, NodeId, ValueId from blueprinting.synthesizer.ir import ( ConcretePlanIR, DistributedTaskIR, diff --git a/tests/validations/cases/seqsel_fig1_test.py b/tests/validation/legacy/test_seqsel_fig1.py old mode 100755 new mode 100644 similarity index 72% rename from tests/validations/cases/seqsel_fig1_test.py rename to tests/validation/legacy/test_seqsel_fig1.py index 0f0dcd8..d960e19 --- a/tests/validations/cases/seqsel_fig1_test.py +++ b/tests/validation/legacy/test_seqsel_fig1.py @@ -1,8 +1,8 @@ from blueprinting.console import print_rich_table -def test_sseqsel_fig1(): - from blueprinting.validations.cases.seqsel_fig1 import seqsel_fig1 +def test_seqsel_fig1(): + from blueprinting.validation.legacy.seqsel_fig1 import seqsel_fig1 ret = seqsel_fig1() print_rich_table(ret, caption="w+opt mem & act mem") diff --git a/tests/validations/cases/seqsel_fig7_test.py b/tests/validation/legacy/test_seqsel_fig7.py old mode 100755 new mode 100644 similarity index 66% rename from tests/validations/cases/seqsel_fig7_test.py rename to tests/validation/legacy/test_seqsel_fig7.py index fe46c95..dbd7753 --- a/tests/validations/cases/seqsel_fig7_test.py +++ b/tests/validation/legacy/test_seqsel_fig7.py @@ -1,8 +1,8 @@ from blueprinting.console import print_rich_table -def test_sseqsel_fig7(): - from blueprinting.validations.cases.seqsel_fig7 import seqsel_fig7 +def test_seqsel_fig7(): + from blueprinting.validation.legacy.seqsel_fig7 import seqsel_fig7 ret = seqsel_fig7() print_rich_table(ret, caption="act mem") diff --git a/tests/validations/cases/seqsel_tab5_test.py b/tests/validation/legacy/test_seqsel_tab5.py old mode 100755 new mode 100644 similarity index 66% rename from tests/validations/cases/seqsel_tab5_test.py rename to tests/validation/legacy/test_seqsel_tab5.py index b9f78ef..78019bd --- a/tests/validations/cases/seqsel_tab5_test.py +++ b/tests/validation/legacy/test_seqsel_tab5.py @@ -1,8 +1,8 @@ from blueprinting.console import print_rich_table -def test_sseqsel_tab5(): - from blueprinting.validations.cases.seqsel_tab5 import seqsel_tab5 +def test_seqsel_tab5(): + from blueprinting.validation.legacy.seqsel_tab5 import seqsel_tab5 ret = seqsel_tab5() print_rich_table(ret, caption="iter time") diff --git a/tests/synthesizer/test_calculon_calibration.py b/tests/validation/test_calculon.py similarity index 58% rename from tests/synthesizer/test_calculon_calibration.py rename to tests/validation/test_calculon.py index 8d7ed5a..8336ac6 100644 --- a/tests/synthesizer/test_calculon_calibration.py +++ b/tests/validation/test_calculon.py @@ -6,13 +6,14 @@ import pytest from blueprinting.analysis.cost_model import CalibrationMode, estimate_iteration -from blueprinting.analysis.transformer_workload import EngineKind, PrimitiveInvocation, TrainingPhase -from blueprinting.synthesizer.experiments import discover_seqsel_tab5_cases, run_calculon_experiment +from blueprinting.mapping import NetworkTierBinding, TransformerTrainingMappingSpec +from blueprinting.synthesizer.dialects.transformer import EngineKind, TrainingPhase from blueprinting.synthesizer.frontend import build_transformer_model_ir, synthesis_session_for from blueprinting.synthesizer.lowering import DistributeTransformerTrainingPass, PlanTransformerTrainingPass from blueprinting.synthesizer.passes import PassManager, PassPipeline from blueprinting.system import SystemProfile -from blueprinting.workload import TransformerExecutionSpec, TransformerModelSpec +from blueprinting.validation import discover_seqsel_tab5_cases, run_calculon_experiment +from blueprinting.workload import TransformerModelSpec, TransformerTrainingWorkloadSpec ROOT = Path(__file__).resolve().parents[2] @@ -26,37 +27,41 @@ def _derive(model_name: str, mode: str): model_data = _json(ROOT / "data" / "models" / f"{model_name}.json") execution_data = _json(ROOT / "data" / "validation" / "seqsel" / "tab5" / f"{model_name}_{mode}.json") model = TransformerModelSpec.from_mapping(model_name, model_data) - execution = TransformerExecutionSpec.from_mapping(execution_data) - source = build_transformer_model_ir(model) + workload = TransformerTrainingWorkloadSpec.from_mapping(execution_data) + mapping = TransformerTrainingMappingSpec.from_mapping(execution_data) + network_binding = NetworkTierBinding.from_mapping(execution_data) + source = build_transformer_model_ir(model, datatype=workload.datatype) result = PassManager().run( PassPipeline.of(DistributeTransformerTrainingPass(), PlanTransformerTrainingPass()), source, - session=synthesis_session_for(model, execution), + session=synthesis_session_for(model, workload, mapping), ) - return model, execution, source, result + return model, workload, mapping, network_binding, source, result def test_transformer_lowering_produces_auditable_ir_checkpoints(): - _, _, source, result = _derive("gpt3-175B", "seqsel") + _, _, _, _, source, result = _derive("gpt3-175B", "seqsel") assert source.require_valid() is None assert tuple(record.pass_name for record in result.records) == ( - "transformer-distribute-v1", - "transformer-plan-work-v1", + "transformer-distribute-v2", + "transformer-plan-work-v2", ) assert tuple(checkpoint.ir.header.schema_name for checkpoint in result.checkpoints) == ( "blueprinting.distributed-task", "blueprinting.portable-plan", ) assert result.ir.require_valid() is None - assert all(isinstance(task.attributes["invocation"], PrimitiveInvocation) for task in result.ir.tasks) + assert all("invocation" not in task.attributes for task in result.ir.tasks) + assert all("network_tier" not in resource.capabilities for task in result.ir.tasks for resource in task.resources) def test_selective_recompute_is_structural_and_linear_gradients_are_derived(): - _, _, _, result = _derive("gpt3-175B", "seqsel") - invocations = tuple(task.attributes["invocation"] for task in result.ir.tasks) + _, _, _, _, _, result = _derive("gpt3-175B", "seqsel") recomputed_layers = { - invocation.source_layer for invocation in invocations if invocation.phase is TrainingPhase.RECOMPUTE + task.workload.attributes["source_layer"] + for task in result.ir.tasks + if task.workload.attributes["phase"] == TrainingPhase.RECOMPUTE.value } assert recomputed_layers == { @@ -65,27 +70,50 @@ def test_selective_recompute_is_structural_and_linear_gradients_are_derived(): "attention.probability_dropout", } query = { - invocation.phase: invocation.work.operations - for invocation in invocations - if invocation.source_layer == "attention.query" and invocation.engine is EngineKind.MATRIX + TrainingPhase(task.workload.attributes["phase"]): task.workload.operations + for task in result.ir.tasks + if task.workload.attributes["source_layer"] == "attention.query" + and task.workload.attributes["engine"] == EngineKind.MATRIX.value } assert query[TrainingPhase.FORWARD] == query[TrainingPhase.ACTIVATION_GRADIENT] assert query[TrainingPhase.FORWARD] == query[TrainingPhase.WEIGHT_GRADIENT] def test_hardware_evidence_is_shared_and_does_not_change_workload(): - _, execution, _, result = _derive("gpt3-175B", "full") + _, workload, _, network_binding, _, result = _derive("gpt3-175B", "full") hardware = SystemProfile.from_mapping( "a100_80g", _json(ROOT / "data" / "systems" / "a100_80g.json"), - datatype=execution.datatype, + datatype=workload.datatype, ) digests_before = tuple(task.workload for task in result.ir.tasks) - peak = estimate_iteration(result.ir, hardware, CalibrationMode.PEAK_ONLY) - calibrated = estimate_iteration(result.ir, hardware, CalibrationMode.SYSTEM_EVIDENCE) + peak = estimate_iteration( + result.ir, + hardware, + CalibrationMode.PEAK_ONLY, + network_binding=network_binding, + ) + calibrated = estimate_iteration( + result.ir, + hardware, + CalibrationMode.SYSTEM_EVIDENCE, + network_binding=network_binding, + ) + alternate_network = NetworkTierBinding( + tensor_parallel=1, + pipeline_parallel=network_binding.pipeline_parallel, + data_parallel=network_binding.data_parallel, + ) + alternate = estimate_iteration( + result.ir, + hardware, + CalibrationMode.SYSTEM_EVIDENCE, + network_binding=alternate_network, + ) assert peak.total < calibrated.total + assert alternate.tensor_parallel != calibrated.tensor_parallel assert tuple(task.workload for task in result.ir.tasks) == digests_before assert result.ir.attributes.get("duration") is None diff --git a/tests/workbench/test_catalog.py b/tests/workbench/test_catalog.py new file mode 100644 index 0000000..c077057 --- /dev/null +++ b/tests/workbench/test_catalog.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from blueprinting.workbench.catalog import default_catalog + + +def test_default_catalog_exposes_packaged_model_and_system_presets() -> None: + catalog = default_catalog() + + assert "gpt3-175B.json" in catalog.names("models") + assert "a100_80g.json" in catalog.names("systems") + assert catalog.load("models", "gpt3-175B.json")["hidden"] > 0 + assert catalog.load("systems", "a100_80g.json")["mem1"]["GiB"] == 80 From 0e99151993ac9eecd138f88a57fada1ce115f433 Mon Sep 17 00:00:00 2001 From: Reiase Date: Sun, 9 Aug 2026 19:13:13 +0800 Subject: [PATCH 6/6] refactor(workbench): redesign the architecture workspace --- src/blueprinting/workbench/nicegui_app.py | 2 +- src/blueprinting/workbench/nicegui_theme.py | 1457 +++++++++++-- src/blueprinting/workbench/nicegui_ui.py | 2072 ++++++++++++++----- src/blueprinting/workbench/presentation.py | 616 +++++- tests/workbench/test_nicegui_workbench.py | 80 +- tests/workbench/test_presentation.py | 175 ++ 6 files changed, 3599 insertions(+), 803 deletions(-) create mode 100644 tests/workbench/test_presentation.py diff --git a/src/blueprinting/workbench/nicegui_app.py b/src/blueprinting/workbench/nicegui_app.py index dbef18c..337a6aa 100644 --- a/src/blueprinting/workbench/nicegui_app.py +++ b/src/blueprinting/workbench/nicegui_app.py @@ -35,7 +35,7 @@ def run_workbench( port=port, title="Blueprinting · Architecture Workbench", favicon="🧭", - dark=True, + dark=False, language="zh-CN", show=show, reload=reload, diff --git a/src/blueprinting/workbench/nicegui_theme.py b/src/blueprinting/workbench/nicegui_theme.py index 5f1fbed..a0c8cda 100644 --- a/src/blueprinting/workbench/nicegui_theme.py +++ b/src/blueprinting/workbench/nicegui_theme.py @@ -1,307 +1,1110 @@ -"""Blueprinting visual tokens for the NiceGUI workbench.""" +"""Probing-inspired evidence-workspace theme for the NiceGUI workbench.""" WORKBENCH_CSS = r""" :root { - --bp-bg: #070a12; - --bp-surface: rgba(15, 20, 34, .82); - --bp-surface-strong: rgba(20, 27, 45, .96); - --bp-border: rgba(148, 163, 184, .14); - --bp-border-bright: rgba(106, 119, 255, .42); - --bp-text: #edf2ff; - --bp-muted: #8d9ab4; - --bp-primary: #7c68ff; - --bp-cyan: #22d3ee; - --bp-violet: #a78bfa; - --bp-amber: #f59e0b; - --bp-danger: #fb7185; - --bp-success: #34d399; - --bp-radius: 18px; - --bp-shadow: 0 22px 70px rgba(0, 0, 0, .28); -} - -html, body, #app, .q-layout { + --bp-canvas: #f4f6f8; + --bp-panel: #ffffff; + --bp-panel-soft: #f8fafc; + --bp-line: #e2e8f0; + --bp-line-strong: #cbd5e1; + --bp-text: #111827; + --bp-text-soft: #334155; + --bp-muted: #64748b; + --bp-blue: #2563eb; + --bp-cyan: #0891b2; + --bp-green: #15803d; + --bp-amber: #b45309; + --bp-red: #b91c1c; + --bp-sidebar: #020617; + --bp-sidebar-panel: #0f172a; + --bp-sidebar-line: #1e293b; + --bp-sidebar-text: #f8fafc; + --bp-sidebar-muted: #94a3b8; + --bp-radius: 8px; +} + +html, +body, +#app, +.q-layout { min-height: 100%; color: var(--bp-text); - background: - radial-gradient(circle at 74% -10%, rgba(88, 77, 255, .18), transparent 36%), - radial-gradient(circle at 12% 38%, rgba(34, 211, 238, .07), transparent 28%), - var(--bp-bg); + background: var(--bp-canvas); } body { + margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; + -webkit-font-smoothing: antialiased; } .nicegui-content { padding: 0; } -.bp-header { - min-height: 70px; - padding: 0 22px; - background: rgba(7, 10, 18, .78) !important; - border-bottom: 1px solid var(--bp-border); - backdrop-filter: blur(22px); +*:focus-visible { + outline: 2px solid var(--bp-blue) !important; + outline-offset: 2px; +} + +.bp-mobile-bar { + display: none !important; } .bp-brand-mark { - width: 38px; - height: 38px; - display: grid; - place-items: center; - border: 1px solid rgba(124, 104, 255, .58); - border-radius: 12px; - background: linear-gradient(145deg, rgba(124, 104, 255, .32), rgba(34, 211, 238, .08)); - box-shadow: 0 0 30px rgba(124, 104, 255, .22), inset 0 1px 0 rgba(255, 255, 255, .12); + position: relative; + width: 34px; + height: 34px; + flex: 0 0 auto; + border: 1px solid rgba(96, 165, 250, .5); + border-radius: 8px; + background: + linear-gradient(rgba(96, 165, 250, .13) 1px, transparent 1px), + linear-gradient(90deg, rgba(96, 165, 250, .13) 1px, transparent 1px), + #10203a; + background-size: 8px 8px; +} + +.bp-brand-mark::after { + position: absolute; + inset: 7px; + content: ""; + border: 1px solid #60a5fa; + border-top-color: transparent; } .bp-brand-title { - font-size: 17px; - font-weight: 720; - letter-spacing: -.02em; + color: var(--bp-text); + font-size: 15px; + font-weight: 700; + letter-spacing: -.015em; } .bp-brand-subtitle { - color: var(--bp-muted); + color: var(--bp-sidebar-muted); font-size: 10px; - letter-spacing: .12em; - text-transform: uppercase; + line-height: 1.4; } -.bp-header .q-tab { - min-height: 68px; - padding: 0 15px; - color: #8290aa; - font-size: 12px; - letter-spacing: .01em; +.bp-sidebar { + width: 288px !important; + color: var(--bp-sidebar-text); + background: var(--bp-sidebar) !important; + border-right: 1px solid var(--bp-sidebar-line) !important; + box-shadow: none !important; } -.bp-header .q-tab--active { - color: #f5f7ff; +.bp-sidebar-shell { + min-height: 100vh; + gap: 0 !important; + padding: 12px; + overflow-y: auto; } -.bp-drawer { - width: 318px !important; - padding: 16px 16px 28px; - background: rgba(9, 13, 23, .94) !important; - border-right: 1px solid var(--bp-border) !important; - backdrop-filter: blur(24px); +.bp-sidebar-brand { + min-height: 38px; + padding: 0 2px; } -.bp-drawer-title { - color: #dfe7fa; - font-size: 12px; - font-weight: 700; - letter-spacing: .1em; +.bp-sidebar .bp-brand-title { + color: var(--bp-sidebar-text); +} + +.bp-sidebar-kicker { + color: #64748b; + font: 650 10px ui-monospace, SFMono-Regular, Menlo, monospace; + letter-spacing: .13em; +} + +.bp-sidebar-title { + color: #e2e8f0; + font-size: 13px; + font-weight: 650; +} + +.bp-mode-switch { + min-height: 42px; + padding: 3px; + border: 1px solid var(--bp-sidebar-line); + border-radius: 8px; + background: rgba(15, 23, 42, .78); +} + +.bp-mode-switch .q-tabs__content { + gap: 3px; +} + +.bp-mode-switch .q-tab { + min-height: 34px; + flex: 1 1 0; + padding: 0 7px; + color: var(--bp-sidebar-muted); + border: 1px solid transparent; + border-radius: 6px; + font-size: 11px; +} + +.bp-mode-switch .q-tab__content { + flex-direction: row; + justify-content: center; + gap: 6px; +} + +.bp-mode-switch .q-tab--active { + color: #dbeafe; + border-color: rgba(96, 165, 250, .18); + background: rgba(37, 99, 235, .18); +} + +.bp-mode-switch .q-tab--active .q-icon { + color: #60a5fa; +} + +.bp-sidebar-rule { + margin: 11px 0; + background: var(--bp-sidebar-line) !important; +} + +.bp-sidebar-controls-card { + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 6px; + padding: 10px; + border: 1px solid var(--bp-sidebar-line); + border-radius: 8px; + background: rgba(15, 23, 42, .68); +} + +.bp-sidebar-control { + min-width: 0; +} + +.bp-sidebar-control .q-field__control { + min-height: 38px; + border-radius: 6px; + background: rgba(30, 41, 59, .86) !important; +} + +.bp-sidebar-control .q-field__native, +.bp-sidebar-control .q-field__input, +.bp-sidebar-control .q-field__marginal { + color: #e2e8f0 !important; + font-size: 11px; +} + +.bp-sidebar-control .q-field__label { + color: #94a3b8 !important; + font-size: 10px; +} + +.bp-sidebar-field-label { + margin: 1px 0 -2px; + color: #64748b; + font-size: 10px; + font-weight: 650; + letter-spacing: .05em; text-transform: uppercase; } -.bp-form-section { +.bp-sidebar-parallel-grid { width: 100%; - padding: 12px; - border: 1px solid var(--bp-border); - border-radius: 14px; - background: rgba(17, 23, 39, .52); + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; } -.bp-form-section .q-field__control, -.bp-form-section .q-item { - border-radius: 10px; +.bp-sidebar-full-config { + min-height: 32px; + color: #bfdbfe !important; + border-color: #334155 !important; + font-size: 11px; } -.bp-main { +.bp-sidebar-primary { + min-height: 38px; + color: #ffffff !important; + border-radius: 7px !important; + background: #2563eb !important; + font-size: 11px; + font-weight: 650; + box-shadow: none !important; +} + +.bp-sidebar-summary { + margin-top: 10px; + padding: 10px; + border: 1px solid var(--bp-sidebar-line); + border-radius: 8px; + background: rgba(15, 23, 42, .68); +} + +.bp-sidebar-fact { + min-width: 0; + padding: 6px 0; + border-top: 1px solid rgba(51, 65, 85, .58); +} + +.bp-sidebar-fact-grid { width: 100%; - max-width: 1540px; - margin: 0 auto; - padding: 28px 32px 60px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 10px; } -.bp-hero { - position: relative; +.bp-sidebar-label { + color: #64748b; + font-size: 10px; +} + +.bp-sidebar-value { width: 100%; - min-height: 164px; overflow: hidden; - padding: 28px 30px; - border: 1px solid var(--bp-border-bright); - border-radius: 24px; - background: - linear-gradient(110deg, rgba(35, 31, 74, .96), rgba(15, 23, 42, .86) 55%, rgba(8, 31, 42, .82)), - var(--bp-surface); - box-shadow: var(--bp-shadow), inset 0 1px 0 rgba(255, 255, 255, .08); + color: #cbd5e1; + font: 550 11px ui-monospace, SFMono-Regular, Menlo, monospace; + line-height: 1.45; + text-overflow: ellipsis; + white-space: nowrap; } -.bp-hero::after { - content: ""; - position: absolute; - right: -65px; - top: -100px; - width: 330px; - height: 330px; - border: 1px solid rgba(34, 211, 238, .18); +.bp-sidebar-state { + width: fit-content; + margin-top: 2px; + padding: 4px 8px; + color: var(--bp-sidebar-muted); + border: 1px solid #334155; + border-radius: 999px; + font-size: 11px; +} + +.bp-sidebar-state--active { + color: #bfdbfe; + border-color: rgba(96, 165, 250, .35); + background: rgba(37, 99, 235, .12); +} + +.bp-sidebar-state--warning { + color: #fde68a; + border-color: rgba(245, 158, 11, .35); + background: rgba(180, 83, 9, .12); +} + +.bp-sidebar-state--ready { + color: #bbf7d0; + border-color: rgba(74, 222, 128, .3); + background: rgba(21, 128, 61, .12); +} + +.bp-sidebar-footer { + margin-top: 12px; + padding-top: 13px; + border-top: 1px solid var(--bp-sidebar-line); +} + +.bp-sidebar-meta { + color: #64748b; + font-size: 10px; + line-height: 1.45; +} + +.bp-service-dot { + width: 6px; + height: 6px; + flex: 0 0 auto; border-radius: 50%; - box-shadow: 0 0 0 36px rgba(124, 104, 255, .045), 0 0 0 82px rgba(34, 211, 238, .028); + background: #22c55e; } -.bp-kicker { - color: #7ee8f7; +.bp-sidebar-legacy { + min-height: 34px; + padding: 0 6px !important; + color: var(--bp-sidebar-muted) !important; font-size: 11px; - font-weight: 750; - letter-spacing: .16em; - text-transform: uppercase; } -.bp-hero-title { - max-width: 850px; - margin-top: 7px; - font-size: clamp(24px, 3vw, 38px); - font-weight: 740; - line-height: 1.12; - letter-spacing: -.035em; +.bp-main { + width: 100%; + max-width: 1600px; + margin: 0 auto; + padding: 20px 24px 56px; } -.bp-hero-copy { - max-width: 850px; - margin-top: 10px; - color: #aab7d0; - font-size: 13px; - line-height: 1.7; +.bp-workspace-heading { + width: 100%; + margin-bottom: 16px; } -.bp-chip { - padding: 6px 10px; - color: #b9c4dc; - font: 600 10px ui-monospace, SFMono-Regular, Menlo, monospace; - border: 1px solid rgba(148, 163, 184, .16); - border-radius: 999px; - background: rgba(5, 8, 15, .35); +.bp-kicker { + color: var(--bp-blue); + font: 650 10px ui-monospace, SFMono-Regular, Menlo, monospace; + letter-spacing: .1em; + text-transform: uppercase; } -.bp-section-title { - margin-top: 6px; - color: #e8edfb; - font-size: 17px; +.bp-page-title { + margin-top: 4px; + color: var(--bp-text); + font-size: clamp(21px, 2vw, 27px); font-weight: 690; - letter-spacing: -.018em; + line-height: 1.24; + letter-spacing: -.03em; } -.bp-section-copy { +.bp-page-copy { max-width: 900px; + margin-top: 4px; color: var(--bp-muted); font-size: 12px; - line-height: 1.7; + line-height: 1.55; } -.bp-card { +.bp-setup-grid { width: 100%; - border: 1px solid var(--bp-border) !important; + display: grid; + grid-template-columns: minmax(0, 1fr) 340px; + gap: 16px; + align-items: start; +} + +.bp-focus-grid, +.bp-case-summary-grid { + width: 100%; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; +} + +.bp-focus-card { + min-width: 0; + padding: 13px; + border: 1px solid var(--bp-line); + border-radius: 7px; + background: var(--bp-panel-soft); +} + +.bp-case-summary-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0; + overflow: hidden; + border: 1px solid var(--bp-line); + border-radius: 7px; + background: var(--bp-panel-soft); +} + +.bp-case-summary-item { + min-width: 0; + padding: 11px 13px; + border-left: 1px solid var(--bp-line); +} + +.bp-case-summary-item:first-child { + border-left: 0; +} + +.bp-case-summary-value { + overflow: hidden; + color: var(--bp-text-soft); + font-size: 11px; + font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bp-setup-boundary { + width: 100%; + padding: 9px 11px; + border-left: 2px solid #93c5fd; + background: #f8fbff; +} + +.bp-setup-actions { + padding-top: 2px; +} + +.bp-setup-guidance-grid { + width: 100%; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.bp-setup-guidance { + min-width: 0; + padding: 2px 16px; + border-left: 1px solid var(--bp-line); +} + +.bp-setup-guidance:first-child { + padding-left: 0; + border-left: 0; +} + +.bp-setup-guidance:last-child { + padding-right: 0; +} + +.bp-setup-config-action { + background: #f8fbff; +} + +.bp-config-root { + width: 100%; + gap: 0 !important; + overflow: hidden; + border: 1px solid var(--bp-line); + border-radius: var(--bp-radius); + background: var(--bp-panel); +} + +.bp-config-section { + width: 100%; + padding: 16px; + border: 0 !important; + border-top: 1px solid var(--bp-line) !important; + border-radius: 0 !important; + background: var(--bp-panel) !important; + box-shadow: none !important; +} + +.bp-config-root > .bp-config-section:first-child { + border-top: 0 !important; +} + +.bp-config-section--search { + background: #f8fbff !important; +} + +.bp-section-index { + min-width: 25px; + height: 25px; + display: grid; + place-items: center; + color: var(--bp-blue); + border: 1px solid #bfdbfe; + border-radius: 5px; + background: #eff6ff; + font: 650 10px ui-monospace, SFMono-Regular, Menlo, monospace; +} + +.bp-section-title { + color: var(--bp-text); + font-size: 14px; + font-weight: 650; +} + +.bp-section-copy, +.bp-card-copy { + color: var(--bp-muted); + font-size: 12px; + line-height: 1.5; +} + +.bp-form-grid { + width: 100%; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 11px; +} + +.bp-form-grid--three { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.bp-form-grid > .q-field, +.bp-form-grid > .q-checkbox { + min-width: 0; +} + +.bp-config-section .q-field__control, +.bp-config-section .q-item { + border-radius: 6px; +} + +.bp-config-section .q-field--outlined .q-field__control::before { + border-color: var(--bp-line-strong); +} + +.bp-config-section .q-field--focused .q-field__control::before { + border-color: var(--bp-blue); +} + +.bp-derived-row { + width: 100%; + min-height: 34px; + padding: 7px 10px; + color: var(--bp-text-soft); + border-left: 2px solid var(--bp-blue); + background: #f8fafc; + font-size: 12px; +} + +.bp-advanced { + width: 100%; + border-top: 1px solid var(--bp-line); +} + +.bp-advanced .q-item { + min-height: 44px; + padding: 8px 0; +} + +.bp-setup-summary { + position: sticky; + top: 20px; +} + +.bp-summary-panel, +.bp-card, +.bp-loading-panel { + width: 100%; + border: 1px solid var(--bp-line) !important; border-radius: var(--bp-radius) !important; - background: var(--bp-surface) !important; - box-shadow: 0 12px 45px rgba(0, 0, 0, .14) !important; - backdrop-filter: blur(16px); + background: var(--bp-panel) !important; + box-shadow: none !important; } -.bp-metric { - position: relative; - min-width: 170px; - flex: 1 1 170px; - padding: 17px 18px; +.bp-summary-panel { overflow: hidden; - border: 1px solid var(--bp-border); - border-radius: 16px; - background: linear-gradient(160deg, rgba(24, 31, 51, .92), rgba(12, 17, 29, .92)); + padding: 17px; } -.bp-metric::before { - content: ""; - position: absolute; - left: 0; - top: 0; - width: 3px; +.bp-summary-rule { + width: 100%; + height: 1px; + margin: 4px 0; + background: var(--bp-line); +} + +.bp-summary-label { + color: var(--bp-muted); + font-size: 11px; +} + +.bp-summary-value { + max-width: 205px; + overflow: hidden; + color: var(--bp-text-soft); + font-size: 11px; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bp-primary-action { + min-height: 40px; + border-radius: 6px !important; + background: var(--bp-blue) !important; + font-size: 12px; + font-weight: 650; + box-shadow: none !important; +} + +.bp-secondary-action { + min-height: 36px; + border-radius: 6px !important; + color: #1d4ed8 !important; + border-color: #bfdbfe !important; + background: #ffffff !important; +} + +.bp-config-dialog { + width: min(920px, calc(100vw - 48px)) !important; + max-width: 920px !important; + height: min(820px, calc(100vh - 48px)); + max-height: calc(100vh - 48px) !important; + padding: 0 !important; + overflow: hidden; + color: var(--bp-text); + border: 1px solid var(--bp-line) !important; + border-radius: 10px !important; + background: var(--bp-panel) !important; + box-shadow: 0 24px 72px rgba(15, 23, 42, .24) !important; +} + +.bp-dialog-shell { + width: 100%; height: 100%; - background: var(--metric-color, #7c68ff); - box-shadow: 0 0 18px var(--metric-color, #7c68ff); + min-height: 0; + gap: 0 !important; +} + +.bp-dialog-heading { + flex: 0 0 auto; + padding: 14px 18px; + border-bottom: 1px solid var(--bp-line); + background: #ffffff; +} + +.bp-dialog-form { + min-height: 0; + flex: 1 1 auto; + align-items: stretch; + overflow-y: auto; + padding: 16px 18px; + background: var(--bp-canvas); +} + +.bp-dialog-footer { + flex: 0 0 auto; + align-items: flex-end; + padding: 12px 18px 14px; + border-top: 1px solid var(--bp-line); + background: #ffffff; +} + +.bp-dialog-footer .bp-primary-action { + width: auto !important; + min-width: 220px; +} + +.bp-result-context { + width: 100%; + padding: 0; +} + +.bp-result-context-row { + min-height: 42px; + flex-wrap: wrap; +} + +.bp-result-chips { + flex-wrap: wrap; +} + +.bp-result-title { + color: var(--bp-text); + font-size: 15px; + font-weight: 670; + letter-spacing: -.02em; +} + +.bp-context-chip, +.bp-data-chip { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 2px 7px; + color: #475569; + border: 1px solid var(--bp-line); + border-radius: 5px; + background: #ffffff; + font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; +} + +.bp-result-config { + min-height: 32px; + padding: 0 10px !important; +} + +.bp-stale-banner, +.bp-status-banner, +.bp-inline-error, +.bp-diagnostic { + width: 100%; + border-radius: 7px; +} + +.bp-stale-banner { + padding: 10px 12px; + color: #92400e; + border: 1px solid #fde68a; + background: #fffbeb; +} + +.bp-status-banner { + padding: 10px 12px; + color: #166534; + border: 1px solid #bbf7d0; + background: #f0fdf4; +} + +.bp-status-banner--warning { + color: #92400e; + border-color: #fde68a; + background: #fffbeb; +} + +.bp-inline-error { + padding: 10px 12px; + color: #991b1b; + border: 1px solid #fecaca; + background: #fef2f2; +} + +.bp-result-tabs { + min-height: 32px; + padding: 0 2px; + border: 1px solid var(--bp-line); + border-radius: 6px; + background: #ffffff; +} + +.bp-result-tabs .q-tab { + min-height: 31px; + padding: 0 13px; + color: #64748b; + font-size: 11px; +} + +.bp-result-tabs .q-tab--active { + color: var(--bp-blue); +} + +.bp-result-panels, +.bp-result-panels .q-tab-panel { + padding: 0; + background: transparent; +} + +.bp-evidence-surface { + width: 100%; + overflow: hidden; + border: 1px solid var(--bp-line); + border-radius: var(--bp-radius); + background: var(--bp-panel); +} + +.bp-evidence-section { + width: 100%; + display: flex; + flex-direction: column; + gap: 14px; + padding: 16px; +} + +.bp-evidence-section + .bp-evidence-section { + border-top: 1px solid var(--bp-line); +} + +.bp-metric-grid { + width: 100%; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0; +} + +.bp-metric { + min-width: 0; + padding: 3px 16px; + border-left: 1px solid var(--bp-line); +} + +.bp-metric:first-child { + padding-left: 0; + border-left: 0; } .bp-metric-label { + display: flex; + align-items: center; + gap: 6px; color: var(--bp-muted); font-size: 10px; font-weight: 650; - letter-spacing: .08em; + letter-spacing: .05em; text-transform: uppercase; } +.bp-metric-label::before { + width: 5px; + height: 5px; + flex: 0 0 auto; + content: ""; + border-radius: 50%; + background: var(--metric-color, var(--bp-blue)); +} + .bp-metric-value { - margin-top: 7px; - color: #f4f7ff; - font: 680 23px ui-monospace, SFMono-Regular, Menlo, monospace; - letter-spacing: -.04em; + margin-top: 6px; + overflow: hidden; + color: var(--bp-text); + font-size: 21px; + font-weight: 680; + line-height: 1.2; + letter-spacing: -.025em; + font-variant-numeric: tabular-nums; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bp-metric-value--range { + font-size: 17px; } .bp-metric-detail { margin-top: 4px; - color: #73809a; - font-size: 10px; + color: var(--bp-muted); + font-size: 11px; } -.bp-status { +.bp-insight { width: 100%; - padding: 11px 14px; - border: 1px solid rgba(52, 211, 153, .24); - border-radius: 12px; - color: #a7f3d0; - background: rgba(16, 185, 129, .07); + padding: 12px 14px; + border-left: 3px solid var(--bp-blue); + background: #f8fbff; } -.bp-status--warning { - color: #fde68a; - border-color: rgba(245, 158, 11, .28); - background: rgba(245, 158, 11, .08); +.bp-chain-header { + flex-wrap: wrap; } -.bp-diagnostic { +.bp-fidelity-tag { + flex: 0 0 auto; + padding: 4px 7px; + color: #475569; + border: 1px solid var(--bp-line); + border-radius: 5px; + background: #f8fafc; + font-size: 9px; + letter-spacing: .05em; +} + +.bp-time-treemap { + height: 430px; +} + +.bp-timeline-chart { + height: 420px; +} + +.bp-time-details { + padding: 0 !important; + overflow: hidden; +} + +.bp-time-details > .q-expansion-item__container > .q-item { + min-height: 42px; + padding: 8px 12px; +} + +.bp-timeline-legend { + min-height: 24px; +} + +.bp-engine-dot { + width: 8px; + height: 8px; + border-radius: 2px; + background: #64748b; +} + +.bp-engine-dot--matrix { + background: #2563eb; +} + +.bp-engine-dot--vector { + background: #7c3aed; +} + +.bp-engine-dot--collective { + background: #0891b2; +} + +.bp-time-scope { width: 100%; - padding: 12px 14px; - border-left: 3px solid var(--diagnostic-color, #7c68ff); - border-radius: 10px; - background: rgba(20, 27, 45, .74); + padding: 9px 11px; + border-left: 2px solid #93c5fd; + background: #f8fbff; +} + +.bp-time-method { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--bp-line); + border-radius: 6px; + background: #ffffff; +} + +.bp-chain-stats { + width: 100%; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + padding-top: 10px; + border-top: 1px solid var(--bp-line); +} + +.bp-card { + padding: 16px; +} + +.bp-chart-grid, +.bp-detail-grid { + width: 100%; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0; + align-items: stretch; +} + +.bp-batch-filter-grid { + width: 100%; + display: grid; + grid-template-columns: minmax(180px, 1.25fr) repeat(3, minmax(130px, 1fr)); + gap: 10px; +} + +.bp-batch-filter { + min-width: 0; +} + +.bp-batch-filter .q-field__control { + min-height: 40px; + border-radius: 6px; +} + +.bp-filter-clear { + min-height: 30px; + color: var(--bp-muted) !important; + font-size: 11px; +} + +.bp-batch-chart-grid { + width: 100%; + display: grid; + grid-template-columns: minmax(0, 1.25fr) minmax(340px, .75fr); + gap: 0; +} + +.bp-batch-chart { + height: 390px; +} + +.bp-detail-grid--wide { + grid-template-columns: minmax(0, 1.35fr) minmax(300px, .65fr); +} + +.bp-evidence-block { + min-width: 0; + padding: 0 16px; +} + +.bp-evidence-block:first-child { + padding-left: 0; +} + +.bp-evidence-block + .bp-evidence-block { + padding-right: 0; + border-left: 1px solid var(--bp-line); +} + +.bp-card-title { + color: var(--bp-text); + font-size: 13px; + font-weight: 650; +} + +.bp-fact-row { + width: 100%; + min-height: 30px; + padding: 5px 0; + border-bottom: 1px solid #f1f5f9; +} + +.bp-fact-row:last-child { + border-bottom: 0; +} + +.bp-stage-flow { + width: 100%; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 11px; +} + +.bp-stage { + position: relative; + min-width: 0; + padding: 13px; + border: 1px solid var(--bp-line); + border-radius: 7px; + background: var(--bp-panel-soft); +} + +.bp-stage:not(:last-child)::after { + position: absolute; + top: 23px; + right: -12px; + z-index: 1; + width: 12px; + height: 1px; + content: ""; + background: var(--bp-line-strong); +} + +.bp-derivation-details { + gap: 10px; +} + +.bp-derivation-details .bp-card { + padding: 0; +} + +.bp-diagnostic { + padding: 11px 12px; + border: 1px solid #e5e7eb; + border-left: 3px solid var(--diagnostic-color, var(--bp-red)); + background: #f8fafc; } .bp-code { width: 100%; max-height: 470px; overflow: auto; - border: 1px solid var(--bp-border); - border-radius: 12px; - background: #060911 !important; + color: #e2e8f0 !important; + border: 1px solid #1e293b; + border-radius: 6px; + background: #020617 !important; + font-size: 12px; } -.bp-stage { - min-width: 210px; - flex: 1 1 210px; - padding: 15px; - border: 1px solid var(--bp-border); - border-radius: 14px; - background: rgba(16, 22, 37, .76); +.bp-grid { + overflow: hidden; + border: 1px solid var(--bp-line); + border-radius: 7px; + --ag-background-color: #ffffff; + --ag-foreground-color: #334155; + --ag-header-background-color: #f8fafc; + --ag-header-foreground-color: #475569; + --ag-border-color: #e2e8f0; + --ag-row-border-color: #f1f5f9; + --ag-odd-row-background-color: #fbfdff; + --ag-selected-row-background-color: #eff6ff; + --ag-font-size: 12px; } -.bp-stage-valid { - color: var(--bp-success); +.bp-loading-panel { + min-height: 400px; + display: grid; + place-items: center; + padding: 34px; + text-align: center; } -.bp-empty { - min-height: 240px; +.bp-loading-glyph { + width: 56px; + height: 56px; display: grid; place-items: center; - text-align: center; - border: 1px dashed rgba(148, 163, 184, .2); - border-radius: var(--bp-radius); - background: rgba(15, 20, 34, .42); + border: 1px solid #bfdbfe; + border-radius: 8px; + background: + linear-gradient(rgba(37, 99, 235, .08) 1px, transparent 1px), + linear-gradient(90deg, rgba(37, 99, 235, .08) 1px, transparent 1px), + #eff6ff; + background-size: 10px 10px; } -.bp-grid { - overflow: hidden; - border: 1px solid var(--bp-border); - border-radius: 14px; +.bp-empty { + min-height: 220px; + display: grid; + place-items: center; + padding: 28px; + text-align: center; + border: 1px dashed var(--bp-line-strong); + border-radius: 7px; + background: var(--bp-panel-soft); } .bp-mono { @@ -312,29 +1115,277 @@ color: var(--bp-muted); } -.q-tab-panel { - padding: 22px 0 0; - background: transparent; +.bp-positive { + color: var(--bp-green); +} + +.bp-warning { + color: var(--bp-amber); } .q-expansion-item { - border-radius: 12px; + border-radius: 7px; +} + +.q-menu { + color: var(--bp-text); + background: #ffffff; + border: 1px solid var(--bp-line); + box-shadow: 0 12px 28px rgba(15, 23, 42, .14); +} + +.bp-sidebar-menu { + min-width: 110px !important; + max-height: 280px !important; + color: #e2e8f0 !important; + border-color: #334155 !important; + background: #0f172a !important; + box-shadow: 0 16px 32px rgba(0, 0, 0, .34) !important; +} + +.bp-sidebar-menu .q-item { + min-height: 34px; + color: #cbd5e1; + font-size: 12px; +} + +.bp-sidebar-menu .q-item--active, +.bp-sidebar-menu .q-item.q-manual-focusable--focused { + color: #dbeafe; + background: rgba(37, 99, 235, .2); +} + +@media (max-width: 1180px) { + .bp-main { + padding: 20px 20px 48px; + } + + .bp-setup-grid { + grid-template-columns: 1fr; + } + + .bp-setup-summary { + position: static; + } + + .bp-chart-grid, + .bp-detail-grid, + .bp-detail-grid--wide, + .bp-batch-chart-grid { + grid-template-columns: 1fr; + } + + .bp-batch-filter-grid, + .bp-case-summary-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .bp-case-summary-item:nth-child(odd) { + border-left: 0; + } + + .bp-case-summary-item:nth-child(n + 3) { + border-top: 1px solid var(--bp-line); + } + + .bp-evidence-block { + padding: 0 0 16px; + } + + .bp-evidence-block + .bp-evidence-block { + padding: 16px 0 0; + border-top: 1px solid var(--bp-line); + border-left: 0; + } + + .bp-metric-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + row-gap: 14px; + } + + .bp-metric:nth-child(odd) { + padding-left: 0; + border-left: 0; + } + + .bp-metric:nth-child(n + 3) { + padding-top: 14px; + border-top: 1px solid var(--bp-line); + } + + .bp-chain-stats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 980px) { + .bp-mobile-bar { + position: sticky; + top: 0; + z-index: 20; + width: calc(100% + 40px); + min-height: 54px; + display: flex !important; + margin: -20px -20px 18px; + padding: 8px 16px; + border-bottom: 1px solid var(--bp-line); + background: rgba(255, 255, 255, .96); + backdrop-filter: blur(12px); + } + + .bp-brand-mark--mobile { + width: 30px; + height: 30px; + } + + .bp-mobile-service { + color: var(--bp-muted); + font-size: 11px; + } } @media (max-width: 900px) { - .bp-main { padding: 18px 16px 44px; } - .bp-header { padding: 0 10px; } - .bp-brand-subtitle { display: none; } - .bp-header .q-tab { padding: 0 7px; font-size: 10px; } - .bp-hero { padding: 22px 20px; } + .bp-form-grid, + .bp-form-grid--three { + grid-template-columns: 1fr; + } + + .bp-setup-guidance-grid { + grid-template-columns: 1fr; + } + + .bp-focus-grid, + .bp-batch-filter-grid { + grid-template-columns: 1fr; + } + + .bp-setup-guidance, + .bp-setup-guidance:first-child, + .bp-setup-guidance:last-child { + padding: 11px 0; + border-top: 1px solid var(--bp-line); + border-left: 0; + } + + .bp-setup-guidance:first-child { + padding-top: 0; + border-top: 0; + } + + .bp-stage-flow { + grid-template-columns: 1fr; + } + + .bp-chain-stats { + grid-template-columns: 1fr; + gap: 6px; + } + + .bp-stage:not(:last-child)::after { + display: none; + } + + .bp-config-dialog { + width: calc(100vw - 24px) !important; + height: calc(100vh - 24px); + max-height: calc(100vh - 24px) !important; + } + + .bp-dialog-heading, + .bp-dialog-footer { + padding-right: 13px; + padding-left: 13px; + } + + .bp-dialog-form { + padding: 13px; + } + + .bp-dialog-footer .bp-primary-action { + width: 100% !important; + } +} + +@media (max-width: 620px) { + .bp-main { + padding: 18px 12px 38px; + } + + .bp-mobile-bar { + width: calc(100% + 24px); + margin: -18px -12px 16px; + padding: 8px 10px; + } + + .bp-result-context > .q-row { + flex-wrap: wrap; + } + + .bp-metric-grid { + grid-template-columns: 1fr; + row-gap: 0; + } + + .bp-metric, + .bp-metric:nth-child(odd), + .bp-metric:first-child { + padding: 12px 0; + border-left: 0; + } + + .bp-metric:first-child { + padding-top: 0; + } + + .bp-metric:nth-child(n + 2) { + border-top: 1px solid var(--bp-line); + } + + .bp-result-tabs .q-tab { + padding: 0 9px; + font-size: 11px; + } + + .bp-case-summary-grid { + grid-template-columns: 1fr; + } + + .bp-case-summary-item, + .bp-case-summary-item:nth-child(odd) { + border-top: 1px solid var(--bp-line); + border-left: 0; + } + + .bp-case-summary-item:first-child { + border-top: 0; + } + + .bp-setup-actions > .q-btn { + width: 100%; + } + + .bp-time-treemap, + .bp-timeline-chart { + height: 360px; + } + + .bp-evidence-section, + .bp-config-section, + .bp-card { + padding: 13px; + } + + .bp-derivation-details .bp-card { + padding: 0; + } } """ METRIC_COLORS = { - "primary": "#7c68ff", - "cyan": "#22d3ee", - "violet": "#a78bfa", - "amber": "#f59e0b", + "primary": "#2563eb", + "cyan": "#0891b2", + "violet": "#7c3aed", + "amber": "#b45309", "neutral": "#64748b", } diff --git a/src/blueprinting/workbench/nicegui_ui.py b/src/blueprinting/workbench/nicegui_ui.py index e99e7ff..0ec5b70 100644 --- a/src/blueprinting/workbench/nicegui_ui.py +++ b/src/blueprinting/workbench/nicegui_ui.py @@ -1,14 +1,15 @@ """NiceGUI architecture-exploration workbench. -The workbench is deliberately a thin client around :class:`BlueprintingService`. -It owns per-browser UI state and presentation only; it never assembles lowering -passes or computes hardware estimates itself. +The UI is a stateful, presentation-only client around :class:`BlueprintingService`. +It exposes one case through a point lens and a case set through a batch lens, +while reusing one configuration surface and immutable evaluation results. """ from __future__ import annotations import json -from collections.abc import Awaitable, Callable +from collections.abc import Callable +from enum import Enum from functools import partial from typing import Any @@ -29,15 +30,19 @@ from .nicegui_theme import METRIC_COLORS, WORKBENCH_CSS from .presentation import ( analysis_metrics, + dependency_timeline_chart_options, format_bytes, format_count, format_seconds, latency_chart_options, memory_chart_options, - stage_rows, sweep_chart_options, + sweep_distribution_chart_options, sweep_rows, task_rows, + time_breakdown_chart_options, + time_breakdown_rows, + timeline_summary, ) _COMPILER_DTYPES = ("float16", "bfloat16", "float32", "float8") @@ -46,6 +51,29 @@ "系统证据曲线": CalibrationMode.SYSTEM_EVIDENCE, "理论峰值基线": CalibrationMode.PEAK_ONLY, } +_BATCH_STATUS_LABELS = { + "all": "全部状态", + "feasible": "容量可行", + "infeasible": "容量不可行", + "pareto": "非支配", + "failed": "推导失败", +} +_BOTTLENECK_LABELS = { + "forward": "前向计算", + "backward": "反向计算", + "optimizer": "优化器更新", + "recompute": "激活重计算", + "tensor_parallel": "张量并行通信", + "pipeline_parallel": "流水并行通信", + "data_parallel": "数据并行通信", + "recommunication": "重通信", + "pipeline_bubble": "流水空泡", +} + + +class WorkbenchMode(Enum): + ANALYSIS = "analysis" + SWEEP = "sweep" def _positive_int(value: Any, label: str) -> int: @@ -59,19 +87,18 @@ def _strip_json_suffix(name: str) -> str: class ConfigurationPanel: - """Own the editable configuration controls for one browser tab.""" + """One movable configuration surface shared by both workbench modes.""" def __init__( self, catalog: ConfigCatalog, *, - on_analyze: Callable[[], Awaitable[None]], - on_sweep: Callable[[], Awaitable[None]], + on_change: Callable[[str], None], ) -> None: self.catalog = catalog - self.on_analyze = on_analyze - self.on_sweep = on_sweep + self.on_change = on_change self._controls: list[Any] = [] + self.root: Any | None = None def build(self) -> None: model_names = self.catalog.names("models") @@ -80,133 +107,156 @@ def build(self) -> None: model_name = self.catalog.preferred("models", "gpt3-175B.json") execution_name = self.catalog.preferred("examples", execution_names[0]) hardware_name = self.catalog.preferred("systems", "a100_80g.json") - - ui.label("Exploration inputs").classes("bp-drawer-title") - ui.label("配置只描述问题;推导与估算由应用服务完成。").classes("text-xs bp-muted leading-relaxed") - - with ui.column().classes("bp-form-section gap-2"): - self.model_preset = self._select("模型预设", model_names, model_name, self._reload_presets) - self.hardware_preset = self._select("硬件证据", hardware_names, hardware_name, self._reload_presets) - self.execution_preset = self._select("策略模板", execution_names, execution_name, self._reload_presets) - model = self.catalog.load("models", model_name) execution = self.catalog.load("examples", execution_name) hardware = self.catalog.load("systems", hardware_name) - with ( - ui.expansion("模型语义", icon="schema", value=False).classes("bp-form-section"), - ui.column().classes("w-full gap-2 pt-2"), - ): - self.hidden = self._number("Hidden size", model["hidden"]) - self.feedforward = self._number("Feed-forward size", model["feedforward"]) - self.sequence = self._number("Sequence length", model["seq_size"]) - self.heads = self._number("Attention heads", model["attn_heads"]) - self.head_size = self._number("Attention head size", model["attn_size"]) - self.blocks = self._number("Transformer blocks", model["num_blocks"]) - - with ( - ui.expansion("执行策略", icon="account_tree", value=True).classes("bp-form-section"), - ui.column().classes("w-full gap-2 pt-2"), - ): - with ui.row().classes("w-full gap-2 no-wrap"): - self.tp = self._number("TP", execution["tensor_par"], on_change=self._update_world_size) - self.pp = self._number("PP", execution["pipeline_par"], on_change=self._update_world_size) - self.dp = self._number("DP", execution["data_par"], on_change=self._update_world_size) - self.world_size_label = ui.label().classes("text-xs bp-muted bp-mono") - self.global_batch = self._number("Global batch", execution["batch_size"]) - self.microbatch = self._number("Microbatch", execution["microbatch_size"]) - self.datatype = self._select( - "Datatype", - self._supported_datatypes(hardware_name), - self._initial_datatype(execution, hardware_name), - ) - self.recompute = self._select( - "Activation recompute", - ("none", "attn_only", "full"), - execution.get("activation_recompute", "none"), - ) - self.communication = self._select( - "TP communication", - ("ar", "rs_ag"), - execution.get("tensor_par_comm_type", "ar"), - ) - self.interleaving = self._number("Pipeline interleaving", execution.get("pipeline_interleaving", 1)) - self.optimizer_sharding = ui.checkbox( - "Optimizer sharding", - value=bool(execution.get("optimizer_sharding", False)), - ).props("dense") - self._controls.append(self.optimizer_sharding) - - with ( - ui.expansion("网络映射", icon="hub", value=False).classes("bp-form-section"), - ui.column().classes("w-full gap-2 pt-2"), - ): - network_options = tuple(range(len(hardware.get("networks", ())))) - self.tp_network = self._select( - "TP network tier", - network_options, - min(int(execution.get("tensor_par_net", 0)), len(network_options) - 1), - ) - self.pp_network = self._select( - "PP network tier", - network_options, - min(int(execution.get("pipeline_par_net", 0)), len(network_options) - 1), - ) - self.dp_network = self._select( - "DP network tier", - network_options, - min(int(execution.get("data_par_net", 0)), len(network_options) - 1), - ) + self.root = ui.column().classes("bp-config-root") + with self.root: + with ui.element("section").classes("bp-config-section"): + self._section_heading("01", "工作负载", "选择模型基线;只在需要时展开并修改语义维度。") + self.model_preset = self._select( + "模型预设", + model_names, + model_name, + self._load_model_preset, + ) + with ( + ui.expansion("模型语义参数", icon="schema", value=False).classes("bp-advanced mt-2"), + ui.element("div").classes("bp-form-grid pt-2"), + ): + self.hidden = self._number("隐藏维度", model["hidden"]) + self.feedforward = self._number("前馈维度", model["feedforward"]) + self.sequence = self._number("序列长度", model["seq_size"]) + self.heads = self._number("注意力头数", model["attn_heads"]) + self.head_size = self._number("单头维度", model["attn_size"]) + self.blocks = self._number("Transformer 层数", model["num_blocks"]) + + with ui.element("section").classes("bp-config-section"): + self._section_heading("02", "映射与目标", "定义执行策略、硬件证据目标和并行拓扑。") + with ui.element("div").classes("bp-form-grid"): + self.execution_preset = self._select( + "策略模板", + execution_names, + execution_name, + self._load_execution_preset, + ) + self.hardware_preset = self._select( + "硬件证据", + hardware_names, + hardware_name, + self._load_hardware_preset, + ) + with ui.element("div").classes("bp-form-grid bp-form-grid--three mt-1"): + self.tp = self._number("TP", execution["tensor_par"], on_change=self._notify_shared).mark( + "tp-input" + ) + self.pp = self._number("PP", execution["pipeline_par"], on_change=self._notify_shared) + self.dp = self._number("DP", execution["data_par"], on_change=self._notify_shared) + with ui.row().classes("bp-derived-row items-center justify-between"): + ui.label("派生设备规模") + self.world_size_label = ui.label().classes("bp-mono") + with ui.element("div").classes("bp-form-grid bp-form-grid--three mt-1"): + self.global_batch = self._number("全局批量", execution["batch_size"]) + self.microbatch = self._number("微批量", execution["microbatch_size"]) + self.datatype = self._select( + "数据类型", + self._supported_datatypes(hardware_name), + self._initial_datatype(execution, hardware_name), + ) + with ( + ui.expansion("执行策略细节", icon="account_tree", value=False).classes("bp-advanced mt-2"), + ui.column().classes("w-full gap-3 pt-2"), + ui.element("div").classes("bp-form-grid"), + ): + self.recompute = self._select( + "激活重计算", + ("none", "attn_only", "full"), + execution.get("activation_recompute", "none"), + ) + self.communication = self._select( + "TP 通信", + ("ar", "rs_ag"), + execution.get("tensor_par_comm_type", "ar"), + ) + self.interleaving = self._number( + "流水交错数", + execution.get("pipeline_interleaving", 1), + ) + self.optimizer_sharding = ui.checkbox( + "优化器分片", + value=bool(execution.get("optimizer_sharding", False)), + on_change=self._notify_shared, + ).props("dense") + self._controls.append(self.optimizer_sharding) + + self.sweep_section = ui.element("section").classes("bp-config-section bp-config-section--search") + with self.sweep_section: + self._section_heading("03", "CaseSet 组合空间", "组合 TP、PP、DP 候选;最多评估 128 个 Case。") + options = self._parallel_options(execution) + with ui.element("div").classes("bp-form-grid"): + self.tp_candidates = self._multi_select( + "TP 候选", + options, + [int(execution["tensor_par"])], + ) + self.pp_candidates = self._multi_select( + "PP 候选", + options, + [int(execution["pipeline_par"])], + ) + self.dp_candidates = self._multi_select( + "DP 候选", + options, + [int(execution["data_par"])], + ) + with ui.row().classes("bp-derived-row items-center justify-between"): + ui.label("候选组合") + self.candidate_count_label = ui.label().classes("bp-mono") + + with ui.element("section").classes("bp-config-section"): + self._section_heading("04", "证据与高级映射", "选择估算来源,并按需指定通信网络层级。") + ui.label("估算证据").classes("bp-section-copy") + self.calibration = ui.radio( + list(_CALIBRATION_LABELS), + value="系统证据曲线", + on_change=self._notify_shared, + ).props("dense inline") + self._controls.append(self.calibration) + with ( + ui.expansion("网络映射", icon="hub", value=False).classes("bp-advanced mt-2"), + ui.element("div").classes("bp-form-grid bp-form-grid--three pt-2"), + ): + network_options = tuple(range(len(hardware.get("networks", ())))) + if not network_options: + raise ValueError(f"硬件预设 {hardware_name} 没有网络层级") + self.tp_network = self._select( + "TP 网络层级", + network_options, + min(int(execution.get("tensor_par_net", 0)), len(network_options) - 1), + ) + self.pp_network = self._select( + "PP 网络层级", + network_options, + min(int(execution.get("pipeline_par_net", 0)), len(network_options) - 1), + ) + self.dp_network = self._select( + "DP 网络层级", + network_options, + min(int(execution.get("data_par_net", 0)), len(network_options) - 1), + ) - with ( - ui.expansion("策略搜索空间", icon="travel_explore", value=False).classes("bp-form-section"), - ui.column().classes("w-full gap-2 pt-2"), - ): - options = self._parallel_options(execution) - self.tp_candidates = self._multi_select( - "TP candidates", options, [int(execution["tensor_par"])], self._update_candidate_count - ) - self.pp_candidates = self._multi_select( - "PP candidates", options, [int(execution["pipeline_par"])], self._update_candidate_count - ) - self.dp_candidates = self._multi_select( - "DP candidates", options, [int(execution["data_par"])], self._update_candidate_count - ) - self.candidate_count_label = ui.label().classes("text-xs bp-muted bp-mono") - - with ui.column().classes("bp-form-section gap-2"): - ui.label("估算证据").classes("text-xs bp-muted") - self.calibration = ui.radio( - list(_CALIBRATION_LABELS), - value="系统证据曲线", - ).props("dense") - self._controls.append(self.calibration) - - self.analysis_button = ( - ui.button("运行单点分析", icon="play_arrow", on_click=self.on_analyze) - .props("unelevated no-caps") - .classes("w-full h-11") - .mark("run-analysis") - ) - self.sweep_button = ( - ui.button("探索策略空间", icon="scatter_plot", on_click=self.on_sweep, color="secondary") - .props("outline no-caps") - .classes("w-full h-11") - .mark("run-sweep") - ) - with ui.column().classes("w-full gap-1"): - self.busy_row = ui.row().classes("items-center gap-2") - with self.busy_row: - ui.spinner("dots", size="22px", color="secondary") - self.busy_label = ui.label("正在执行分析…").classes("text-xs bp-muted") - self.progress = ui.linear_progress(value=0, show_value=False, color="secondary").classes("w-full") - self.progress_label = ui.label().classes("text-xs bp-muted bp-mono") - self.busy_row.set_visibility(False) - self.progress.set_visibility(False) - self.progress_label.set_visibility(False) self._update_world_size() self._update_candidate_count() + @staticmethod + def _section_heading(index: str, title: str, copy: str) -> None: + with ui.row().classes("w-full items-start gap-3 no-wrap mb-3"): + ui.label(index).classes("bp-section-index") + with ui.column().classes("gap-1"): + ui.label(title).classes("bp-section-title") + ui.label(copy).classes("bp-section-copy") + def _select( self, label: str, @@ -215,22 +265,16 @@ def _select( on_change: Callable[..., Any] | None = None, ) -> Any: control = ( - ui.select(list(options), label=label, value=value, on_change=on_change) + ui.select(list(options), label=label, value=value, on_change=on_change or self._notify_shared) .props("outlined dense options-dense") .classes("w-full") ) self._controls.append(control) return control - def _multi_select( - self, - label: str, - options: tuple[int, ...], - value: list[int], - on_change: Callable[..., Any], - ) -> Any: + def _multi_select(self, label: str, options: tuple[int, ...], value: list[int]) -> Any: control = ( - ui.select(list(options), label=label, value=value, multiple=True, on_change=on_change) + ui.select(list(options), label=label, value=value, multiple=True, on_change=self._notify_sweep) .props("outlined dense use-chips options-dense") .classes("w-full") ) @@ -245,7 +289,7 @@ def _number( on_change: Callable[..., Any] | None = None, ) -> Any: control = ( - ui.number(label, value=float(value), min=1, step=1, precision=0, on_change=on_change) + ui.number(label, value=float(value), min=1, step=1, precision=0, on_change=on_change or self._notify_shared) .props("outlined dense") .classes("w-full") ) @@ -284,12 +328,8 @@ def _parallel_options(execution: dict[str, Any]) -> tuple[int, ...]: ) ) - def _reload_presets(self, *_: Any) -> None: - model_name, execution_name, hardware_name = self._selection_names() - model = self.catalog.load("models", model_name) - execution = self.catalog.load("examples", execution_name) - hardware = self.catalog.load("systems", hardware_name) - + def _load_model_preset(self, *_: Any) -> None: + model = self.catalog.load("models", str(self.model_preset.value)) for control, value in ( (self.hidden, model["hidden"]), (self.feedforward, model["feedforward"]), @@ -297,6 +337,30 @@ def _reload_presets(self, *_: Any) -> None: (self.heads, model["attn_heads"]), (self.head_size, model["attn_size"]), (self.blocks, model["num_blocks"]), + ): + control.set_value(float(value)) + self._notify_shared() + + def _load_hardware_preset(self, *_: Any) -> None: + hardware_name = str(self.hardware_preset.value) + hardware = self.catalog.load("systems", hardware_name) + datatypes = self._supported_datatypes(hardware_name) + current_datatype = str(self.datatype.value) + self.datatype.set_options( + list(datatypes), + value=current_datatype if current_datatype in datatypes else datatypes[0], + ) + network_options = tuple(range(len(hardware.get("networks", ())))) + if not network_options: + raise ValueError(f"硬件预设 {hardware_name} 没有网络层级") + for control in (self.tp_network, self.pp_network, self.dp_network): + current = int(control.value or 0) + control.set_options(list(network_options), value=min(current, len(network_options) - 1)) + self._notify_shared() + + def _load_execution_preset(self, *_: Any) -> None: + execution = self.catalog.load("examples", str(self.execution_preset.value)) + for control, value in ( (self.tp, execution["tensor_par"]), (self.pp, execution["pipeline_par"]), (self.dp, execution["data_par"]), @@ -305,41 +369,44 @@ def _reload_presets(self, *_: Any) -> None: (self.interleaving, execution.get("pipeline_interleaving", 1)), ): control.set_value(float(value)) - self.recompute.set_value(execution.get("activation_recompute", "none")) communication = execution.get("tensor_par_comm_type", "ar") self.communication.set_value(communication if communication in {"ar", "rs_ag"} else "ar") self.optimizer_sharding.set_value(bool(execution.get("optimizer_sharding", False))) - datatypes = self._supported_datatypes(hardware_name) - self.datatype.set_options(list(datatypes), value=self._initial_datatype(execution, hardware_name)) - network_options = tuple(range(len(hardware.get("networks", ())))) - if not network_options: - raise ValueError(f"硬件预设 {hardware_name} 没有网络层级") + supported = self._supported_datatypes(str(self.hardware_preset.value)) + datatype = str(execution.get("datatype", supported[0])) + self.datatype.set_value(datatype if datatype in supported else supported[0]) for control, field in ( (self.tp_network, "tensor_par_net"), (self.pp_network, "pipeline_par_net"), (self.dp_network, "data_par_net"), ): - control.set_options( - list(network_options), - value=min(int(execution.get(field, 0)), len(network_options) - 1), - ) + options = tuple(control.options) + value = min(int(execution.get(field, 0)), len(options) - 1) + control.set_value(value) options = self._parallel_options(execution) self.tp_candidates.set_options(list(options), value=[int(execution["tensor_par"])]) self.pp_candidates.set_options(list(options), value=[int(execution["pipeline_par"])]) self.dp_candidates.set_options(list(options), value=[int(execution["data_par"])]) + self._update_candidate_count() + self._notify_shared() + + def _notify_shared(self, *_: Any) -> None: self._update_world_size() + self.on_change("shared") + + def _notify_sweep(self, *_: Any) -> None: self._update_candidate_count() + self.on_change("sweep") - def _update_world_size(self, *_: Any) -> None: + def _update_world_size(self) -> None: values = (self.tp.value, self.pp.value, self.dp.value) if any(value is None for value in values): - self.world_size_label.set_text("World size: —") + self.world_size_label.set_text("—") return - world_size = int(values[0]) * int(values[1]) * int(values[2]) - self.world_size_label.set_text(f"World size: {world_size:,}") + self.world_size_label.set_text(f"{int(values[0]) * int(values[1]) * int(values[2]):,} devices") def candidate_count(self) -> int: return ( @@ -348,13 +415,82 @@ def candidate_count(self) -> int: * len(self.dp_candidates.value or ()) ) - def _update_candidate_count(self, *_: Any) -> None: + def _update_candidate_count(self) -> None: count = self.candidate_count() - self.candidate_count_label.set_text(f"Candidates: {count} / 128") - if count > 128: - self.candidate_count_label.classes(add="text-negative", remove="bp-muted") + self.candidate_count_label.set_text(f"{count} / 128") + if count > 128 or count <= 0: + self.candidate_count_label.classes(add="bp-warning", remove="bp-muted") else: - self.candidate_count_label.classes(add="bp-muted", remove="text-negative") + self.candidate_count_label.classes(add="bp-muted", remove="bp-warning") + + def set_mode(self, mode: WorkbenchMode) -> None: + self.sweep_section.set_visibility(mode is WorkbenchMode.SWEEP) + + def set_model_preset(self, value: str) -> None: + if str(self.model_preset.value) == value: + return + self.model_preset.set_value(value) + self._load_model_preset() + + def set_hardware_preset(self, value: str) -> None: + if str(self.hardware_preset.value) == value: + return + self.hardware_preset.set_value(value) + self._load_hardware_preset() + + def set_parallel_value(self, axis: str, value: int) -> None: + controls = {"tp": self.tp, "pp": self.pp, "dp": self.dp} + control = controls[axis] + if int(control.value or 0) == value: + return + control.set_value(float(value)) + self._notify_shared() + + def load_point_parallelism(self, tensor_parallel: int, pipeline_parallel: int, data_parallel: int) -> None: + """Load a Case selected from a CaseSet without mutating the CaseSet definition.""" + + for control, value in ( + (self.tp, tensor_parallel), + (self.pp, pipeline_parallel), + (self.dp, data_parallel), + ): + control.set_value(float(value)) + self._update_world_size() + + def set_parallel_candidates(self, axis: str, values: tuple[int, ...]) -> None: + controls = { + "tp": self.tp_candidates, + "pp": self.pp_candidates, + "dp": self.dp_candidates, + } + control = controls[axis] + if tuple(int(value) for value in (control.value or ())) == values: + return + control.set_value(list(values)) + self._notify_sweep() + + def set_calibration(self, value: str) -> None: + if value not in _CALIBRATION_LABELS: + raise ValueError("请选择有效的估算证据模式") + if str(self.calibration.value) == value: + return + self.calibration.set_value(value) + self._notify_shared() + + def summary(self) -> tuple[tuple[str, str], ...]: + model_name, execution_name, hardware_name = self._selection_names() + values = (self.tp.value, self.pp.value, self.dp.value) + world_size = ( + "—" if any(value is None for value in values) else f"{int(values[0]) * int(values[1]) * int(values[2]):,}" + ) + return ( + ("模型", _strip_json_suffix(model_name)), + ("目标", _strip_json_suffix(hardware_name)), + ("策略", _strip_json_suffix(execution_name)), + ("并行度", f"TP {int(self.tp.value or 0)} · PP {int(self.pp.value or 0)} · DP {int(self.dp.value or 0)}"), + ("设备数", world_size), + ("证据", str(self.calibration.value)), + ) def draft(self) -> AnalysisDraft: model_name, execution_name, hardware_name = self._selection_names() @@ -364,12 +500,12 @@ def draft(self) -> AnalysisDraft: pp = _positive_int(self.pp.value, "PP") dp = _positive_int(self.dp.value, "DP") model_data = { - "hidden": _positive_int(self.hidden.value, "Hidden size"), - "feedforward": _positive_int(self.feedforward.value, "Feed-forward size"), - "seq_size": _positive_int(self.sequence.value, "Sequence length"), - "attn_heads": _positive_int(self.heads.value, "Attention heads"), - "attn_size": _positive_int(self.head_size.value, "Attention head size"), - "num_blocks": _positive_int(self.blocks.value, "Transformer blocks"), + "hidden": _positive_int(self.hidden.value, "隐藏维度"), + "feedforward": _positive_int(self.feedforward.value, "前馈维度"), + "seq_size": _positive_int(self.sequence.value, "序列长度"), + "attn_heads": _positive_int(self.heads.value, "注意力头数"), + "attn_size": _positive_int(self.head_size.value, "单头维度"), + "num_blocks": _positive_int(self.blocks.value, "Transformer 层数"), } execution_data.update( { @@ -377,12 +513,12 @@ def draft(self) -> AnalysisDraft: "pipeline_par": pp, "data_par": dp, "num_procs": tp * pp * dp, - "batch_size": _positive_int(self.global_batch.value, "Global batch"), - "microbatch_size": _positive_int(self.microbatch.value, "Microbatch"), + "batch_size": _positive_int(self.global_batch.value, "全局批量"), + "microbatch_size": _positive_int(self.microbatch.value, "微批量"), "datatype": str(self.datatype.value), "activation_recompute": str(self.recompute.value), "tensor_par_comm_type": str(self.communication.value), - "pipeline_interleaving": _positive_int(self.interleaving.value, "Pipeline interleaving"), + "pipeline_interleaving": _positive_int(self.interleaving.value, "流水交错数"), "optimizer_sharding": bool(self.optimizer_sharding.value), "tensor_par_net": int(self.tp_network.value), "pipeline_par_net": int(self.pp_network.value), @@ -419,27 +555,13 @@ def sweep_request(self) -> SweepRequest: raise ValueError("TP、PP、DP 候选集合不能为空") return SweepRequest(base=self.draft(), **candidates) - def set_busy(self, busy: bool, label: str = "正在执行分析…") -> None: + def set_busy(self, busy: bool) -> None: for control in self._controls: control.disable() if busy else control.enable() - self.analysis_button.disable() if busy else self.analysis_button.enable() - self.sweep_button.disable() if busy else self.sweep_button.enable() - self.busy_label.set_text(label) - self.busy_row.set_visibility(busy) - - def set_progress(self, completed: int, total: int, *, visible: bool) -> None: - self.progress.set_visibility(visible) - self.progress_label.set_visibility(visible) - if total <= 0: - self.progress.set_value(0) - self.progress_label.set_text("") - return - self.progress.set_value(min(completed / total, 1.0)) - self.progress_label.set_text(f"{completed} / {total} candidates") class BlueprintingWorkbench: - """Per-client NiceGUI workbench and asynchronous application controller.""" + """Per-client controller for the blueprint-editor workbench.""" def __init__( self, @@ -449,333 +571,1007 @@ def __init__( ) -> None: self.catalog = catalog or default_catalog() self.service = service_factory() + self.mode = WorkbenchMode.ANALYSIS + self.form: ConfigurationPanel | None = None self.analysis_outcome: AnalysisOutcome | None = None self.sweep_report: SweepReport | None = None + self.sweep_base: AnalysisDraft | None = None + self.analysis_stale = False + self.sweep_stale = False + self.busy = False + self.local_error: str | None = None self._progress_completed = 0 self._progress_total = 0 + self.setup_summary: Any | None = None + self.stale_host: Any | None = None + self.loading_progress: Any | None = None + self.loading_progress_label: Any | None = None + self.setup_action: Any | None = None + self.drawer_action: Any | None = None + self.sidebar_controls: Any | None = None + self.sidebar_summary: Any | None = None + self.sidebar_action_host: Any | None = None + self.quick_controls: list[Any] = [] + self.quick_model: Any | None = None + self.quick_hardware: Any | None = None + self.quick_tp: Any | None = None + self.quick_pp: Any | None = None + self.quick_dp: Any | None = None + self.quick_calibration: Any | None = None + self._syncing_quick_controls = False + self.batch_results_host: Any | None = None + self.batch_status_filter: Any | None = None + self.batch_tp_filter: Any | None = None + self.batch_pp_filter: Any | None = None + self.batch_dp_filter: Any | None = None + self.batch_grid: Any | None = None def build(self) -> None: ui.add_css(WORKBENCH_CSS) - ui.colors(primary="#7c68ff", secondary="#22d3ee", accent="#f59e0b", dark="#070a12") - ui.dark_mode(True) - ui.page_title("Blueprinting · Architecture Workbench") + ui.colors(primary="#2563eb", secondary="#2563eb", positive="#15803d", warning="#b45309", negative="#b91c1c") + ui.dark_mode(False) + ui.page_title("Blueprinting · 硬件架构工作台") self._build_legacy_dialog() - with ui.header(elevated=False).classes("bp-header items-center no-wrap"): - ui.button(icon="tune", on_click=self._toggle_drawer).props("flat round dense").classes("lt-md") - with ui.row().classes("items-center gap-3 no-wrap"): - with ui.element("div").classes("bp-brand-mark"): - ui.icon("architecture", size="22px", color="secondary") - with ui.column().classes("gap-0"): - ui.label("Blueprinting").classes("bp-brand-title") - ui.label("Hardware architecture workbench").classes("bp-brand-subtitle") + with ui.left_drawer(value=True, bordered=False) as self.sidebar: + self.sidebar.props("width=288 breakpoint=980").classes("bp-sidebar") + with ui.column().classes("bp-sidebar-shell w-full no-wrap"): + with ui.row().classes("bp-sidebar-brand w-full items-center gap-3 no-wrap"): + ui.element("div").classes("bp-brand-mark") + with ui.column().classes("gap-0 min-w-0"): + ui.label("Blueprinting").classes("bp-brand-title") + ui.label("硬件架构探索工作台").classes("bp-brand-subtitle") + ui.separator().classes("bp-sidebar-rule") + ui.label("ANALYSIS LENS").classes("bp-sidebar-kicker") + ui.label("观察尺度").classes("bp-sidebar-title") + with ( + ui.tabs(value=self.mode.value, on_change=self._change_mode) + .props("dense no-caps indicator-color=transparent") + .classes("bp-mode-switch w-full mt-2") + .mark("mode-switch") as self.mode_switch + ): + self.analysis_mode_tab = ui.tab( + WorkbenchMode.ANALYSIS.value, + "单点剖析", + icon="query_stats", + ).mark("mode-analysis") + self.sweep_mode_tab = ui.tab( + WorkbenchMode.SWEEP.value, + "批量探索", + icon="scatter_plot", + ).mark("mode-sweep") + ui.separator().classes("bp-sidebar-rule") + self.sidebar_controls = ui.column().classes("w-full gap-2") + self.sidebar_summary = ui.column().classes("bp-sidebar-summary w-full gap-2") + self.sidebar_action_host = ui.column().classes("w-full mt-2") + with ui.column().classes("bp-sidebar-footer w-full gap-2"): + with ui.row().classes("items-center gap-2"): + ui.element("span").classes("bp-service-dot") + ui.label("本地服务可用").classes("bp-sidebar-meta") + ui.label("Portable plan · Analytical evidence").classes("bp-sidebar-meta bp-mono") + ui.button("Legacy 工具", icon="history", on_click=self.legacy_dialog.open).props( + "flat no-caps align=left" + ).classes("bp-sidebar-legacy w-full") - with ( - ui.tabs() - .props("dense no-caps indicator-color=secondary active-color=white") - .classes("self-stretch q-ml-lg") as self.tabs - ): - self.overview_tab = ui.tab("overview", "分析总览", icon="dashboard").mark("tab-overview") - self.ir_tab = ui.tab("ir", "IR 推导审计", icon="schema").mark("tab-ir") - self.sweep_tab = ui.tab("sweep", "策略空间", icon="scatter_plot").mark("tab-sweep") - ui.space() - ui.chip("service ready", icon="check_circle", color="transparent", text_color="positive").props( - "dense outline" - ).classes("gt-sm bp-mono") - ui.button("Legacy", icon="history", on_click=self.legacy_dialog.open).props("flat dense no-caps") - - with ui.left_drawer(value=True, bordered=False) as self.drawer: - self.drawer.props("width=318 breakpoint=900").classes("bp-drawer") - with ui.column().classes("w-full gap-3"): - self.form = ConfigurationPanel( - self.catalog, - on_analyze=self.run_analysis, - on_sweep=self.run_sweep, - ) - self.form.build() - self.progress_timer = ui.timer(0.12, self._poll_progress, active=False, immediate=False) + with ( + ui.dialog() as self.config_dialog, + ui.card().classes("bp-config-dialog").mark("configuration-modal"), + ui.column().classes("bp-dialog-shell w-full no-wrap"), + ): + with ui.row().classes("bp-dialog-heading w-full items-center no-wrap"): + with ui.column().classes("gap-0"): + self.dialog_title = ui.label("Case 完整配置").classes("bp-section-title") + self.dialog_copy = ui.label("工作负载、映射与证据细节。 ").classes("bp-section-copy") + ui.space() + ui.button(icon="close", on_click=self.config_dialog.close).props("flat round dense") + self.drawer_form_host = ui.column().classes("bp-dialog-form w-full") + self.drawer_footer = ui.column().classes("bp-dialog-footer w-full gap-2") with ui.element("main").classes("bp-main"): - self._render_hero() - with ui.tab_panels(self.tabs, value=self.overview_tab, animated=True, keep_alive=True).classes("w-full"): - with ui.tab_panel(self.overview_tab): - self.overview_content = ui.column().classes("w-full gap-5") - with ui.tab_panel(self.ir_tab): - self.ir_content = ui.column().classes("w-full gap-5") - with ui.tab_panel(self.sweep_tab): - self.sweep_content = ui.column().classes("w-full gap-5") - - self._render_overview() - self._render_ir_audit() - self._render_sweep() + with ui.row().classes("bp-mobile-bar w-full items-center gap-3 no-wrap"): + ui.button(icon="menu", on_click=self._toggle_sidebar).props("flat round dense") + ui.element("div").classes("bp-brand-mark bp-brand-mark--mobile") + ui.label("Blueprinting").classes("bp-brand-title") + ui.space() + with ui.row().classes("items-center gap-2 no-wrap"): + ui.element("span").classes("bp-service-dot") + ui.label("本地服务").classes("bp-mobile-service") + self.workspace = ui.column().classes("w-full gap-4") + + self.progress_timer = ui.timer(0.12, self._poll_progress, active=False, immediate=False) + self._render_workspace() + + def _toggle_sidebar(self) -> None: + if hasattr(self, "sidebar"): + self.sidebar.toggle() def _build_legacy_dialog(self) -> None: - with ui.dialog() as self.legacy_dialog, ui.card().classes("bp-card p-6").style("width: 560px; max-width: 92vw"): + with ui.dialog() as self.legacy_dialog, ui.card().classes("bp-card").style("width: 560px; max-width: 92vw"): with ui.row().classes("items-center gap-3"): - ui.icon("inventory_2", size="28px", color="secondary") + ui.icon("inventory_2", size="26px", color="secondary") with ui.column().classes("gap-0"): - ui.label("Calculon / Streamlit Legacy").classes("text-lg font-semibold") - ui.label("旧界面保持隔离,不参与 Blueprinting 分析路径。").classes("text-xs bp-muted") + ui.label("Calculon / Streamlit Legacy").classes("bp-card-title") + ui.label("旧工具保持隔离,不参与 Blueprinting 主分析路径。 ").classes("bp-card-copy") ui.separator().classes("my-2") - ui.label("需要旧 Calculon 或浮点工具时,单独启动:").classes("text-sm") + ui.label("需要旧 Calculon 或浮点工具时,请单独启动:").classes("text-sm") ui.code("uv run streamlit run streamlit_app.py", language="bash").classes("bp-code") with ui.row().classes("w-full justify-end gap-2"): ui.link("打开 localhost:8501", "http://127.0.0.1:8501", new_tab=True).classes("text-secondary") ui.button("关闭", on_click=self.legacy_dialog.close).props("flat no-caps") - def _toggle_drawer(self) -> None: - if hasattr(self, "drawer"): - self.drawer.toggle() - - def _render_hero(self) -> None: - with ui.element("section").classes("bp-hero"): - ui.label("FORMAL EXPLORATION · EVIDENCE DRIVEN").classes("bp-kicker") - ui.label("从工作负载语义走向可审计的硬件蓝图").classes("bp-hero-title") - ui.label( - "以类型化 IR、可验证 lowering 和版本化性能证据,把模型、并行策略与候选硬件映射成可比较的执行计划。" - ).classes("bp-hero-copy") - with ui.row().classes("gap-2 mt-4"): - for label in ("ModelIR", "DistributedTaskIR", "PortablePlanIR", "Evidence", "Pareto"): - ui.label(label).classes("bp-chip") + def _change_mode(self, event: Any) -> None: + if self.busy: + self.mode_switch.set_value(self.mode.value) + return + self.mode = WorkbenchMode(str(event.value)) + self.local_error = None + self.config_dialog.close() + if self.form is not None: + self.form.set_mode(self.mode) + self._render_sidebar_controls() + self._render_workspace() + + def _ensure_form(self, host: Any) -> None: + if self.form is None: + with host: + self.form = ConfigurationPanel(self.catalog, on_change=self._configuration_changed) + self.form.build() + else: + self.form.root.move(host) + self.form.set_mode(self.mode) + self._render_sidebar_controls() + self._render_sidebar_summary() + self._render_sidebar_action() + + def _detach_form(self) -> None: + if self.form is not None and self.form.root is not None: + self.form.root.move(self.drawer_form_host) + + def _render_sidebar_controls(self) -> None: + if self.sidebar_controls is None or self.form is None: + return + self.sidebar_controls.clear() + self.quick_controls = [] + self.quick_model = None + self.quick_hardware = None + self.quick_tp = None + self.quick_pp = None + self.quick_dp = None + self.quick_calibration = None + with self.sidebar_controls: + ui.label("CASESET CONTROLS" if self.mode is WorkbenchMode.SWEEP else "CASE CONTROLS").classes( + "bp-sidebar-kicker" + ) + with ui.element("section").classes("bp-sidebar-controls-card"): + with ui.row().classes("w-full items-center justify-between gap-2"): + ui.label("空间与过滤" if self.mode is WorkbenchMode.SWEEP else "当前 Case").classes( + "bp-sidebar-title" + ) + ui.label("范围" if self.mode is WorkbenchMode.SWEEP else "具体值").classes("bp-sidebar-meta") + + model_options = {name: _strip_json_suffix(name) for name in self.catalog.names("models")} + hardware_options = {name: _strip_json_suffix(name) for name in self.catalog.names("systems")} + self.quick_model = ( + ui.select( + model_options, + label="模型", + value=str(self.form.model_preset.value), + on_change=self._quick_model_changed, + ) + .props("filled dense dark options-dense popup-content-class=bp-sidebar-menu") + .classes("bp-sidebar-control w-full") + .mark("quick-model") + ) + self.quick_hardware = ( + ui.select( + hardware_options, + label="目标", + value=str(self.form.hardware_preset.value), + on_change=self._quick_hardware_changed, + ) + .props("filled dense dark options-dense popup-content-class=bp-sidebar-menu") + .classes("bp-sidebar-control w-full") + .mark("quick-hardware") + ) + self.quick_controls.extend((self.quick_model, self.quick_hardware)) + + ui.label("候选集合" if self.mode is WorkbenchMode.SWEEP else "并行拓扑").classes( + "bp-sidebar-field-label" + ) + with ui.element("div").classes("bp-sidebar-parallel-grid"): + if self.mode is WorkbenchMode.SWEEP: + self.quick_tp = self._quick_candidate_select("TP", "tp", self.form.tp_candidates.value) + self.quick_pp = self._quick_candidate_select("PP", "pp", self.form.pp_candidates.value) + self.quick_dp = self._quick_candidate_select("DP", "dp", self.form.dp_candidates.value) + else: + self.quick_tp = self._quick_parallel_number("TP", "tp", self.form.tp.value) + self.quick_pp = self._quick_parallel_number("PP", "pp", self.form.pp.value) + self.quick_dp = self._quick_parallel_number("DP", "dp", self.form.dp.value) + self.quick_controls.extend((self.quick_tp, self.quick_pp, self.quick_dp)) + + self.quick_calibration = ( + ui.select( + list(_CALIBRATION_LABELS), + label="估算证据", + value=str(self.form.calibration.value), + on_change=self._quick_calibration_changed, + ) + .props("filled dense dark options-dense popup-content-class=bp-sidebar-menu") + .classes("bp-sidebar-control w-full") + .mark("quick-calibration") + ) + self.quick_controls.append(self.quick_calibration) + ui.button( + "定义 CaseSet" if self.mode is WorkbenchMode.SWEEP else "完整 Case 配置", + icon="tune", + on_click=self._open_configuration, + ).props("outline dense no-caps").classes("bp-sidebar-full-config w-full").mark( + "open-full-configuration" + ) + self._set_quick_controls_busy(self.busy) + + def _quick_parallel_number(self, label: str, axis: str, value: Any) -> Any: + return ( + ui.number( + label, + value=float(value), + min=1, + step=1, + precision=0, + on_change=partial(self._quick_parallel_changed, axis), + ) + .props("filled dense dark") + .classes("bp-sidebar-control") + .mark(f"quick-{axis}") + ) + + def _quick_candidate_select(self, label: str, axis: str, values: Any) -> Any: + return ( + ui.select( + list(_PARALLEL_OPTIONS), + label=label, + value=[int(value) for value in (values or ())], + multiple=True, + on_change=partial(self._quick_candidates_changed, axis), + ) + .props("filled dense dark options-dense popup-content-class=bp-sidebar-menu") + .classes("bp-sidebar-control") + .mark(f"quick-{axis}-candidates") + ) + + def _quick_model_changed(self, event: Any) -> None: + if self._syncing_quick_controls or self.form is None or event.value is None: + return + self.form.set_model_preset(str(event.value)) + + def _quick_hardware_changed(self, event: Any) -> None: + if self._syncing_quick_controls or self.form is None or event.value is None: + return + self.form.set_hardware_preset(str(event.value)) + + def _quick_parallel_changed(self, axis: str, event: Any) -> None: + if self._syncing_quick_controls or self.form is None or event.value is None: + return + self.form.set_parallel_value(axis, _positive_int(event.value, axis.upper())) + + def _quick_candidates_changed(self, axis: str, event: Any) -> None: + if self._syncing_quick_controls or self.form is None: + return + values = tuple(int(value) for value in (event.value or ())) + self.form.set_parallel_candidates(axis, values) + + def _quick_calibration_changed(self, event: Any) -> None: + if self._syncing_quick_controls or self.form is None or event.value is None: + return + self.form.set_calibration(str(event.value)) + + def _sync_sidebar_controls(self) -> None: + if self.form is None or self.quick_model is None: + return + self._syncing_quick_controls = True + try: + self.quick_model.set_value(str(self.form.model_preset.value)) + self.quick_hardware.set_value(str(self.form.hardware_preset.value)) + if self.mode is WorkbenchMode.SWEEP: + self.quick_tp.set_value(list(self.form.tp_candidates.value or ())) + self.quick_pp.set_value(list(self.form.pp_candidates.value or ())) + self.quick_dp.set_value(list(self.form.dp_candidates.value or ())) + else: + self.quick_tp.set_value(float(self.form.tp.value)) + self.quick_pp.set_value(float(self.form.pp.value)) + self.quick_dp.set_value(float(self.form.dp.value)) + self.quick_calibration.set_value(str(self.form.calibration.value)) + finally: + self._syncing_quick_controls = False + + def _set_quick_controls_busy(self, busy: bool) -> None: + for control in self.quick_controls: + control.disable() if busy else control.enable() + + def _configuration_changed(self, scope: str) -> None: + self.local_error = None + if scope == "shared": + if self.analysis_outcome is not None: + self.analysis_stale = True + if self.sweep_report is not None: + self.sweep_stale = True + elif self.sweep_report is not None: + self.sweep_stale = True + if self.setup_summary is not None: + self._render_setup_summary() + self._render_drawer_footer() + self._render_stale_status() + self._sync_sidebar_controls() + self._render_sidebar_summary() + self._render_sidebar_action() + + def _render_workspace(self) -> None: + self._detach_form() + self.workspace.clear() + self.setup_summary = None + self.stale_host = None + self.loading_progress = None + self.loading_progress_label = None + self.setup_action = None + self.batch_results_host = None + self.batch_status_filter = None + self.batch_tp_filter = None + self.batch_pp_filter = None + self.batch_dp_filter = None + self.batch_grid = None + with self.workspace: + if self.busy: + self._render_loading() + elif self.mode is WorkbenchMode.ANALYSIS and self.analysis_outcome is not None: + self._render_analysis_result() + elif self.mode is WorkbenchMode.SWEEP and self.sweep_report is not None: + self._render_sweep_result() + else: + self._render_setup() + self._render_sidebar_summary() + self._render_sidebar_action() + + def _render_sidebar_summary(self) -> None: + if self.sidebar_summary is None: + return + self.sidebar_summary.clear() + with self.sidebar_summary: + ui.label("CASESET STATUS" if self.mode is WorkbenchMode.SWEEP else "CASE STATUS").classes( + "bp-sidebar-kicker" + ) + with ui.row().classes("w-full items-center justify-between gap-2"): + ui.label("候选集合" if self.mode is WorkbenchMode.SWEEP else "当前 Case").classes("bp-sidebar-title") + if self.form is None: + ui.label("配置载入后显示摘要。 ").classes("bp-sidebar-meta") + return + summary = dict(self.form.summary()) + with ui.element("div").classes("bp-sidebar-fact-grid"): + self._sidebar_fact("设备", summary["设备数"]) + self._sidebar_fact( + "批量", + f"{int(self.form.global_batch.value or 0)} / {int(self.form.microbatch.value or 0)}", + ) + self._sidebar_fact("数据", str(self.form.datatype.value)) + if self.mode is WorkbenchMode.SWEEP: + self._sidebar_fact("Case", f"{self.form.candidate_count()} / 128") + else: + self._sidebar_fact( + "TP · PP · DP", + f"{int(self.form.tp.value or 0)} · {int(self.form.pp.value or 0)} · " + f"{int(self.form.dp.value or 0)}", + ) + stale = self.analysis_stale if self.mode is WorkbenchMode.ANALYSIS else self.sweep_stale + has_result = ( + self.analysis_outcome is not None + if self.mode is WorkbenchMode.ANALYSIS + else self.sweep_report is not None + ) + if self.busy: + status_class, status_text = "bp-sidebar-state bp-sidebar-state--active", "正在运行" + elif stale: + status_class, status_text = "bp-sidebar-state bp-sidebar-state--warning", "结果需要更新" + elif has_result: + status_class, status_text = "bp-sidebar-state bp-sidebar-state--ready", "结果已就绪" + else: + status_class, status_text = "bp-sidebar-state", "等待运行" + ui.label(status_text).classes(status_class) + + def _render_sidebar_action(self) -> None: + if self.sidebar_action_host is None: + return + self.sidebar_action_host.clear() + if self.form is None: + return + + has_result = ( + self.analysis_outcome is not None if self.mode is WorkbenchMode.ANALYSIS else self.sweep_report is not None + ) + stale = self.analysis_stale if self.mode is WorkbenchMode.ANALYSIS else self.sweep_stale + if self.mode is WorkbenchMode.ANALYSIS: + label = "更新当前 Case" if stale else ("重新剖析" if has_result else "剖析当前 Case") + marker = "sidebar-run-analysis" + callback = self.run_analysis + else: + label = "更新 CaseSet" if stale else ("重新评估" if has_result else "评估全部 Case") + marker = "sidebar-run-sweep" + callback = self.run_sweep + + with self.sidebar_action_host: + action = ( + ui.button(label, icon="play_arrow", on_click=callback) + .props("unelevated no-caps") + .classes("bp-sidebar-primary w-full") + .mark(marker) + ) + if self.busy or (self.mode is WorkbenchMode.SWEEP and not 0 < self.form.candidate_count() <= 128): + action.disable() + + @staticmethod + def _sidebar_fact(label: str, value: str) -> None: + with ui.column().classes("bp-sidebar-fact gap-0"): + ui.label(label).classes("bp-sidebar-label") + ui.label(value).classes("bp-sidebar-value") + + def _render_setup(self) -> None: + if self.mode is WorkbenchMode.ANALYSIS: + self._workspace_heading( + "POINT LENS", + "单点剖析", + "聚焦一个 Case,解释可行性、解析任务贡献、资源约束与证据边界。", + ) + else: + self._workspace_heading( + "BATCH LENS", + "批量探索", + "把一组 Case 作为整体,观察分布、上下界、可行边界并逐步收缩候选空间。", + ) + self._ensure_form(self.drawer_form_host) + with ui.element("section").classes("bp-evidence-surface bp-setup-evidence"): + with ui.element("div").classes("bp-evidence-section"): + self.setup_summary = ui.column().classes("w-full gap-3") + with ui.element("div").classes("bp-evidence-section"): + if self.mode is WorkbenchMode.ANALYSIS: + self._card_heading("这个视图回答什么", "从结果下钻到任务和证据,不要求先完成固定步骤。") + focus_items = ( + ("01", "是否可行", "检查单设备容量约束和结构化诊断。"), + ("02", "时间花在哪里", "按 phase 展开解析任务贡献与延迟组成。"), + ("03", "结论能相信到哪里", "核对 evidence revision、限制与 canonical audit。"), + ) + else: + self._card_heading("这个视图回答什么", "从总体分布逐步过滤,再进入任意单点继续剖析。") + focus_items = ( + ("01", "空间长什么样", "观察 Case 分布、成功率和上下界。"), + ("02", "边界在哪里", "识别容量不可行区域和延迟—内存非支配集。"), + ("03", "哪个点值得展开", "筛选并选择 Case,切换到单点剖析。"), + ) + with ui.element("div").classes("bp-focus-grid"): + for index, title, copy in focus_items: + self._setup_focus(index, title, copy) + self._render_setup_summary() + + @staticmethod + def _setup_focus(index: str, title: str, copy: str) -> None: + with ui.element("div").classes("bp-focus-card"): + ui.label(index).classes("bp-kicker bp-mono") + ui.label(title).classes("bp-card-title mt-1") + ui.label(copy).classes("bp-card-copy mt-1") + + def _render_setup_summary(self) -> None: + if self.setup_summary is None or self.form is None: + return + self.setup_summary.clear() + summary = dict(self.form.summary()) + with self.setup_summary: + with ui.row().classes("w-full items-end justify-between gap-3"): + with ui.column().classes("gap-0"): + ui.label("CURRENT CASESET" if self.mode is WorkbenchMode.SWEEP else "CURRENT CASE").classes( + "bp-kicker" + ) + ui.label("批量候选定义" if self.mode is WorkbenchMode.SWEEP else "当前分析对象").classes( + "bp-result-title mt-1" + ) + ui.label( + f"{self.form.candidate_count()} cases" + if self.mode is WorkbenchMode.SWEEP + else f"{summary['设备数']} devices" + ).classes("bp-context-chip bp-mono") + with ui.element("div").classes("bp-case-summary-grid"): + self._case_summary_item("工作负载", summary["模型"]) + self._case_summary_item("目标", summary["目标"]) + if self.mode is WorkbenchMode.SWEEP: + dimensions = ( + f"{len(self.form.tp_candidates.value or ())} × " + f"{len(self.form.pp_candidates.value or ())} × " + f"{len(self.form.dp_candidates.value or ())}" + ) + self._case_summary_item("搜索维度", dimensions) + else: + self._case_summary_item("映射", summary["并行度"]) + self._case_summary_item("Evidence", summary["证据"]) + if self.local_error: + with ui.row().classes("bp-inline-error items-start gap-2 no-wrap mt-2"): + ui.icon("error", size="18px") + ui.label(self.local_error).classes("text-xs") + with ui.row().classes("bp-setup-boundary items-start gap-2 no-wrap"): + ui.icon("info", size="17px", color="secondary") + boundary = ( + "当前显示解析任务贡献,不是事件级 Timeline。" + if self.mode is WorkbenchMode.ANALYSIS + else "当前批量空间仅覆盖所选 TP / PP / DP 组合,不代表通用硬件蓝图搜索。" + ) + ui.label(boundary).classes("bp-card-copy") + label = "剖析当前 Case" if self.mode is WorkbenchMode.ANALYSIS else "评估全部 Case" + marker = "run-analysis" if self.mode is WorkbenchMode.ANALYSIS else "run-sweep" + callback = self.run_analysis if self.mode is WorkbenchMode.ANALYSIS else self.run_sweep + with ui.row().classes("bp-setup-actions w-full items-center justify-end gap-2"): + ui.button("更多设置", icon="tune", on_click=self._open_configuration).props( + "outline dense no-caps" + ).classes("bp-secondary-action").mark("setup-full-configuration") + self.setup_action = ( + ui.button(label, icon="play_arrow", on_click=callback) + .props("unelevated no-caps") + .classes("bp-primary-action") + .mark(marker) + ) + if self.mode is WorkbenchMode.SWEEP and not 0 < self.form.candidate_count() <= 128: + self.setup_action.disable() + ui.label("候选组合必须在 1–128 之间。 ").classes("bp-card-copy bp-warning") + + @staticmethod + def _case_summary_item(label: str, value: str) -> None: + with ui.column().classes("bp-case-summary-item gap-1"): + ui.label(label).classes("bp-summary-label") + ui.label(value).classes("bp-case-summary-value bp-mono") + + def _open_configuration(self) -> None: + if self.form is None: + return + if self.mode is WorkbenchMode.ANALYSIS: + self.dialog_title.set_text("Case 完整配置") + self.dialog_copy.set_text("编辑当前工作负载、映射、证据与高级约束。") + else: + self.dialog_title.set_text("CaseSet 定义") + self.dialog_copy.set_text("编辑共享基线、候选范围与批量评估证据。") + self.form.root.move(self.drawer_form_host) + self.form.set_mode(self.mode) + self._render_drawer_footer() + self.config_dialog.open() + + def _render_drawer_footer(self) -> None: + if not hasattr(self, "drawer_footer"): + return + self.drawer_footer.clear() + self.drawer_action = None + if self.form is None: + return + with self.drawer_footer: + if self.local_error: + with ui.row().classes("bp-inline-error items-start gap-2 no-wrap"): + ui.icon("error", size="18px") + ui.label(self.local_error).classes("text-xs") + if self.mode is WorkbenchMode.SWEEP: + with ui.row().classes("w-full justify-between"): + ui.label("Case 组合").classes("bp-summary-label") + ui.label(f"{self.form.candidate_count()} / 128").classes("bp-summary-value bp-mono") + has_result = ( + self.analysis_outcome is not None + if self.mode is WorkbenchMode.ANALYSIS + else self.sweep_report is not None + ) + if self.mode is WorkbenchMode.ANALYSIS: + label = "重新剖析当前 Case" if has_result else "剖析当前 Case" + marker = "rerun-analysis" if has_result else "drawer-run-analysis" + else: + label = "重新评估 CaseSet" if has_result else "评估全部 Case" + marker = "rerun-sweep" if has_result else "drawer-run-sweep" + callback = self.run_analysis if self.mode is WorkbenchMode.ANALYSIS else self.run_sweep + self.drawer_action = ( + ui.button(label, icon="refresh", on_click=callback) + .props("unelevated no-caps") + .classes("bp-primary-action w-full") + .mark(marker) + ) + if self.mode is WorkbenchMode.SWEEP and not 0 < self.form.candidate_count() <= 128: + self.drawer_action.disable() async def run_analysis(self) -> None: + if self.form is None: + return try: draft = self.form.draft() - self.form.set_busy(True, "正在推导 canonical IR 并估算…") - ui.notify("分析已开始", type="info", position="bottom-right") + except (ValueError, TypeError) as error: + self.local_error = str(error) + self._render_setup_summary() + self._render_drawer_footer() + return + + self.local_error = None + self.busy = True + self.form.set_busy(True) + self._set_quick_controls_busy(True) + self.analysis_mode_tab.disable() + self.sweep_mode_tab.disable() + self.config_dialog.close() + self._render_workspace() + try: outcome = await run.io_bound(self.service.analyze, draft) if outcome is None: raise RuntimeError("分析服务没有返回结果") self.analysis_outcome = outcome - self._render_overview() - self._render_ir_audit() - self.tabs.set_value(self.overview_tab) - if outcome.ok: - ui.notify("分析完成", type="positive", position="bottom-right") - else: - ui.notify("分析返回结构化诊断", type="warning", position="bottom-right") - except (ValueError, TypeError) as error: - ui.notify(str(error), type="negative", position="bottom-right", close_button=True) + self.analysis_stale = False except Exception as error: # pragma: no cover - NiceGUI safety boundary - ui.notify(f"未预期的界面错误:{error}", type="negative", position="bottom-right", close_button=True) + self.local_error = f"未预期的界面错误:{error}" + self.analysis_stale = self.analysis_outcome is not None + ui.notify(self.local_error, type="negative", position="bottom-right", close_button=True) finally: + self.busy = False self.form.set_busy(False) + self._set_quick_controls_busy(False) + self.analysis_mode_tab.enable() + self.sweep_mode_tab.enable() + self._render_workspace() async def run_sweep(self) -> None: + if self.form is None: + return try: request = self.form.sweep_request() - self._progress_completed = 0 - self._progress_total = request.candidate_count - self.form.set_progress(0, request.candidate_count, visible=True) - self.form.set_busy(True, f"正在探索 {request.candidate_count} 个候选…") - self.progress_timer.activate() + except (ValueError, TypeError) as error: + self.local_error = str(error) + self._render_setup_summary() + self._render_drawer_footer() + return - def on_progress(completed: int, total: int) -> None: - self._progress_completed = completed - self._progress_total = total + self.local_error = None + self._progress_completed = 0 + self._progress_total = request.candidate_count + self.busy = True + self.form.set_busy(True) + self._set_quick_controls_busy(True) + self.analysis_mode_tab.disable() + self.sweep_mode_tab.disable() + self.config_dialog.close() + self.progress_timer.activate() + self._render_workspace() + + def on_progress(completed: int, total: int) -> None: + self._progress_completed = completed + self._progress_total = total + try: report = await run.io_bound(self.service.sweep, request, on_progress) if report is None: - raise RuntimeError("策略搜索服务没有返回结果") + raise RuntimeError("批量评估服务没有返回结果") self.sweep_report = report + self.sweep_base = request.base + self.sweep_stale = False self._progress_completed = request.candidate_count - self._render_sweep() - self.tabs.set_value(self.sweep_tab) - ui.notify( - f"策略搜索完成:{report.succeeded_count}/{len(report.cases)} 成功", - type="positive", - position="bottom-right", - ) - except (ValueError, TypeError) as error: - ui.notify(str(error), type="negative", position="bottom-right", close_button=True) except Exception as error: # pragma: no cover - NiceGUI safety boundary - ui.notify(f"未预期的界面错误:{error}", type="negative", position="bottom-right", close_button=True) + self.local_error = f"未预期的界面错误:{error}" + self.sweep_stale = self.sweep_report is not None + ui.notify(self.local_error, type="negative", position="bottom-right", close_button=True) finally: self._poll_progress() self.progress_timer.deactivate() + self.busy = False self.form.set_busy(False) - - def _poll_progress(self) -> None: - if hasattr(self, "form"): - self.form.set_progress(self._progress_completed, self._progress_total, visible=self._progress_total > 0) - - def _render_overview(self) -> None: - self.overview_content.clear() - with self.overview_content: - self._section_heading( - "Architecture analysis", - "分析总览", - "单点分析保持 workload、mapping、portable plan 与 hardware evidence 的边界可见。", + self._set_quick_controls_busy(False) + self.analysis_mode_tab.enable() + self.sweep_mode_tab.enable() + self._render_workspace() + + def _render_loading(self) -> None: + if self.mode is WorkbenchMode.ANALYSIS: + self._workspace_heading( + "POINT LENS", + "正在剖析当前 Case", + "Case 定义已冻结;正在推导任务、验证约束并解析硬件证据。", ) - outcome = self.analysis_outcome - if outcome is None: - self._empty_state( - "等待第一张硬件蓝图", - "从左侧选择模型、硬件证据和执行策略,然后运行单点分析。", - "route", + label = "ModelIR → DistributedTaskIR → PortablePlanIR" + else: + self._workspace_heading( + "BATCH LENS", + "正在评估 CaseSet", + "每个 TP / PP / DP Case 都会独立推导;失败项保留诊断并参与分布统计。", + ) + label = f"{self._progress_total} cases" + with ( + ui.element("section").classes("bp-loading-panel"), + ui.column().classes("items-center gap-3").style("width: min(520px, 100%)"), + ): + with ui.element("div").classes("bp-loading-glyph"): + ui.spinner("grid", size="30px", color="secondary") + ui.label("评估进行中").classes("bp-result-title") + ui.label(label).classes("bp-card-copy bp-mono") + if self.mode is WorkbenchMode.SWEEP: + self.loading_progress = ui.linear_progress(value=0, show_value=False, color="secondary").classes( + "w-full mt-2" ) - self._render_capability_cards() - return + self.loading_progress_label = ui.label("0 / 0 cases").classes("bp-card-copy bp-mono") + def _poll_progress(self) -> None: + if self.loading_progress is None or self.loading_progress_label is None: + return + total = self._progress_total + value = min(self._progress_completed / total, 1.0) if total > 0 else 0 + self.loading_progress.set_value(value) + self.loading_progress_label.set_text(f"{self._progress_completed} / {total} cases") + + def _render_result_context( + self, + title: str, + copy: str, + chips: tuple[str, ...], + ) -> None: + with ui.element("section").classes("bp-result-context"): + with ui.row().classes("bp-result-context-row w-full items-center gap-3"): + with ui.column().classes("gap-0 min-w-0"): + ui.label(title).classes("bp-result-title") + ui.label(copy).classes("bp-card-copy") + with ui.row().classes("bp-result-chips gap-1"): + for chip in chips: + ui.label(chip).classes("bp-context-chip") + ui.space() + ui.button("配置", icon="tune", on_click=self._open_configuration).props( + "outline dense no-caps" + ).classes("bp-secondary-action bp-result-config no-wrap").mark("edit-configuration") + self.stale_host = ui.column().classes("w-full") + self._render_stale_status() + + def _render_stale_status(self) -> None: + if self.stale_host is None: + return + self.stale_host.clear() + stale = self.analysis_stale if self.mode is WorkbenchMode.ANALYSIS else self.sweep_stale + if stale: + with self.stale_host, ui.row().classes("bp-stale-banner items-center gap-2 no-wrap"): + ui.icon("sync_problem", size="18px") + ui.label("配置已经变化;当前页面仍显示上一次结果。重新运行后才会替换。 ").classes("text-xs") + + def _render_analysis_result(self) -> None: + outcome = self.analysis_outcome + assert outcome is not None + report = outcome.report + if report is None: + self._render_result_context( + "当前 Case 未生成计划", + "配置验证或推导阶段返回了结构化诊断。", + (f"request {outcome.request_digest[:16]}",), + ) self._render_diagnostics(outcome.diagnostics) - report = outcome.report - if report is None: - ui.label(f"Request {outcome.request_digest}").classes("bp-mono text-xs bp-muted") - return + return - status_class = "bp-status" if report.feasible else "bp-status bp-status--warning" + with ui.column().classes("bp-result-header w-full gap-2"): + self._render_result_context( + f"{report.model_name} × {report.hardware_name}", + "从迭代总量下钻到 phase、subsystem、operation 与完整 portable task graph。", + ( + f"world {report.world_size}", + report.calibration_mode, + f"plan {report.plan_digest[:12]}", + ), + ) + with ( + ui.tabs() + .props("dense no-caps indicator-color=primary active-color=primary") + .classes("bp-result-tabs") as tabs + ): + conclusion_tab = ui.tab("conclusion", "时间剖析").mark("tab-conclusion") + workload_tab = ui.tab("workload", "任务与工作量").mark("tab-workload") + derivation_tab = ui.tab("derivation", "技术审计").mark("tab-derivation") + with ui.tab_panels(tabs, value=conclusion_tab, animated=False, keep_alive=True).classes( + "bp-result-panels w-full" + ): + with ui.tab_panel(conclusion_tab), ui.column().classes("w-full gap-4 pt-3"): + self._render_analysis_conclusion(outcome) + with ui.tab_panel(workload_tab), ui.column().classes("w-full gap-4 pt-3"): + self._render_workload(outcome) + with ui.tab_panel(derivation_tab), ui.column().classes("w-full gap-4 pt-3"): + self._render_derivation(outcome) + + def _render_analysis_conclusion(self, outcome: AnalysisOutcome) -> None: + report = outcome.report + assert report is not None + evidence_surface = ui.element("section").classes("bp-evidence-surface") + + with evidence_surface, ui.element("div").classes("bp-evidence-section"): + self._render_diagnostics(outcome.diagnostics) + status_class = "bp-status-banner" if report.feasible else "bp-status-banner bp-status-banner--warning" status_icon = "check_circle" if report.feasible else "warning" status_text = ( - "该候选满足当前单设备内存容量约束。" + "当前配置满足单设备内存容量约束。" if report.feasible - else "该候选完成了分析,但不满足单设备内存容量约束。" + else "分析已完成,但当前配置超过单设备内存容量约束。" ) with ui.row().classes(f"{status_class} items-center gap-2"): ui.icon(status_icon, size="18px") ui.label(status_text).classes("text-sm") - with ui.row().classes("w-full gap-3"): + with ui.element("div").classes("bp-metric-grid"): for metric in analysis_metrics(report): with ui.element("div").classes("bp-metric").style(f"--metric-color: {METRIC_COLORS[metric.tone]}"): ui.label(metric.label).classes("bp-metric-label") ui.label(metric.value).classes("bp-metric-value") ui.label(metric.detail).classes("bp-metric-detail") - with ui.row().classes("w-full gap-4 items-stretch"): - with ui.card().classes("bp-card p-4").style("flex: 1 1 560px"): - self._card_heading("延迟分解", "结构化 latency components,而不是后验调平系数。") - ui.echart(latency_chart_options(report), renderer="svg").classes("w-full h-80") - with ui.card().classes("bp-card p-4").style("flex: 1 1 560px"): - self._card_heading( - "内存分解", - f"{format_bytes(report.memory['total'])} / {format_bytes(report.memory['capacity'])}", - ) - ui.echart(memory_chart_options(report), renderer="svg").classes("w-full h-80") - - with ui.row().classes("w-full gap-4 items-stretch"): - with ui.card().classes("bp-card p-5").style("flex: 1 1 420px"): - self._card_heading("Portable workload facts", "IR 推导出的工作量事实") - workload = report.workload.to_dict() - facts = ( - ("Portable tasks", f"{workload['task_count']:,}"), - ( - "Compute / Collective", - f"{workload['compute_task_count']:,} / {workload['collective_task_count']:,}", - ), - ("Operations", format_count(workload["operations"])), - ( - "Read / Write", - f"{format_bytes(workload['read_bytes'])} / {format_bytes(workload['write_bytes'])}", - ), - ("Messages", format_bytes(workload["message_bytes"])), - ) - for label, value in facts: - with ui.row().classes("w-full justify-between items-center py-1"): - ui.label(label).classes("text-xs bp-muted") - ui.label(value).classes("text-sm bp-mono") - with ui.card().classes("bp-card p-5").style("flex: 2 1 620px"): - self._card_heading("Evidence & implementation boundary", report.evidence_revision) - with ui.row().classes("w-full gap-2"): - ui.badge(report.calibration_mode, color="primary") - ui.badge(report.hardware_name, color="secondary", text_color="dark") - ui.badge(f"world {report.world_size}", color="grey-8") - for limitation in report.limitations: - with ui.row().classes("items-start gap-2 no-wrap"): - ui.icon("subdirectory_arrow_right", size="16px", color="grey-6") - ui.label(limitation).classes("text-xs bp-muted leading-relaxed") - ui.label(f"Plan {report.plan_digest[:16]} · Request {report.request_digest[:16]}").classes( - "text-xs bp-mono bp-muted mt-2" - ) - - def _render_capability_cards(self) -> None: - capabilities = ( - ("形式化推导", "ModelIR → DistributedTaskIR → PortablePlanIR", "schema", "#7c68ff"), - ("证据估算", "计算、访存、通信与容量约束保持来源可见", "query_stats", "#22d3ee"), - ("策略探索", "保留失败候选并生成延迟—内存 Pareto 前沿", "scatter_plot", "#f59e0b"), - ) - with ui.row().classes("w-full gap-3"): - for title, copy, icon, color in capabilities: - with ui.card().classes("bp-card p-5").style("flex: 1 1 260px"): - ui.icon(icon, color=color, size="24px") - ui.label(title).classes("font-semibold mt-2") - ui.label(copy).classes("text-xs bp-muted leading-relaxed") - - def _render_ir_audit(self) -> None: - self.ir_content.clear() - with self.ir_content: - self._section_heading( - "Derivation audit", - "IR 推导审计", - "检查每一层 canonical checkpoint、Verifier、lineage digest 与 Portable task workload。", - ) - outcome = self.analysis_outcome - if outcome is None: - self._empty_state("尚无推导记录", "运行单点分析后,这里会复用同一结果进行逐层审计。", "schema") - return - self._render_diagnostics(outcome.diagnostics) - report = outcome.report - if report is None: - return - - with ui.row().classes("w-full gap-3 items-stretch"): - for index, stage in enumerate(report.stages): - with ui.element("div").classes("bp-stage"): - with ui.row().classes("w-full items-center justify-between"): - ui.label(f"0{index + 1}").classes("bp-kicker bp-mono") - ui.icon("verified", color="positive" if stage.valid else "negative", size="18px") - ui.label(stage.label).classes("font-semibold mt-2") - ui.label(stage.schema).classes("text-xs bp-mono bp-muted") - ui.label(f"{stage.node_count:,} nodes · {stage.value_count:,} values/buffers").classes( - "text-xs bp-muted mt-2" - ) - ui.label(format_seconds(stage.duration_ns / 1e9)).classes("text-xs bp-mono text-secondary") - if index < len(report.stages) - 1: - ui.icon("arrow_forward", color="grey-7", size="20px").classes("self-center gt-sm") + bottleneck = _BOTTLENECK_LABELS.get(report.bottleneck, report.bottleneck.replace("_", " ")) + with ui.element("div").classes("bp-insight"): + ui.label("当前主导项").classes("bp-kicker") + ui.label(bottleneck).classes("bp-result-title mt-1") + ui.label("这是当前解析式延迟分解中的最大组成项,不等同于事件仿真的 critical path。 ").classes( + "bp-card-copy mt-1" + ) - ui.aggrid( + with evidence_surface, ui.element("section").classes("bp-evidence-section"): + with ui.row().classes("bp-chain-header w-full items-start justify-between gap-3"): + with ui.column().classes("gap-0"): + ui.label("自顶向下时间分解").classes("bp-card-title") + ui.label( + "Iteration → 时间项 → phase / subsystem → source layer → operation;点击矩形继续下钻。 " + ).classes("bp-card-copy") + ui.label("ITERATION HIERARCHY").classes("bp-fidelity-tag bp-mono") + ui.echart(time_breakdown_chart_options(report), renderer="canvas").classes("w-full bp-time-treemap") + with ui.row().classes("bp-time-method items-start gap-2 no-wrap"): + ui.icon("functions", size="17px", color="secondary") + ui.label( + "第一级严格使用 iteration estimate;有 task 证据的时间项按 block 内贡献比例继续分解,pipeline bubble、PP 与 DP 等调度项在当前层级保持为不可再分的解析项。" + ).classes("bp-card-copy") + breakdown_rows = [ { - "columnDefs": [ - {"headerName": "Stage", "field": "label", "pinned": "left", "minWidth": 150}, - {"headerName": "Pass", "field": "pass", "minWidth": 210}, - {"headerName": "Schema", "field": "schema", "minWidth": 190}, - {"headerName": "Nodes", "field": "nodes", "type": "numericColumn"}, - {"headerName": "Values", "field": "values", "type": "numericColumn"}, - {"headerName": "Lowering ms", "field": "lowering_ms", "type": "numericColumn"}, - {"headerName": "Valid", "field": "valid"}, - {"headerName": "Digest", "field": "digest", "minWidth": 260}, - ], - "rowData": stage_rows(outcome), - "defaultColDef": {"sortable": True, "filter": True, "resizable": True}, - "domLayout": "autoHeight", - }, - theme="quartz", - ).classes("w-full bp-grid") + **row, + "share_iteration_pct": round(float(row["share_iteration"]) * 100, 4), + "share_parent_pct": round(float(row["share_parent"]) * 100, 4), + } + for row in time_breakdown_rows(report) + ] + with ( + ui.expansion("查看完整层次明细", icon="account_tree", value=False).classes( + "bp-time-details bp-card w-full" + ), + ui.column().classes("w-full gap-2 pt-2"), + ): + ui.aggrid( + { + "columnDefs": [ + {"headerName": "Level", "field": "level", "width": 82}, + {"headerName": "Path", "field": "path", "pinned": "left", "minWidth": 360}, + {"headerName": "Seconds", "field": "seconds", "type": "numericColumn"}, + { + "headerName": "% Iteration", + "field": "share_iteration_pct", + "type": "numericColumn", + }, + {"headerName": "% Parent", "field": "share_parent_pct", "type": "numericColumn"}, + {"headerName": "Kind", "field": "category", "minWidth": 150}, + ], + "rowData": breakdown_rows, + "defaultColDef": {"sortable": True, "filter": True, "resizable": True}, + "pagination": True, + "paginationPageSize": 25, + }, + theme="quartz", + auto_size_columns=False, + ).classes("w-full bp-grid").style("height: 430px") + + projection = timeline_summary(report) + with evidence_surface, ui.element("section").classes("bp-evidence-section"): + with ui.row().classes("bp-chain-header w-full items-start justify-between gap-3"): + with ui.column().classes("gap-0"): + ui.label("Portable task timeline").classes("bp-card-title") + ui.label( + "完整绘制当前 PortablePlan block 的全部 task;开始时间仅由 dependency 推导,支持横向缩放。 " + ).classes("bp-card-copy") + with ui.row().classes("gap-1"): + ui.label("BLOCK SCOPE").classes("bp-fidelity-tag bp-mono") + ui.label("DEPENDENCY PROJECTION").classes("bp-fidelity-tag bp-mono") + with ui.row().classes("bp-timeline-legend items-center gap-4"): + for engine, label in (("matrix", "Matrix"), ("vector", "Vector"), ("collective", "Collective")): + with ui.row().classes("items-center gap-1"): + ui.element("span").classes(f"bp-engine-dot bp-engine-dot--{engine}") + ui.label(label).classes("bp-summary-label") + ui.echart(dependency_timeline_chart_options(report), renderer="canvas").classes("w-full bp-timeline-chart") + with ui.element("div").classes("bp-chain-stats"): + for label, value in ( + ("Projected span", format_seconds(projection.span_seconds)), + ("Portable tasks", f"{projection.task_count:,}"), + ("Dependencies", f"{projection.dependency_count:,}"), + ("Phase lanes", f"{projection.lane_count:,}"), + ): + with ui.row().classes("items-center gap-2"): + ui.label(label).classes("bp-summary-label") + ui.label(value).classes("bp-card-copy bp-mono") + with ui.row().classes("bp-time-scope items-start gap-2 no-wrap"): + ui.icon("info", size="17px", color="secondary") + ui.label( + "该时间轴覆盖完整 portable block task graph,但尚未展开 block multiplicity、microbatch、pipeline stage、queue、overlap 与 contention;这些信息需要 ConcretePlan 和 SimulationTraceIR。" + ).classes("bp-card-copy") - for stage in report.stages: - with ( - ui.expansion( - stage.label, - caption=f"{stage.pass_name} · {stage.digest[:16]}", - icon="verified" if stage.valid else "error", - value=False, - ).classes("bp-card w-full"), - ui.column().classes("w-full gap-3 p-3"), + with ( + evidence_surface, + ui.element("div").classes("bp-evidence-section"), + ui.element("div").classes("bp-chart-grid"), + ): + with ui.element("section").classes("bp-evidence-block"): + self._card_heading("迭代一级时间项", "与上方层次视图的第一级一致;蓝色条为当前最大组成项。") + ui.echart(latency_chart_options(report), renderer="svg").classes("w-full h-80") + with ui.element("section").classes("bp-evidence-block"): + self._card_heading( + "单设备内存构成", + f"总量 {format_bytes(report.memory['total'])} / 容量 {format_bytes(report.memory['capacity'])}", + ) + ui.echart(memory_chart_options(report), renderer="svg").classes("w-full h-80") + + with ( + evidence_surface, + ui.element("div").classes("bp-evidence-section"), + ui.element("div").classes("bp-detail-grid bp-detail-grid--wide"), + ): + with ui.element("section").classes("bp-evidence-block"): + self._card_heading("证据与适用边界", report.evidence_revision) + evidence = report.evidence + for label, value in ( + ("证据模式", report.calibration_mode), + ("Matrix peak", f"{format_count(evidence['matrix_peak_ops_per_second'])} op/s"), + ("Vector peak", f"{format_count(evidence['vector_peak_ops_per_second'])} op/s"), + ("Memory peak", f"{format_bytes(evidence['memory_peak_bytes_per_second'])}/s"), + ("网络层级", str(evidence["network_tiers"])), ): - with ui.row().classes("gap-2"): - ui.badge(stage.schema, color="grey-8") - ui.badge(f"{stage.node_count} nodes", color="primary") - ui.badge(f"{stage.value_count} values", color="secondary", text_color="dark") - ui.badge(format_seconds(stage.duration_ns / 1e9), color="grey-8") - self._render_diagnostics(stage.diagnostics) - pretty_snapshot = json.dumps(json.loads(stage.snapshot_json), ensure_ascii=False, indent=2) - ui.code(pretty_snapshot, language="json").classes("bp-code") - ui.button( - "下载 canonical snapshot", - icon="download", - on_click=partial(self._download_snapshot, stage.stage, stage.digest, pretty_snapshot), - ).props("outline dense no-caps") + self._fact_row(label, value) + ui.label("实现边界").classes("bp-card-title mt-3") + for limitation in report.limitations: + with ui.row().classes("items-start gap-2 no-wrap"): + ui.icon("subdirectory_arrow_right", size="16px", color="grey-6") + ui.label(limitation).classes("bp-card-copy") + with ui.element("section").classes("bp-evidence-block"): + self._card_heading("结果身份", "用于复现与问题定位") + for label, value in ( + ("Request", report.request_digest), + ("Session", report.session_fingerprint), + ("Plan", report.plan_digest), + ("Schema", report.schema), + ): + ui.label(label).classes("bp-summary-label mt-2") + ui.label(value).classes("bp-card-copy bp-mono break-all") + + def _render_workload(self, outcome: AnalysisOutcome) -> None: + report = outcome.report + assert report is not None + workload = report.workload + evidence_surface = ui.element("section").classes("bp-evidence-surface") + facts = ( + ("Portable tasks", f"{workload['task_count']:,}", "primary"), + ( + "Compute / collective", + f"{workload['compute_task_count']:,} / {workload['collective_task_count']:,}", + "cyan", + ), + ("Operations", format_count(workload["operations"]), "violet"), + ( + "Read / write", + f"{format_bytes(workload['read_bytes'])} / {format_bytes(workload['write_bytes'])}", + "amber", + ), + ) + with ( + evidence_surface, + ui.element("div").classes("bp-evidence-section"), + ui.element("div").classes("bp-metric-grid"), + ): + for label, value, tone in facts: + with ui.element("div").classes("bp-metric").style(f"--metric-color: {METRIC_COLORS[tone]}"): + ui.label(label).classes("bp-metric-label") + ui.label(value).classes("bp-metric-value") + detail = ( + format_bytes(workload["message_bytes"]) + if label == "Compute / collective" + else "exact workload fact" + ) + ui.label(f"Messages {detail}" if label == "Compute / collective" else detail).classes( + "bp-metric-detail" + ) - self._card_heading("Portable task audit", f"{len(report.tasks):,} tasks") + with evidence_surface, ui.element("section").classes("bp-evidence-section"): + self._card_heading("Portable task audit", f"{len(report.tasks):,} 个任务;预测列来自当前 evidence view。") ui.aggrid( { "columnDefs": [ @@ -803,6 +1599,62 @@ def _render_ir_audit(self) -> None: auto_size_columns=False, ).classes("w-full bp-grid").style("height: 520px") + def _render_derivation(self, outcome: AnalysisOutcome) -> None: + report = outcome.report + assert report is not None + evidence_surface = ui.element("section").classes("bp-evidence-surface") + with evidence_surface, ui.element("div").classes("bp-evidence-section"): + ui.label("Canonical derivation checkpoints").classes("bp-kicker") + ui.label("每一层结果在提交前经过 verifier,并保留父 digest 与不可变快照。 ").classes("bp-card-copy") + with ui.element("div").classes("bp-stage-flow mt-3"): + for index, stage in enumerate(report.stages): + with ui.element("div").classes("bp-stage"): + with ui.row().classes("w-full items-center justify-between"): + ui.label(f"0{index + 1}").classes("bp-kicker bp-mono") + ui.icon( + "verified" if stage.valid else "error", + color="positive" if stage.valid else "negative", + size="18px", + ) + ui.label(stage.label).classes("bp-card-title mt-2") + ui.label(stage.schema).classes("bp-card-copy bp-mono") + ui.label(f"{stage.node_count:,} nodes · {stage.value_count:,} values/buffers").classes( + "bp-card-copy mt-2" + ) + ui.label(format_seconds(stage.duration_ns / 1e9)).classes("bp-card-copy bp-mono text-secondary") + + with evidence_surface, ui.element("div").classes("bp-evidence-section bp-derivation-details"): + for stage in report.stages: + with ( + ui.expansion( + stage.label, + caption=f"{stage.pass_name} · {stage.digest[:16]}", + icon="verified" if stage.valid else "error", + value=False, + ).classes("bp-card w-full"), + ui.column().classes("w-full gap-3 pt-2"), + ): + with ui.row().classes("gap-2"): + for value in ( + stage.schema, + f"{stage.node_count} nodes", + f"{stage.value_count} values", + format_seconds(stage.duration_ns / 1e9), + ): + ui.label(value).classes("bp-data-chip bp-mono") + if stage.parent_digests: + ui.label(f"Parents · {' · '.join(stage.parent_digests)}").classes( + "bp-card-copy bp-mono break-all" + ) + self._render_diagnostics(stage.diagnostics) + pretty_snapshot = json.dumps(json.loads(stage.snapshot_json), ensure_ascii=False, indent=2) + ui.code(pretty_snapshot, language="json").classes("bp-code") + ui.button( + "下载 canonical snapshot", + icon="download", + on_click=partial(self._download_snapshot, stage.stage, stage.digest, pretty_snapshot), + ).props("outline dense no-caps").classes("bp-secondary-action") + @staticmethod def _download_snapshot(stage: str, digest: str, content: str) -> None: ui.download( @@ -811,43 +1663,159 @@ def _download_snapshot(stage: str, digest: str, content: str) -> None: media_type="application/json", ) - def _render_sweep(self) -> None: - self.sweep_content.clear() - with self.sweep_content: - self._section_heading( - "Design-space exploration", - "策略空间探索", - "批量推导 TP/PP/DP 候选,保留失败原因,并标记延迟—内存 Pareto 前沿。", + def _render_sweep_result(self) -> None: + report = self.sweep_report + assert report is not None + base = self.sweep_base + chips = [f"{len(report.cases)} cases", f"request {report.request_digest[:12]}"] + title = "TP / PP / DP CaseSet" + if base is not None: + title = f"{base.model_name} × {base.hardware_name}" + chips.insert(0, base.calibration_mode.value) + self._render_result_context( + title, + "观察整批 Case 的分布、上下界和可行边界;选择任意一行可继续单点剖析。", + tuple(chips), + ) + rows = sweep_rows(report) + with ui.element("section").classes("bp-evidence-surface"): + with ui.element("div").classes("bp-evidence-section"): + with ui.row().classes("w-full items-end justify-between gap-3"): + self._card_heading("CaseSet 过滤器", "过滤即时作用于分布、上下界和候选表,不会重新运行分析。") + ui.button("清除过滤", icon="filter_alt_off", on_click=self._clear_batch_filters).props( + "flat dense no-caps" + ).classes("bp-filter-clear") + with ui.element("div").classes("bp-batch-filter-grid"): + self.batch_status_filter = ( + ui.select( + _BATCH_STATUS_LABELS, + label="状态", + value="all", + on_change=self._batch_filter_changed, + ) + .props("outlined dense options-dense") + .classes("bp-batch-filter") + .mark("batch-status-filter") + ) + axes = { + "tp": sorted({int(row["tp"]) for row in rows}), + "pp": sorted({int(row["pp"]) for row in rows}), + "dp": sorted({int(row["dp"]) for row in rows}), + } + self.batch_tp_filter = self._batch_axis_filter("TP", axes["tp"], "batch-tp-filter") + self.batch_pp_filter = self._batch_axis_filter("PP", axes["pp"], "batch-pp-filter") + self.batch_dp_filter = self._batch_axis_filter("DP", axes["dp"], "batch-dp-filter") + self.batch_results_host = ui.column().classes("w-full gap-0") + self._render_filtered_sweep_content() + + def _batch_axis_filter(self, label: str, options: list[int], marker: str) -> Any: + return ( + ui.select( + options, + label=label, + value=[], + multiple=True, + on_change=self._batch_filter_changed, ) - report = self.sweep_report - if report is None: - self._empty_state( - "尚未探索配置空间", - "在左侧展开策略搜索空间,选择候选集合后开始探索;最多接受 128 个候选。", - "scatter_plot", - ) - return + .props("outlined dense use-chips options-dense") + .classes("bp-batch-filter") + .mark(marker) + ) + + def _batch_filter_changed(self, *_: Any) -> None: + self._render_filtered_sweep_content() + + def _clear_batch_filters(self) -> None: + if self.batch_status_filter is None: + return + self.batch_status_filter.set_value("all") + for control in (self.batch_tp_filter, self.batch_pp_filter, self.batch_dp_filter): + if control is not None: + control.set_value([]) + self._render_filtered_sweep_content() + + def _filtered_sweep_rows(self) -> list[dict[str, Any]]: + report = self.sweep_report + if report is None: + return [] + rows = sweep_rows(report) + status = str(self.batch_status_filter.value) if self.batch_status_filter is not None else "all" + selected_axes = { + "tp": set(self.batch_tp_filter.value or ()) if self.batch_tp_filter is not None else set(), + "pp": set(self.batch_pp_filter.value or ()) if self.batch_pp_filter is not None else set(), + "dp": set(self.batch_dp_filter.value or ()) if self.batch_dp_filter is not None else set(), + } - rows = sweep_rows(report) - pareto_count = sum(bool(row["pareto"]) for row in rows) + def visible(row: dict[str, Any]) -> bool: + if status == "feasible" and not (row["status"] == "success" and row["feasible"]): + return False + if status == "infeasible" and not (row["status"] == "success" and not row["feasible"]): + return False + if status == "pareto" and not row["pareto"]: + return False + if status == "failed" and row["status"] == "success": + return False + return all(not values or row[axis] in values for axis, values in selected_axes.items()) + + return [row for row in rows if visible(row)] + + def _render_filtered_sweep_content(self) -> None: + report = self.sweep_report + if report is None or self.batch_results_host is None: + return + rows = self._filtered_sweep_rows() + self.batch_results_host.clear() + self.batch_grid = None + with self.batch_results_host: + successful = [row for row in rows if row["status"] == "success"] + latency_values = [float(row["latency_s"]) for row in successful if row["latency_s"] is not None] + memory_values = [float(row["memory_gib"]) for row in successful if row["memory_gib"] is not None] + visible_feasible = sum(bool(row["feasible"]) for row in successful) + visible_pareto = sum(bool(row["pareto"]) for row in successful) summary = ( - ("候选", len(report.cases), "#7c68ff"), - ("成功", report.succeeded_count, "#22d3ee"), - ("可行", report.feasible_count, "#34d399"), - ("Pareto", pareto_count, "#f59e0b"), + ("可见 Case", f"{len(rows)} / {len(report.cases)}", "primary", "当前过滤结果"), + ("容量可行", str(visible_feasible), "cyan", f"非支配 {visible_pareto}"), + ("延迟上下界", self._range_text(latency_values, format_seconds), "violet", "成功 Case"), + ( + "内存上下界", + self._range_text(memory_values, lambda value: f"{value:.2f} GiB"), + "amber", + "单设备", + ), ) - with ui.row().classes("w-full gap-3"): - for label, value, color in summary: - with ui.element("div").classes("bp-metric").style(f"--metric-color: {color}"): + with ( + ui.element("section").classes("bp-evidence-section"), + ui.element("div").classes("bp-metric-grid"), + ): + for label, value, tone, detail in summary: + with ui.element("div").classes("bp-metric").style(f"--metric-color: {METRIC_COLORS[tone]}"): ui.label(label).classes("bp-metric-label") - ui.label(str(value)).classes("bp-metric-value") - ui.label("strategy candidates").classes("bp-metric-detail") + ui.label(value).classes("bp-metric-value bp-metric-value--range") + ui.label(detail).classes("bp-metric-detail") - successful = [row for row in rows if row["status"] == "success" and row["latency_s"] is not None] if successful: - with ui.card().classes("bp-card p-4"): - self._card_heading("延迟—内存空间", "亮色点为非支配解;失败候选不会被静默丢弃。") - ui.echart(sweep_chart_options(report), renderer="svg").classes("w-full h-96") + with ( + ui.element("section").classes("bp-evidence-section"), + ui.element("div").classes("bp-batch-chart-grid"), + ): + with ui.element("section").classes("bp-evidence-block"): + self._card_heading( + "延迟—内存空间", + "越靠左下越优;非支配标记仅针对原始 CaseSet 的延迟与单设备内存。", + ) + ui.echart(sweep_chart_options(report, rows), renderer="svg").classes("w-full bp-batch-chart") + with ui.element("section").classes("bp-evidence-block"): + self._card_heading("可见分布", "直方图只统计当前过滤后成功完成的 Case。") + ui.echart(sweep_distribution_chart_options(report, rows), renderer="svg").classes( + "w-full bp-batch-chart" + ) + else: + with ui.element("section").classes("bp-evidence-section"): + self._empty_state( + "当前过滤没有可绘制的 Case", + "调整状态或并行度过滤器;失败 Case 仍保留在候选表中。", + "filter_alt_off", + ) sorted_rows = sorted( rows, @@ -858,56 +1826,95 @@ def _render_sweep(self) -> None: row["latency_s"] if row["latency_s"] is not None else float("inf"), ), ) - self._card_heading("全部候选", f"Request {report.request_digest[:16]}") - ui.aggrid( - { - "columnDefs": [ - {"headerName": "TP", "field": "tp", "pinned": "left", "width": 80}, - {"headerName": "PP", "field": "pp", "width": 80}, - {"headerName": "DP", "field": "dp", "width": 80}, - {"headerName": "World", "field": "world_size", "type": "numericColumn"}, - {"headerName": "Status", "field": "status"}, - {"headerName": "Feasible", "field": "feasible"}, - {"headerName": "Pareto", "field": "pareto"}, - {"headerName": "Latency s", "field": "latency_s", "type": "numericColumn"}, - {"headerName": "Memory GiB", "field": "memory_gib", "type": "numericColumn"}, - {"headerName": "Token/s/device", "field": "tokens_s_device", "type": "numericColumn"}, - {"headerName": "Bottleneck", "field": "bottleneck", "minWidth": 150}, + with ui.element("section").classes("bp-evidence-section"): + with ui.row().classes("w-full items-end justify-between gap-3"): + self._card_heading("可见 Case", "选择一行后,可直接切换到单点模式重新剖析该 Case。") + ui.button("单点剖析所选", icon="query_stats", on_click=self._open_selected_batch_case).props( + "outline dense no-caps" + ).classes("bp-secondary-action").mark("batch-open-point") + self.batch_grid = ( + ui.aggrid( { - "headerName": "Diagnostic", - "field": "diagnostic", - "minWidth": 280, - "tooltipField": "diagnostic", + "columnDefs": [ + {"headerName": "TP", "field": "tp", "pinned": "left", "width": 76}, + {"headerName": "PP", "field": "pp", "width": 76}, + {"headerName": "DP", "field": "dp", "width": 76}, + {"headerName": "World", "field": "world_size", "type": "numericColumn"}, + {"headerName": "Status", "field": "status"}, + {"headerName": "Feasible", "field": "feasible"}, + {"headerName": "Non-dominated", "field": "pareto", "minWidth": 145}, + {"headerName": "Latency s", "field": "latency_s", "type": "numericColumn"}, + {"headerName": "Memory GiB", "field": "memory_gib", "type": "numericColumn"}, + { + "headerName": "Token/s/device", + "field": "tokens_s_device", + "type": "numericColumn", + }, + {"headerName": "Bottleneck", "field": "bottleneck", "minWidth": 150}, + { + "headerName": "Diagnostic", + "field": "diagnostic", + "minWidth": 280, + "tooltipField": "diagnostic", + }, + ], + "rowData": sorted_rows, + "rowSelection": "single", + "defaultColDef": {"sortable": True, "filter": True, "resizable": True}, + "pagination": True, + "paginationPageSize": 25, }, - ], - "rowData": sorted_rows, - "defaultColDef": {"sortable": True, "filter": True, "resizable": True}, - "pagination": True, - "paginationPageSize": 25, - }, - theme="quartz", - auto_size_columns=False, - ).classes("w-full bp-grid").style("height: 520px") + theme="quartz", + auto_size_columns=False, + ) + .classes("w-full bp-grid") + .style("height: 500px") + ) invalid = [row for row in rows if row["status"] != "success"] if invalid: - with ui.expansion( - f"无效候选诊断({len(invalid)})", - icon="warning", - value=False, - ).classes("bp-card w-full"): + with ( + ui.element("section").classes("bp-evidence-section"), + ui.expansion(f"失败 Case 诊断({len(invalid)})", icon="warning", value=False).classes( + "bp-card w-full" + ), + ui.column().classes("w-full gap-2 pt-2"), + ): for row in invalid: with ui.element("div").classes("bp-diagnostic"): - ui.label(f"TP{row['tp']} / PP{row['pp']} / DP{row['dp']}").classes( - "text-xs font-semibold bp-mono" - ) - ui.label(row["diagnostic"] or "未提供诊断").classes("text-xs bp-muted") + ui.label(f"TP{row['tp']} / PP{row['pp']} / DP{row['dp']}").classes("bp-card-title bp-mono") + ui.label(row["diagnostic"] or "未提供诊断").classes("bp-card-copy") + + @staticmethod + def _range_text(values: list[float], formatter: Callable[[float], str]) -> str: + if not values: + return "—" + lower = min(values) + upper = max(values) + if lower == upper: + return formatter(lower) + return f"{formatter(lower)} – {formatter(upper)}" + + async def _open_selected_batch_case(self) -> None: + if self.batch_grid is None or self.form is None: + return + row = await self.batch_grid.get_selected_row() + if row is None: + ui.notify("请先在候选表中选择一个 Case。", type="warning", position="bottom-right") + return + self.form.load_point_parallelism(int(row["tp"]), int(row["pp"]), int(row["dp"])) + self.mode = WorkbenchMode.ANALYSIS + self.mode_switch.set_value(self.mode.value) + self.form.set_mode(self.mode) + self.local_error = None + self._render_sidebar_controls() + await self.run_analysis() def _render_diagnostics(self, diagnostics: tuple[AnalysisDiagnostic, ...]) -> None: colors = { - DiagnosticLevel.ERROR: "#fb7185", - DiagnosticLevel.WARNING: "#f59e0b", - DiagnosticLevel.INFO: "#22d3ee", + DiagnosticLevel.ERROR: "#b91c1c", + DiagnosticLevel.WARNING: "#b45309", + DiagnosticLevel.INFO: "#2563eb", } icons = { DiagnosticLevel.ERROR: "error", @@ -923,33 +1930,36 @@ def _render_diagnostics(self, diagnostics: tuple[AnalysisDiagnostic, ...]) -> No with ui.column().classes("gap-1"): location = ".".join(diagnostic.path) title = diagnostic.code + (f" · {location}" if location else "") - ui.label(title).classes("text-xs font-semibold bp-mono") - ui.label(diagnostic.message).classes("text-xs bp-muted") + ui.label(title).classes("bp-card-title bp-mono") + ui.label(diagnostic.message).classes("bp-card-copy") if diagnostic.hint: - ui.label(f"建议:{diagnostic.hint}").classes("text-xs text-secondary") + ui.label(f"建议:{diagnostic.hint}").classes("bp-card-copy text-secondary") @staticmethod - def _section_heading(kicker: str, title: str, copy: str) -> None: - with ui.column().classes("gap-1"): + def _workspace_heading(kicker: str, title: str, copy: str) -> None: + with ui.column().classes("bp-workspace-heading gap-0"): ui.label(kicker).classes("bp-kicker") - ui.label(title).classes("bp-section-title") - ui.label(copy).classes("bp-section-copy") + ui.label(title).classes("bp-page-title") + ui.label(copy).classes("bp-page-copy") @staticmethod def _card_heading(title: str, copy: str) -> None: - with ui.column().classes("gap-1 mb-2"): - ui.label(title).classes("font-semibold") - ui.label(copy).classes("text-xs bp-muted") + with ui.column().classes("gap-1 mb-3"): + ui.label(title).classes("bp-card-title") + ui.label(copy).classes("bp-card-copy") + + @staticmethod + def _fact_row(label: str, value: str) -> None: + with ui.row().classes("bp-fact-row items-center justify-between gap-4"): + ui.label(label).classes("bp-summary-label") + ui.label(value).classes("bp-summary-value bp-mono") @staticmethod def _empty_state(title: str, copy: str, icon: str) -> None: - with ( - ui.element("div").classes("bp-empty"), - ui.column().classes("items-center gap-2 p-8"), - ): - ui.icon(icon, size="36px", color="primary") - ui.label(title).classes("text-lg font-semibold") - ui.label(copy).classes("max-w-xl text-xs bp-muted leading-relaxed") + with ui.element("div").classes("bp-empty"), ui.column().classes("items-center gap-2"): + ui.icon(icon, size="34px", color="secondary") + ui.label(title).classes("bp-result-title") + ui.label(copy).classes("bp-card-copy") def create_workbench_root( diff --git a/src/blueprinting/workbench/presentation.py b/src/blueprinting/workbench/presentation.py index e0d4ac8..5d3a11d 100644 --- a/src/blueprinting/workbench/presentation.py +++ b/src/blueprinting/workbench/presentation.py @@ -9,6 +9,7 @@ from __future__ import annotations from dataclasses import dataclass +from math import sqrt from typing import Any from blueprinting.application import AnalysisOutcome, AnalysisReport, SweepReport @@ -24,6 +25,47 @@ class MetricView: tone: str = "neutral" +@dataclass(frozen=True) +class TimelineSummary: + """Scope and size of a dependency-only task-time projection.""" + + task_count: int + dependency_count: int + lane_count: int + span_seconds: float + + +_TIME_CATEGORY_LABELS = { + "forward": "前向计算", + "backward": "反向计算", + "optimizer": "优化器更新", + "recompute": "激活重计算", + "tensor_parallel": "张量并行通信", + "pipeline_parallel": "流水并行通信", + "data_parallel": "数据并行通信", + "recommunication": "重通信", + "pipeline_bubble": "流水空泡", +} +_PHASE_LABELS = { + "forward": "前向", + "recompute": "重计算", + "activation_gradient": "激活梯度", + "weight_gradient": "权重梯度", + "optimizer": "优化器", + "recommunication": "重通信", +} +_SYNTHETIC_TIME_TERMS = { + "pipeline_parallel": "Pipeline point-to-point 解析项", + "data_parallel": "Data-parallel collective 解析项", + "pipeline_bubble": "1F1B / interleaving 空泡解析项", +} +_ENGINE_COLORS = { + "matrix": "#2563eb", + "vector": "#7c3aed", + "collective": "#0891b2", +} + + def format_seconds(value: float) -> str: if value >= 1: return f"{value:.3f} s" @@ -55,16 +97,15 @@ def format_count(value: int | float) -> str: def analysis_metrics(report: AnalysisReport) -> tuple[MetricView, ...]: utilization = report.memory["total"] / report.memory["capacity"] return ( - MetricView("迭代延迟", format_seconds(report.total_seconds), "端到端解析式估算", "primary"), - MetricView("Token/s", format_count(report.total_tokens_per_second), "全局训练吞吐", "cyan"), + MetricView("迭代延迟", format_seconds(report.total_seconds), "解析式训练迭代估算", "primary"), + MetricView("全局吞吐", format_count(report.total_tokens_per_second), "Token/s", "cyan"), MetricView( - "Token/s/设备", + "单设备吞吐", format_count(report.tokens_per_second_per_device), - f"{report.world_size:,} 个设备", + f"Token/s · {report.world_size:,} 个设备", "violet", ), MetricView("单设备内存", format_bytes(report.memory["total"]), f"容量使用率 {utilization:.1%}", "amber"), - MetricView("主导项", report.bottleneck.replace("_", " "), "当前延迟分解最大项", "neutral"), ) @@ -134,47 +175,44 @@ def sweep_rows(report: SweepReport) -> list[dict[str, Any]]: def latency_chart_options(report: AnalysisReport) -> dict[str, Any]: - labels = [name.replace("_", " ") for name in report.latency] - values = [float(value) for value in report.latency.values()] + entries = [(name.replace("_", " "), float(value)) for name, value in report.latency.items()] + largest = max((value for _, value in entries), default=0.0) + data = [ + { + "value": value, + "itemStyle": { + "color": "#2563eb" if value == largest else "#94a3b8", + "borderRadius": [0, 4, 4, 0], + }, + } + for _, value in entries + ] return { "backgroundColor": "transparent", - "animationDuration": 500, - "grid": {"left": 118, "right": 24, "top": 18, "bottom": 32}, + "animationDuration": 350, + "grid": {"left": 120, "right": 34, "top": 18, "bottom": 38}, "tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}}, "xAxis": { "type": "value", "name": "seconds", - "nameTextStyle": {"color": "#7f8ca8"}, - "axisLabel": {"color": "#7f8ca8"}, - "splitLine": {"lineStyle": {"color": "rgba(130, 153, 197, .10)"}}, + "nameTextStyle": {"color": "#64748b"}, + "axisLabel": {"color": "#64748b"}, + "axisLine": {"lineStyle": {"color": "#cbd5e1"}}, + "splitLine": {"lineStyle": {"color": "#e2e8f0"}}, }, "yAxis": { "type": "category", - "data": labels, + "data": [label for label, _ in entries], "inverse": True, - "axisLabel": {"color": "#aebbd4"}, + "axisLabel": {"color": "#475569"}, "axisLine": {"show": False}, "axisTick": {"show": False}, }, "series": [ { "type": "bar", - "data": values, - "barMaxWidth": 16, - "itemStyle": { - "borderRadius": [0, 8, 8, 0], - "color": { - "type": "linear", - "x": 0, - "y": 0, - "x2": 1, - "y2": 0, - "colorStops": [ - {"offset": 0, "color": "#7357ff"}, - {"offset": 1, "color": "#22d3ee"}, - ], - }, - }, + "data": data, + "barMaxWidth": 14, } ], } @@ -182,86 +220,542 @@ def latency_chart_options(report: AnalysisReport) -> dict[str, Any]: def memory_chart_options(report: AnalysisReport) -> dict[str, Any]: entries = tuple((name, value) for name, value in report.memory.items() if name not in {"total", "capacity"}) + colors = ("#2563eb", "#0891b2", "#64748b", "#7c3aed", "#d97706", "#be123c") + capacity_gib = float(report.memory["capacity"]) / 1024**3 + series = [] + for index, (name, value) in enumerate(entries): + item: dict[str, Any] = { + "name": name.replace("_", " "), + "type": "bar", + "stack": "memory", + "barMaxWidth": 34, + "data": [float(value) / 1024**3], + "itemStyle": {"color": colors[index % len(colors)]}, + "emphasis": {"focus": "series"}, + } + if index == 0: + item["markLine"] = { + "silent": True, + "symbol": "none", + "label": { + "show": True, + "formatter": f"容量 {capacity_gib:.1f} GiB", + "color": "#b45309", + "position": "insideEndTop", + }, + "lineStyle": {"color": "#b45309", "type": "dashed", "width": 1}, + "data": [{"xAxis": capacity_gib}], + } + series.append(item) return { "backgroundColor": "transparent", - "animationDuration": 500, - "grid": {"left": 118, "right": 24, "top": 18, "bottom": 32}, + "animationDuration": 350, + "legend": { + "type": "scroll", + "top": 0, + "textStyle": {"color": "#475569", "fontSize": 11}, + "pageTextStyle": {"color": "#64748b"}, + }, + "grid": {"left": 24, "right": 28, "top": 72, "bottom": 42}, "tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}}, "xAxis": { "type": "value", "name": "GiB", - "nameTextStyle": {"color": "#7f8ca8"}, - "axisLabel": {"color": "#7f8ca8"}, - "splitLine": {"lineStyle": {"color": "rgba(130, 153, 197, .10)"}}, + "nameTextStyle": {"color": "#64748b"}, + "axisLabel": {"color": "#64748b"}, + "axisLine": {"lineStyle": {"color": "#cbd5e1"}}, + "splitLine": {"lineStyle": {"color": "#e2e8f0"}}, }, "yAxis": { "type": "category", - "data": [name.replace("_", " ") for name, _ in entries], + "data": ["每设备内存"], + "axisLabel": {"show": False}, + "axisLine": {"show": False}, + "axisTick": {"show": False}, + }, + "series": series, + } + + +def _time_category_for_task(phase: str, engine: str) -> str | None: + if engine == "collective": + if phase == "recommunication": + return "recommunication" + if phase in {"forward", "activation_gradient"}: + return "tensor_parallel" + return None + if phase in {"activation_gradient", "weight_gradient"}: + return "backward" + if phase in {"forward", "recompute", "optimizer"}: + return phase + return None + + +def _scaled_time_children(report: AnalysisReport, category: str, seconds: float) -> list[dict[str, Any]]: + matching = [ + task + for task in report.tasks + if _time_category_for_task(task.phase, task.engine) == category and task.total_seconds > 0 + ] + raw_total = sum(float(task.total_seconds) for task in matching) + if not matching or raw_total <= 0: + return [ + { + "name": _SYNTHETIC_TIME_TERMS.get(category, "未进一步分解的解析项"), + "value": seconds, + } + ] + + groups: dict[str, dict[str, dict[str, float]]] = {} + for task in matching: + source_layer = task.source_layer or "other" + domain = source_layer.split(".", 1)[0] + phase = _PHASE_LABELS.get(task.phase, task.phase.replace("_", " ")) + group_name = f"{phase} · {domain}" if category in {"backward", "tensor_parallel"} else domain + layer = groups.setdefault(group_name, {}).setdefault(source_layer, {}) + operation = task.operation.replace("transformer.", "").replace("collective.", "") + layer[operation] = layer.get(operation, 0.0) + float(task.total_seconds) + + scale = seconds / raw_total + children: list[dict[str, Any]] = [] + for group_name, layers in groups.items(): + layer_nodes = [] + for layer_name, operations in layers.items(): + operation_nodes = [ + {"name": operation.replace("_", " "), "value": raw_seconds * scale} + for operation, raw_seconds in operations.items() + ] + layer_nodes.append( + { + "name": layer_name, + "value": sum(float(node["value"]) for node in operation_nodes), + "children": operation_nodes, + } + ) + children.append( + { + "name": group_name, + "value": sum(float(node["value"]) for node in layer_nodes), + "children": layer_nodes, + } + ) + return children + + +def time_breakdown_tree(report: AnalysisReport) -> list[dict[str, Any]]: + """Build an iteration-to-operation hierarchy while preserving headline totals.""" + + nodes = [] + for category, value in report.latency.items(): + seconds = float(value) + if seconds <= 0: + continue + nodes.append( + { + "name": _TIME_CATEGORY_LABELS.get(category, category.replace("_", " ")), + "value": seconds, + "category": category, + "children": _scaled_time_children(report, category, seconds), + } + ) + return nodes + + +def time_breakdown_rows(report: AnalysisReport) -> list[dict[str, Any]]: + """Flatten the hierarchy for precise, sortable inspection.""" + + rows: list[dict[str, Any]] = [] + total = float(report.total_seconds) + + def append_nodes(nodes: list[dict[str, Any]], path: tuple[str, ...], parent_seconds: float) -> None: + for node in nodes: + seconds = float(node["value"]) + node_path = (*path, str(node["name"])) + rows.append( + { + "level": len(node_path), + "component": str(node["name"]), + "path": " / ".join(node_path), + "seconds": seconds, + "share_iteration": seconds / total if total else 0.0, + "share_parent": seconds / parent_seconds if parent_seconds else 0.0, + "category": str(node.get("category", "detail")), + } + ) + children = node.get("children", []) + if children: + append_nodes(children, node_path, seconds) + + append_nodes(time_breakdown_tree(report), (), total) + return rows + + +def time_breakdown_chart_options(report: AnalysisReport) -> dict[str, Any]: + total = float(report.total_seconds) + return { + "backgroundColor": "transparent", + "animationDuration": 350, + "tooltip": { + ":formatter": ( + "function(params) {" + "const value = Number(params.value || 0);" + f"const total = {total!r};" + "const share = total > 0 ? value / total * 100 : 0;" + "return '' + params.name + '
' + " + "value.toFixed(6) + ' s · ' + share.toFixed(2) + '% of iteration';" + "}" + ) + }, + "series": [ + { + "type": "treemap", + "name": "Iteration time", + "data": time_breakdown_tree(report), + "roam": False, + "nodeClick": "zoomToNode", + "leafDepth": 2, + "visibleMin": 24, + "squareRatio": 1.15, + "breadcrumb": { + "show": True, + "bottom": 0, + "height": 22, + "itemStyle": {"color": "#f8fafc", "borderColor": "#cbd5e1"}, + "emphasis": {"itemStyle": {"color": "#eff6ff"}}, + }, + "label": {"show": True, "color": "#ffffff", "fontSize": 11, "overflow": "truncate"}, + "upperLabel": {"show": True, "height": 24, "color": "#ffffff", "fontSize": 11}, + "levels": [ + {"itemStyle": {"borderColor": "#ffffff", "borderWidth": 0, "gapWidth": 3}}, + { + "color": ["#2563eb", "#0891b2", "#7c3aed", "#d97706", "#64748b", "#be123c"], + "colorMappingBy": "index", + "itemStyle": {"borderColor": "#ffffff", "borderWidth": 3, "gapWidth": 3}, + }, + { + "colorSaturation": [0.28, 0.58], + "itemStyle": {"borderColorSaturation": 0.68, "gapWidth": 2, "borderWidth": 1}, + }, + { + "colorSaturation": [0.18, 0.42], + "itemStyle": {"borderColorSaturation": 0.55, "gapWidth": 1}, + }, + ], + } + ], + } + + +def task_dependency_projection(report: AnalysisReport) -> list[dict[str, Any]]: + """Project task start/end from dependencies only, without resource scheduling.""" + + tasks = {task.task_id: task for task in report.tasks} + starts: dict[str, float] = {} + ends: dict[str, float] = {} + visiting: set[str] = set() + + def project(task_id: str) -> float: + if task_id in ends: + return ends[task_id] + task = tasks[task_id] + if task_id in visiting: + raise ValueError("portable task dependencies contain a cycle") + visiting.add(task_id) + start = max((project(item) for item in task.dependencies if item in tasks), default=0.0) + starts[task_id] = start + ends[task_id] = start + max(float(task.total_seconds), 0.0) + visiting.remove(task_id) + return ends[task_id] + + for task in report.tasks: + project(task.task_id) + + return [ + { + "task_id": task.task_id, + "operation": task.operation, + "phase": task.phase, + "engine": task.engine, + "source_layer": task.source_layer, + "concurrency_group": task.concurrency_group, + "start_seconds": starts[task.task_id], + "end_seconds": ends[task.task_id], + "duration_seconds": max(float(task.total_seconds), 0.0), + "dependencies": len(task.dependencies), + } + for task in report.tasks + ] + + +def timeline_summary(report: AnalysisReport) -> TimelineSummary: + rows = task_dependency_projection(report) + return TimelineSummary( + task_count=len(rows), + dependency_count=sum(len(task.dependencies) for task in report.tasks), + lane_count=len({task.phase for task in report.tasks}), + span_seconds=max((float(row["end_seconds"]) for row in rows), default=0.0), + ) + + +def dependency_timeline_chart_options(report: AnalysisReport) -> dict[str, Any]: + """Render every portable task on a zoomable dependency-projected timeline.""" + + projection = task_dependency_projection(report) + phases = list(dict.fromkeys(str(row["phase"]) for row in projection)) + phase_indices = {phase: index for index, phase in enumerate(phases)} + data = [] + for row in projection: + start_ms = float(row["start_seconds"]) * 1e3 + end_ms = float(row["end_seconds"]) * 1e3 + duration_ms = float(row["duration_seconds"]) * 1e3 + data.append( + { + "name": str(row["operation"]).split(".")[-1].replace("_", " "), + "value": [phase_indices[str(row["phase"])], start_ms, end_ms, duration_ms], + "operation": row["operation"], + "phase": _PHASE_LABELS.get(str(row["phase"]), str(row["phase"]).replace("_", " ")), + "engine": row["engine"], + "source_layer": row["source_layer"], + "task_id": row["task_id"], + "dependencies": row["dependencies"], + "itemStyle": { + "color": _ENGINE_COLORS.get(str(row["engine"]), "#64748b"), + "opacity": 0.9, + }, + } + ) + return { + "backgroundColor": "transparent", + "animationDuration": 250, + "grid": {"left": 112, "right": 26, "top": 20, "bottom": 72}, + "tooltip": { + ":formatter": ( + "function(params) {" + "const d = params.data; const v = d.value;" + "return '' + d.operation + '
' + d.source_layer + " + "'
Phase · ' + d.phase + ' · Engine · ' + d.engine + " + "'
Start · ' + Number(v[1]).toFixed(4) + ' ms' + " + "'
Duration · ' + Number(v[3]).toFixed(4) + ' ms' + " + "'
Dependencies · ' + d.dependencies;" + "}" + ) + }, + "dataZoom": [ + {"type": "inside", "xAxisIndex": 0, "filterMode": "weakFilter"}, + { + "type": "slider", + "xAxisIndex": 0, + "height": 18, + "bottom": 18, + "filterMode": "weakFilter", + "borderColor": "#e2e8f0", + "fillerColor": "rgba(37, 99, 235, .12)", + "handleStyle": {"color": "#2563eb"}, + }, + ], + "xAxis": { + "type": "value", + "name": "dependency-projected ms", + "nameLocation": "middle", + "nameGap": 48, + "nameTextStyle": {"color": "#64748b"}, + "axisLabel": {"color": "#64748b"}, + "axisLine": {"lineStyle": {"color": "#cbd5e1"}}, + "splitLine": {"lineStyle": {"color": "#e2e8f0"}}, + "min": 0, + }, + "yAxis": { + "type": "category", + "data": [_PHASE_LABELS.get(phase, phase.replace("_", " ")) for phase in phases], "inverse": True, - "axisLabel": {"color": "#aebbd4"}, + "axisLabel": {"color": "#475569", "fontSize": 11}, "axisLine": {"show": False}, "axisTick": {"show": False}, }, "series": [ { - "type": "bar", - "data": [float(value) / 1024**3 for _, value in entries], - "barMaxWidth": 16, - "itemStyle": {"borderRadius": [0, 8, 8, 0], "color": "#f59e0b"}, + "type": "custom", + "name": "Portable tasks", + "clip": True, + ":renderItem": ( + "function(params, api) {" + "const lane = api.value(0);" + "const start = api.coord([api.value(1), lane]);" + "const end = api.coord([api.value(2), lane]);" + "const height = api.size([0, 1])[1] * 0.56;" + "const bounds = params.coordSys;" + "const left = Math.max(start[0], bounds.x);" + "const right = Math.min(" + "Math.max(end[0], start[0] + 1), bounds.x + bounds.width" + ");" + "const top = Math.max(start[1] - height / 2, bounds.y);" + "const bottom = Math.min(start[1] + height / 2, bounds.y + bounds.height);" + "if (right <= left || bottom <= top) { return null; }" + "return {type: 'rect', shape: {" + "x: left, y: top, width: right - left, height: bottom - top" + "}, style: api.style()};" + "}" + ), + "encode": {"x": [1, 2], "y": 0}, + "data": data, } ], } -def sweep_chart_options(report: SweepReport) -> dict[str, Any]: - successful = [row for row in sweep_rows(report) if row["status"] == "success" and row["latency_s"] is not None] +def sweep_chart_options( + report: SweepReport, + rows: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + successful = [ + row + for row in (rows if rows is not None else sweep_rows(report)) + if row["status"] == "success" and row["latency_s"] is not None + ] - def points(pareto: bool) -> list[dict[str, Any]]: + def points(*, feasible: bool, pareto: bool = False) -> list[dict[str, Any]]: return [ { "value": [row["memory_gib"], row["latency_s"]], "name": f"TP{row['tp']} / PP{row['pp']} / DP{row['dp']}", } for row in successful - if bool(row["pareto"]) is pareto + if bool(row["feasible"]) is feasible and (not feasible or bool(row["pareto"]) is pareto) ] return { "backgroundColor": "transparent", - "animationDuration": 600, - "legend": {"top": 0, "textStyle": {"color": "#aebbd4"}}, - "grid": {"left": 64, "right": 26, "top": 48, "bottom": 48}, + "animationDuration": 400, + "legend": {"top": 0, "textStyle": {"color": "#475569"}}, + "grid": {"left": 66, "right": 28, "top": 52, "bottom": 52}, "tooltip": {"trigger": "item"}, "xAxis": { "type": "value", "name": "Memory / GiB", "nameLocation": "middle", "nameGap": 30, - "nameTextStyle": {"color": "#7f8ca8"}, - "axisLabel": {"color": "#7f8ca8"}, - "splitLine": {"lineStyle": {"color": "rgba(130, 153, 197, .10)"}}, + "nameTextStyle": {"color": "#64748b"}, + "axisLabel": {"color": "#64748b"}, + "axisLine": {"lineStyle": {"color": "#cbd5e1"}}, + "splitLine": {"lineStyle": {"color": "#e2e8f0"}}, }, "yAxis": { "type": "value", "name": "Latency / s", - "nameTextStyle": {"color": "#7f8ca8"}, - "axisLabel": {"color": "#7f8ca8"}, - "splitLine": {"lineStyle": {"color": "rgba(130, 153, 197, .10)"}}, + "nameTextStyle": {"color": "#64748b"}, + "axisLabel": {"color": "#64748b"}, + "axisLine": {"lineStyle": {"color": "#cbd5e1"}}, + "splitLine": {"lineStyle": {"color": "#e2e8f0"}}, }, "series": [ { "name": "可行候选", "type": "scatter", - "symbolSize": 11, - "data": points(False), - "itemStyle": {"color": "#53627d", "opacity": 0.72}, + "symbolSize": 10, + "data": points(feasible=True, pareto=False), + "itemStyle": {"color": "#64748b", "opacity": 0.72}, + }, + { + "name": "非支配候选", + "type": "scatter", + "symbolSize": 16, + "data": points(feasible=True, pareto=True), + "itemStyle": {"color": "#2563eb", "borderColor": "#dbeafe", "borderWidth": 1}, }, { - "name": "Pareto 前沿", + "name": "容量不可行", "type": "scatter", - "symbolSize": 18, - "data": points(True), - "itemStyle": {"color": "#22d3ee", "shadowBlur": 16, "shadowColor": "rgba(34,211,238,.5)"}, + "symbol": "emptyCircle", + "symbolSize": 12, + "data": points(feasible=False), + "itemStyle": {"color": "#b91c1c", "opacity": 0.82}, + }, + ], + } + + +def _histogram(values: list[float]) -> tuple[list[str], list[int]]: + if not values: + return [], [] + lower = min(values) + upper = max(values) + if lower == upper: + return [f"{lower:.3g}"], [len(values)] + + bin_count = max(2, min(10, round(sqrt(len(values))))) + width = (upper - lower) / bin_count + counts = [0] * bin_count + for value in values: + index = min(int((value - lower) / width), bin_count - 1) + counts[index] += 1 + labels = [f"{lower + index * width:.3g}–{lower + (index + 1) * width:.3g}" for index in range(bin_count)] + return labels, counts + + +def sweep_distribution_chart_options( + report: SweepReport, + rows: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + """Render latency and memory histograms for the visible successful cases.""" + + successful = [row for row in (rows if rows is not None else sweep_rows(report)) if row["status"] == "success"] + latency_labels, latency_counts = _histogram( + [float(row["latency_s"]) for row in successful if row["latency_s"] is not None] + ) + memory_labels, memory_counts = _histogram( + [float(row["memory_gib"]) for row in successful if row["memory_gib"] is not None] + ) + common_axis = { + "type": "category", + "axisLabel": {"color": "#64748b", "fontSize": 10, "hideOverlap": True}, + "axisLine": {"lineStyle": {"color": "#cbd5e1"}}, + "axisTick": {"show": False}, + } + common_value_axis = { + "type": "value", + "minInterval": 1, + "axisLabel": {"color": "#64748b", "fontSize": 10}, + "axisLine": {"show": False}, + "splitLine": {"lineStyle": {"color": "#e2e8f0"}}, + } + return { + "backgroundColor": "transparent", + "animationDuration": 350, + "title": [ + {"text": "延迟 / seconds", "left": 2, "top": 0, "textStyle": {"color": "#475569", "fontSize": 11}}, + {"text": "单设备内存 / GiB", "left": 2, "top": "51%", "textStyle": {"color": "#475569", "fontSize": 11}}, + ], + "grid": [ + {"left": 42, "right": 14, "top": 34, "height": "29%"}, + {"left": 42, "right": 14, "top": "62%", "height": "25%"}, + ], + "tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}}, + "xAxis": [ + {**common_axis, "gridIndex": 0, "data": latency_labels}, + {**common_axis, "gridIndex": 1, "data": memory_labels}, + ], + "yAxis": [ + {**common_value_axis, "gridIndex": 0}, + {**common_value_axis, "gridIndex": 1}, + ], + "series": [ + { + "name": "Case 数", + "type": "bar", + "xAxisIndex": 0, + "yAxisIndex": 0, + "data": latency_counts, + "barMaxWidth": 26, + "itemStyle": {"color": "#2563eb", "borderRadius": [3, 3, 0, 0]}, + }, + { + "name": "Case 数", + "type": "bar", + "xAxisIndex": 1, + "yAxisIndex": 1, + "data": memory_counts, + "barMaxWidth": 26, + "itemStyle": {"color": "#0891b2", "borderRadius": [3, 3, 0, 0]}, }, ], } diff --git a/tests/workbench/test_nicegui_workbench.py b/tests/workbench/test_nicegui_workbench.py index 7699900..831e93a 100644 --- a/tests/workbench/test_nicegui_workbench.py +++ b/tests/workbench/test_nicegui_workbench.py @@ -9,7 +9,7 @@ from nicegui import core from nicegui.testing import User, user_simulation -from blueprinting.workbench.nicegui_app import build_parser +from blueprinting.workbench.nicegui_app import build_parser, run_workbench from blueprinting.workbench.nicegui_ui import create_workbench_root @@ -41,13 +41,33 @@ async def test_nicegui_workbench_loads_without_eager_analysis( async with simulated_user(create_workbench_root()) as user: await user.open("/") - await user.should_see("从工作负载语义走向可审计的硬件蓝图") - await user.should_see("等待第一张硬件蓝图") + await user.should_see("单点剖析") + await user.should_see("观察尺度") + await user.should_see("当前 Case") + await user.should_see("这个视图回答什么") + await user.should_see("等待运行") + await user.should_see("当前分析对象") + await user.should_see("当前显示解析任务贡献,不是事件级 Timeline") await user.should_see(marker="run-analysis") - await user.should_see(marker="run-sweep") + await user.should_see(marker="sidebar-run-analysis") await user.should_see("Calculon / Streamlit Legacy") +async def test_sidebar_quick_controls_sync_with_full_configuration( + simulated_user: Callable[[Callable[[], None]], AbstractAsyncContextManager[User]], +) -> None: + async with simulated_user(create_workbench_root()) as user: + await user.open("/") + + user.find(marker="quick-tp").clear().type("8").trigger("update:model-value") + user.find(marker="open-full-configuration").click() + + await user.should_see(marker="configuration-modal") + await user.should_see("模型语义参数") + tp_input = next(iter(user.find(marker="tp-input").elements)) + assert tp_input.value == 8.0 + + async def test_nicegui_analysis_reuses_one_result_across_views( simulated_user: Callable[[Callable[[], None]], AbstractAsyncContextManager[User]], ) -> None: @@ -56,23 +76,56 @@ async def test_nicegui_analysis_reuses_one_result_across_views( user.find(marker="run-analysis").click() await user.should_see("迭代延迟", retries=100) - await user.should_see("Portable workload facts", retries=100) + await user.should_see("结果已就绪", retries=100) + await user.should_see("自顶向下时间分解", retries=100) + await user.should_see("ITERATION HIERARCHY", retries=100) + await user.should_see("第一级严格使用 iteration estimate", retries=100) + await user.should_see("Portable task timeline", retries=100) + await user.should_see("DEPENDENCY PROJECTION", retries=100) + await user.should_see("查看完整层次明细", retries=100) await user.should_see("Portable task audit", retries=100) + await user.should_see("Canonical derivation checkpoints", retries=100) await user.should_see("模型语义", retries=100) await user.should_see("分布式任务", retries=100) await user.should_see("可移植计划", retries=100) +async def test_nicegui_marks_results_stale_after_configuration_change( + simulated_user: Callable[[Callable[[], None]], AbstractAsyncContextManager[User]], +) -> None: + async with simulated_user(create_workbench_root()) as user: + await user.open("/") + user.find(marker="run-analysis").click() + await user.should_see("迭代延迟", retries=100) + + user.find(marker="edit-configuration").click() + user.find(marker="tp-input").clear().type("8").trigger("update:model-value") + + await user.should_see("配置已经变化;当前页面仍显示上一次结果") + await user.should_see("结果需要更新") + await user.should_see(marker="rerun-analysis") + + async def test_nicegui_strategy_sweep_keeps_candidate_status( simulated_user: Callable[[Callable[[], None]], AbstractAsyncContextManager[User]], ) -> None: async with simulated_user(create_workbench_root()) as user: await user.open("/") + user.find(marker="mode-sweep").click() + await user.should_see("批量探索") + await user.should_see("批量候选定义") + quick_candidates = next(iter(user.find(marker="quick-tp-candidates").elements)) + assert quick_candidates.props["popup-content-class"] == "bp-sidebar-menu" user.find(marker="run-sweep").click() + await user.should_see("CaseSet 过滤器", retries=100) await user.should_see("延迟—内存空间", retries=100) - await user.should_see("全部候选", retries=100) - await user.should_see("strategy candidates", retries=100) + await user.should_see("可见 Case", retries=100) + await user.should_see("延迟上下界", retries=100) + await user.should_see("内存上下界", retries=100) + await user.should_see("非支配", retries=100) + await user.should_see(marker="batch-status-filter", retries=100) + await user.should_see(marker="batch-open-point", retries=100) def test_workbench_cli_defaults_to_local_only() -> None: @@ -88,3 +141,16 @@ def test_workbench_cli_accepts_server_overrides() -> None: args: Any = build_parser().parse_args(["--host", "0.0.0.0", "--port", "9000", "--no-open", "--reload"]) assert (args.host, args.port, args.no_open, args.reload) == ("0.0.0.0", 9000, True, True) + + +def test_workbench_runtime_starts_in_light_mode(monkeypatch: pytest.MonkeyPatch) -> None: + captured: dict[str, Any] = {} + + def capture_run(_root: Callable[[], None], **options: Any) -> None: + captured.update(options) + + monkeypatch.setattr("blueprinting.workbench.nicegui_app.ui.run", capture_run) + + run_workbench(show=False) + + assert captured["dark"] is False diff --git a/tests/workbench/test_presentation.py b/tests/workbench/test_presentation.py new file mode 100644 index 0000000..2d6a1c1 --- /dev/null +++ b/tests/workbench/test_presentation.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +import shutil +import subprocess + +import pytest + +from blueprinting.analysis import CalibrationMode +from blueprinting.application import AnalysisDraft, BlueprintingService, SweepCase, SweepReport +from blueprinting.workbench import default_catalog +from blueprinting.workbench.presentation import ( + analysis_metrics, + dependency_timeline_chart_options, + memory_chart_options, + sweep_chart_options, + sweep_distribution_chart_options, + task_dependency_projection, + time_breakdown_rows, + time_breakdown_tree, + timeline_summary, +) + + +def _analysis_report(): + catalog = default_catalog() + outcome = BlueprintingService().analyze( + AnalysisDraft.from_mappings( + model_name="small-transformer", + model_data={ + "hidden": 128, + "feedforward": 512, + "seq_size": 32, + "attn_heads": 8, + "attn_size": 16, + "num_blocks": 4, + }, + execution_name="small-execution", + execution_data={ + "num_procs": 8, + "tensor_par": 2, + "pipeline_par": 2, + "data_par": 2, + "tensor_par_net": 0, + "pipeline_par_net": 1, + "data_par_net": 1, + "batch_size": 8, + "microbatch_size": 1, + "datatype": "float16", + "attention_type": "multihead", + "activation_recompute": "none", + "pipeline_interleaving": 1, + "optimizer_sharding": False, + "tensor_par_comm_type": "ar", + "tensor_par_overlap": "none", + "data_par_overlap": False, + "weight_offload": False, + "activations_offload": False, + "optimizer_offload": False, + "training": True, + }, + hardware_name="a100_80g", + hardware_data=catalog.load("systems", "a100_80g.json"), + calibration_mode=CalibrationMode.SYSTEM_EVIDENCE, + ) + ) + assert outcome.report is not None + return outcome.report + + +def test_analysis_summary_has_four_decision_metrics() -> None: + metrics = analysis_metrics(_analysis_report()) + + assert [metric.label for metric in metrics] == ["迭代延迟", "全局吞吐", "单设备吞吐", "单设备内存"] + + +def test_memory_chart_stacks_components_and_marks_capacity() -> None: + options = memory_chart_options(_analysis_report()) + + assert all(series["stack"] == "memory" for series in options["series"]) + assert options["series"][0]["markLine"]["data"][0]["xAxis"] > 0 + + +def test_time_breakdown_preserves_iteration_total_at_every_parent() -> None: + report = _analysis_report() + tree = time_breakdown_tree(report) + + assert sum(node["value"] for node in tree) == pytest.approx(report.total_seconds) + for node in tree: + assert sum(child["value"] for child in node["children"]) == pytest.approx(node["value"]) + + rows = time_breakdown_rows(report) + expected_categories = {name for name, value in report.latency.items() if float(value) > 0} + assert {row["category"] for row in rows if row["level"] == 1} == expected_categories + + +def test_dependency_timeline_contains_every_task_and_respects_edges() -> None: + report = _analysis_report() + projection = task_dependency_projection(report) + by_id = {row["task_id"]: row for row in projection} + + assert len(projection) == len(report.tasks) + for task in report.tasks: + for dependency in task.dependencies: + assert by_id[task.task_id]["start_seconds"] >= by_id[dependency]["end_seconds"] + + summary = timeline_summary(report) + options = dependency_timeline_chart_options(report) + assert summary.task_count == len(report.tasks) + assert len(options["series"][0]["data"]) == len(report.tasks) + assert options["series"][0]["type"] == "custom" + assert len(options["dataZoom"]) == 2 + + +@pytest.mark.skipif(shutil.which("node") is None, reason="Node.js is required for JavaScript rendering test") +def test_dependency_timeline_render_item_executes_without_echarts_global() -> None: + render_item = dependency_timeline_chart_options(_analysis_report())["series"][0][":renderItem"] + script = """ +const renderItem = new Function('return (' + process.argv[1] + ');')(); +const values = [0, 10, 25, 15]; +const result = renderItem( + {coordSys: {x: 0, y: 0, width: 500, height: 300}}, + { + value: index => values[index], + coord: point => [point[0] * 2, 50 + point[1] * 40], + size: () => [0, 40], + style: () => ({fill: '#2563eb'}), + }, +); +if (!result || result.type !== 'rect') throw new Error('renderItem did not return a rectangle'); +if (result.shape.width <= 0 || result.shape.height <= 0) throw new Error('rectangle has no area'); +process.stdout.write('rendered'); +""" + + completed = subprocess.run( + ["node", "-e", script, render_item], + check=True, + capture_output=True, + text=True, + ) + + assert completed.stdout == "rendered" + + +def test_sweep_chart_distinguishes_feasible_nondominated_and_capacity_failure() -> None: + report = SweepReport( + schema="test", + request_digest="request", + cases=( + SweepCase(1, 1, 1, 1, "success", True, True, 1.0, 10, 1.0, "forward", "a", ()), + SweepCase(2, 1, 1, 2, "success", True, False, 2.0, 12, 0.8, "memory", "b", ()), + SweepCase(4, 1, 1, 4, "success", False, False, 0.5, 20, 1.4, "memory", "c", ()), + ), + ) + + series = sweep_chart_options(report)["series"] + + assert [item["name"] for item in series] == ["可行候选", "非支配候选", "容量不可行"] + assert [len(item["data"]) for item in series] == [1, 1, 1] + + +def test_sweep_distribution_tracks_all_visible_successful_cases() -> None: + report = SweepReport( + schema="test", + request_digest="request", + cases=( + SweepCase(1, 1, 1, 1, "success", True, True, 1.0, 10 * 1024**3, 1.0, "forward", "a", ()), + SweepCase(2, 1, 1, 2, "success", True, False, 2.0, 12 * 1024**3, 0.8, "memory", "b", ()), + SweepCase(4, 1, 1, 4, "failed", False, False, None, None, None, None, "c", ()), + ), + ) + + options = sweep_distribution_chart_options(report) + + assert [series["name"] for series in options["series"]] == ["Case 数", "Case 数"] + assert [sum(series["data"]) for series in options["series"]] == [2, 2]