Skip to content

Commit 3c29d7d

Browse files
committed
test(readme): verify published memory evidence
1 parent 6cd0b80 commit 3c29d7d

2 files changed

Lines changed: 287 additions & 5 deletions

File tree

tests/test_analysis_extensions.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import json
44
from pathlib import Path
55

6+
import pytest
7+
68
from fakegpu.calibration import (
79
BUNDLE_SCHEMA_VERSION,
810
build_workload_calibration_bundle,
@@ -24,6 +26,9 @@
2426

2527

2628
ROOT = Path(__file__).resolve().parents[1]
29+
MEMORY_EVIDENCE_PATH = (
30+
ROOT / "tests" / "data" / "memory_validation_evidence.json"
31+
)
2732

2833

2934
def test_analysis_apis_are_exported_from_package() -> None:
@@ -171,6 +176,105 @@ def test_calibration_comparison_and_bundle() -> None:
171176
assert bundle["entries"][0]["id"] == "tiny-sft"
172177

173178

179+
def test_published_memory_evidence_matches_calibration_math() -> None:
180+
evidence = json.loads(MEMORY_EVIDENCE_PATH.read_text(encoding="utf-8"))
181+
assert (
182+
evidence["schema_version"]
183+
== "fakegpu.memory_validation_evidence.v1"
184+
)
185+
revision = evidence["source_revision"]
186+
assert len(revision) == 40
187+
int(revision, 16)
188+
189+
groups = {
190+
group["id"]: group for group in evidence["evidence_groups"]
191+
}
192+
assert set(groups) == {
193+
"controlled_aten",
194+
"qwen3_8b_inference",
195+
"qwen_sft",
196+
"qwen_qlora",
197+
}
198+
for group in groups.values():
199+
assert group["source_path"].startswith("docs/")
200+
assert group["source_anchor"]
201+
202+
controlled = groups["controlled_aten"]
203+
assert controlled["workload_count"] == 13
204+
assert controlled["observation_count"] == 26
205+
assert (
206+
controlled[
207+
"published_maximum_absolute_percentage_error_percent"
208+
]
209+
== 0.08
210+
)
211+
assert controlled["published_maximum_underestimate_percent"] == 0.08
212+
213+
inference = groups["qwen3_8b_inference"]
214+
prediction = {
215+
"schema_version": "readme_evidence_prediction.v1",
216+
"memory_timeline": {
217+
"phases": [
218+
{
219+
"phase": item["id"],
220+
"peak_bytes": item["predicted_bytes"],
221+
}
222+
for item in inference["measurements"]
223+
]
224+
},
225+
}
226+
observation = {
227+
"schema_version": "readme_evidence_observation.v1",
228+
"memory_timeline": {
229+
"phases": [
230+
{
231+
"phase": item["id"],
232+
"peak_bytes": item["observed_bytes"],
233+
}
234+
for item in inference["measurements"]
235+
]
236+
},
237+
}
238+
comparison = compare_memory_reports(
239+
prediction,
240+
observation,
241+
workload=inference["id"],
242+
)
243+
comparisons = {
244+
item["phase"]: item for item in comparison["comparisons"]
245+
}
246+
for measurement in inference["measurements"]:
247+
actual_percent = (
248+
comparisons[measurement["id"]][
249+
"absolute_percentage_error"
250+
]
251+
* 100
252+
)
253+
assert actual_percent == pytest.approx(
254+
measurement[
255+
"published_absolute_percentage_error_percent"
256+
],
257+
abs=0.0000005,
258+
)
259+
assert comparison["summary"]["underprediction_phase_count"] == 2
260+
assert comparison["summary"]["recommended_memory_safety_margin_bytes"] > 0
261+
assert comparison["summary"]["recommended_memory_safety_factor"] > 1
262+
263+
sft_errors = [
264+
item["published_absolute_percentage_error_percent"]
265+
for item in groups["qwen_sft"]["measurements"]
266+
]
267+
assert len(sft_errors) == 10
268+
assert (min(sft_errors), max(sft_errors)) == (0.102, 1.921)
269+
270+
qlora_errors = [
271+
item["published_absolute_percentage_error_percent"]
272+
for item in groups["qwen_qlora"]["measurements"]
273+
]
274+
assert len(qlora_errors) == 10
275+
assert (min(qlora_errors), max(qlora_errors)) == (0.628, 1.732)
276+
277+
174278
def test_training_plan_normalizes_deepspeed_zero3_offload() -> None:
175279
normalized = normalize_training_config(
176280
{

tests/test_cli_commands.py

Lines changed: 183 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@
2020

2121

2222
ROOT = Path(__file__).resolve().parents[1]
23+
README_PATHS = (
24+
"README.md",
25+
"README.zh-CN.md",
26+
"README.zh-TW.md",
27+
)
28+
MEMORY_EVIDENCE_PATH = (
29+
ROOT / "tests" / "data" / "memory_validation_evidence.json"
30+
)
31+
32+
33+
def _anchored_readme_section(readme: str, anchor: str) -> str:
34+
match = re.search(
35+
rf'<a id="{re.escape(anchor)}"></a>\n\n'
36+
rf"### [^\n]+\n\n"
37+
rf"(?P<body>.*?)"
38+
rf'(?=\n(?:<a id="[^"]+"></a>\n\n)?### )',
39+
readme,
40+
flags=re.DOTALL,
41+
)
42+
assert match is not None, anchor
43+
return match.group("body")
2344

2445

2546
def _run_fakegpu(*args: str) -> subprocess.CompletedProcess[str]:
@@ -241,6 +262,63 @@ def test_fakecuda_profile_matrix() -> None:
241262
)
242263

243264

265+
def test_calibrate_compare_cli_writes_error_and_safety_data(
266+
tmp_path: Path,
267+
) -> None:
268+
prediction_path = tmp_path / "prediction.json"
269+
observation_path = tmp_path / "observation.json"
270+
output_path = tmp_path / "comparison.json"
271+
prediction_path.write_text(
272+
json.dumps(
273+
{
274+
"schema_version": "test_prediction.v1",
275+
"memory_timeline": {
276+
"phases": [
277+
{"phase": "peak", "peak_bytes": 900},
278+
]
279+
},
280+
}
281+
),
282+
encoding="utf-8",
283+
)
284+
observation_path.write_text(
285+
json.dumps(
286+
{
287+
"schema_version": "test_observation.v1",
288+
"memory_timeline": {
289+
"phases": [
290+
{"phase": "peak", "peak_bytes": 1_000},
291+
]
292+
},
293+
}
294+
),
295+
encoding="utf-8",
296+
)
297+
298+
result = _run_fakegpu(
299+
"calibrate",
300+
"compare",
301+
str(prediction_path),
302+
str(observation_path),
303+
"--json",
304+
str(output_path),
305+
)
306+
307+
assert result.returncode == 0, result.stderr
308+
comparison = json.loads(output_path.read_text(encoding="utf-8"))
309+
assert (
310+
comparison["schema_version"]
311+
== "fakegpu.calibration_comparison.v1"
312+
)
313+
assert comparison["comparisons"][0]["absolute_percentage_error"] == 0.1
314+
summary = comparison["summary"]
315+
assert summary["underprediction_phase_count"] == 1
316+
assert summary["recommended_memory_safety_margin_bytes"] == 100
317+
assert summary["recommended_memory_safety_factor"] == pytest.approx(
318+
10 / 9
319+
)
320+
321+
244322
def test_top_level_help_names_builtin_commands() -> None:
245323
result = _run_fakegpu("--help")
246324
assert result.returncode == 0
@@ -252,11 +330,7 @@ def test_top_level_help_names_builtin_commands() -> None:
252330
command_result.stderr,
253331
)
254332

255-
for readme_path in (
256-
"README.md",
257-
"README.zh-CN.md",
258-
"README.zh-TW.md",
259-
):
333+
for readme_path in README_PATHS:
260334
readme = (ROOT / readme_path).read_text(encoding="utf-8")
261335
documented_commands = set(
262336
re.findall(
@@ -266,3 +340,107 @@ def test_top_level_help_names_builtin_commands() -> None:
266340
)
267341
)
268342
assert documented_commands == set(BUILTIN_COMMANDS), readme_path
343+
344+
345+
def test_readmes_document_supported_use_cases() -> None:
346+
use_case_commands = {
347+
"estimate-llm",
348+
"preflight",
349+
"demo",
350+
"validate",
351+
"plan-training",
352+
"analyze-repo",
353+
"analyze-kernel",
354+
"capabilities",
355+
"simulate-topology",
356+
"replay-trace",
357+
"bandwidth",
358+
"calibrate",
359+
}
360+
assert use_case_commands <= set(BUILTIN_COMMANDS)
361+
362+
for readme_path in README_PATHS:
363+
readme = (ROOT / readme_path).read_text(encoding="utf-8")
364+
for language_path in README_PATHS:
365+
assert f"({language_path})" in readme, (
366+
readme_path,
367+
language_path,
368+
)
369+
370+
section = _anchored_readme_section(readme, "use-cases")
371+
table_rows = [
372+
line for line in section.splitlines() if line.startswith("|")
373+
]
374+
assert len(table_rows) == 8, readme_path
375+
for command in use_case_commands:
376+
assert re.search(
377+
rf"`{re.escape(command)}(?:`| )",
378+
section,
379+
), (readme_path, command)
380+
381+
382+
def test_readmes_match_memory_validation_evidence() -> None:
383+
evidence = json.loads(MEMORY_EVIDENCE_PATH.read_text(encoding="utf-8"))
384+
revision = evidence["source_revision"]
385+
groups = {
386+
group["id"]: group for group in evidence["evidence_groups"]
387+
}
388+
389+
controlled = groups["controlled_aten"]
390+
controlled_error = float(
391+
controlled[
392+
"published_maximum_absolute_percentage_error_percent"
393+
]
394+
)
395+
inference = groups["qwen3_8b_inference"]["measurements"]
396+
inference_errors = [
397+
abs(item["predicted_bytes"] - item["observed_bytes"])
398+
/ item["observed_bytes"]
399+
* 100
400+
for item in inference
401+
]
402+
sft_errors = [
403+
item["published_absolute_percentage_error_percent"]
404+
for item in groups["qwen_sft"]["measurements"]
405+
]
406+
qlora_errors = [
407+
item["published_absolute_percentage_error_percent"]
408+
for item in groups["qwen_qlora"]["measurements"]
409+
]
410+
expected_claims = {
411+
str(controlled["workload_count"]),
412+
str(controlled["observation_count"]),
413+
f"{controlled_error:.2f}%",
414+
f"{100 - controlled_error:.2f}%",
415+
*(f"{error:.4f}%" for error in inference_errors),
416+
*(f"{100 - error:.4f}%" for error in inference_errors),
417+
f"{min(sft_errors):.3f}%–{max(sft_errors):.3f}%",
418+
f"{100 - max(sft_errors):.3f}%–{100 - min(sft_errors):.3f}%",
419+
f"{min(qlora_errors):.3f}%–{max(qlora_errors):.3f}%",
420+
(
421+
f"{100 - max(qlora_errors):.3f}%–"
422+
f"{100 - min(qlora_errors):.3f}%"
423+
),
424+
}
425+
426+
for readme_path in README_PATHS:
427+
readme = (ROOT / readme_path).read_text(encoding="utf-8")
428+
section = _anchored_readme_section(
429+
readme,
430+
"memory-estimation-evidence",
431+
)
432+
evidence_rows = [
433+
line for line in section.splitlines() if line.startswith("|")
434+
]
435+
assert len(evidence_rows) == 6, readme_path
436+
for claim in expected_claims:
437+
assert claim in section, (readme_path, claim)
438+
assert "tests/data/memory_validation_evidence.json" in section
439+
440+
for group in groups.values():
441+
source_url = (
442+
"https://github.com/FanBB2333/FakeGPU/blob/"
443+
f"{revision}/{group['source_path']}"
444+
f"#{group['source_anchor']}"
445+
)
446+
assert source_url in readme, (readme_path, group["id"])

0 commit comments

Comments
 (0)