Skip to content

Commit 6759bd6

Browse files
committed
Merge reconciled parallel-infer-integration base into PR #140
2 parents fa4e498 + a3c7d39 commit 6759bd6

21 files changed

Lines changed: 1204 additions & 482 deletions

‎AGENTS.md‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# TrainCheck engineering guidance
2+
3+
This file applies throughout the repository. Preserve TrainCheck's explicit type
4+
and runtime contracts: catching malformed data early is part of correctness.
5+
Follow the user's task scope and preserve unrelated work. Resolve routine choices
6+
using these rules; ask when an unresolved choice changes public behavior or scope.
7+
8+
## Types and data contracts
9+
10+
- Fully annotate new functions and changed function interfaces, including private
11+
helpers, worker payloads, container element types, and return values. When
12+
changing an untyped helper's behavior, give it an explicit contract.
13+
- Represent structured data with appropriate aliases, `TypedDict`, dataclasses,
14+
or small protocols. Prefer the simplest representation that states the real
15+
contract; do not introduce abstractions solely to satisfy a checker.
16+
- Do not remove annotations or assertions, widen a type to `Any`, coerce values,
17+
or add ignores merely to silence errors. A necessary contract change needs a
18+
stated reason, caller review, and tests. Document narrow uses of `Any`, `cast`,
19+
`type: ignore`, and `noqa`; existing typing debt is not a reason to add more.
20+
- Treat external JSON, framework metadata, and dataframe values as untrusted
21+
inputs. Validate and narrow at the boundary, using `object` for unknown values
22+
where practical. `cast` and `TypedDict` do not perform runtime validation.
23+
- Required fields must be required in both the type and implementation. Validate
24+
them and use direct access, such as `record["func_call_id"]`; do not substitute
25+
a default when a required field is absent or malformed.
26+
- `.get()`, `getattr()`, and optional types are appropriate when absence has a
27+
defined meaning, such as a cache miss or an unavailable framework capability.
28+
State that meaning and preserve it in the result. Do not blanket-ban these APIs.
29+
- Keep missing, empty, false, unobserved, skipped, unsupported, and failed states
30+
distinct where they mean different things. Never turn an unknown result into
31+
success, zero violations, or an empty dataset through a permissive default.
32+
- Types must describe accepted values, including legacy dataframe scalars. Do
33+
not claim all IDs are integers or silently coerce them unless that is the
34+
actual validated schema.
35+
36+
## Failures and compatibility
37+
38+
- Retain explicit assertions for internal invariants. Reject malformed external
39+
inputs with explicit validation and useful errors; assertions alone disappear
40+
under optimized Python execution.
41+
- Catch exceptions narrowly. A broad catch is justified for cleanup followed by
42+
re-raising, or an explicit failure result whose caller handles it. Do not swallow
43+
errors, continue with fabricated data, or silently fall back after corruption.
44+
- Keep compatibility fallbacks at clear boundaries. Distinguish an absent optional
45+
API from a present API with malformed metadata or unexpected runtime failure.
46+
Expose unsupported behavior and test supported and fallback paths.
47+
- Annotate context-manager exception and traceback parameters. If `__exit__`
48+
never suppresses an exception, use `Literal[False]` for its return type.
49+
- Prefer small, cohesive changes. Separate deliberate semantic changes from
50+
performance changes so reviewers can assess each independently.
51+
52+
## Tracing, inference, and performance
53+
54+
- Instrumentation must preserve application behavior: RNG streams, model state,
55+
return values, exceptions, and checkpoint compatibility. Test the relevant
56+
contract when changing wrappers, serialization, suppression, or sampling.
57+
- Tracer bookkeeping and serialization must not recursively generate user trace
58+
events. Check record completeness and multiplicity, including failure paths
59+
and concurrency where applicable.
60+
- Optimizations must preserve row order, dtypes, duplicates, identity, boundary
61+
conditions, and error behavior. Document cache ownership, data stability, and
62+
invalidation; test mutation or reuse when supported. A fast path needs the same
63+
semantics as the fallback. Inputs unsupported by the fast path must reach the
64+
fallback with unchanged semantics.
65+
- Compare outputs without normalizing away meaningful differences. Dropping
66+
records, IDs, or array contents to obtain equality does not establish parity.
67+
- Invariant validation must report what actually executed: activation/coverage,
68+
dormant rules, skips, failures, and violations. Zero violations alone is not
69+
evidence that the intended invariants were checked.
70+
- For performance claims, record the baseline and candidate revisions, workload,
71+
data/trace identity, configuration, environment, and reproducible commands.
72+
Use matched conditions and repeats/order controls when variability matters.
73+
Separate profiled timings from ordinary runs and component timings from total
74+
inference time. Give the denominator for percentages, memory cost, output
75+
equivalence, and material limitations. Label single-run results accordingly.
76+
- Keep large traces, datasets, checkpoints, credentials, and machine-specific
77+
scratch files out of commits. Store durable results in the agreed location and
78+
record how to find and reproduce them. Use isolated experiment directories and
79+
environments on shared machines; heavy cluster work belongs on allocated
80+
compute resources, not login nodes.
81+
82+
## Validation
83+
84+
- Use the repository's Python requirement in `pyproject.toml` and the tool versions
85+
and options in `.pre-commit-config.yaml` and `.github/workflows/`. Do not disable
86+
hooks, signing, assertions, or checks to make a change pass.
87+
- Run the configured checks for changed code, for example
88+
`pre-commit run --files path/to/changed.py`, and relevant CI checks before
89+
claiming readiness. Run `pre-commit run --all-files` in an isolated clean
90+
checkout when broader validation is needed. Hooks can edit files: inspect and
91+
retain only changes belonging to the task.
92+
- CI is a minimum, not complete coverage. Currently the mypy hook is scoped to
93+
`traincheck/`, untyped bodies are not fully checked, tests have lint/format
94+
exclusions, and tools-only changes may skip CI. Explicitly type-check changed
95+
reusable tools and run relevant tests even when automation does not select them.
96+
- Fully check new modules and reusable helpers; use stricter mypy options such as
97+
`--check-untyped-defs`, `--disallow-untyped-defs`, and `--disallow-any-generics`
98+
for their scope as appropriate. For legacy modules, compare with the base and
99+
introduce no new errors. Do not expand a focused task into a repository-wide
100+
strictness migration. Report any remaining errors and checker coverage limits.
101+
- Behavioral bug fixes need focused regression tests that fail before and pass
102+
after the fix. Exercise meaningful negative paths: missing required fields, wrong types,
103+
malformed records, propagated exceptions, and affected fallback behavior.
104+
Add state, RNG, cache, or concurrency tests when those contracts change.
105+
- Run existing tests relevant to affected callers and compare outputs for
106+
optimizations. Do not add tests that merely repeat implementation details.
107+
Documentation-only edits normally need content/link and diff checks, not a
108+
runtime test suite. Report exactly what ran and what could not run.
109+
110+
## Git and review
111+
112+
- Inspect branch, worktree status, and applicable guidance before editing. Preserve
113+
unrelated changes and shared environments. Use an isolated branch/worktree when
114+
needed; stage named files or hunks, never sweep unrelated work into a commit.
115+
- Checkpoint coherent progress with small, signed commits and focused PRs as
116+
authorized. Preserve configured signing; if signing fails, report the failure
117+
instead of silently producing an unsigned commit. Do not merge without explicit
118+
user authorization.
119+
- PR descriptions should state the problem, final approach, behavior/contracts
120+
preserved or changed, validation, and limitations. Include measured before/after
121+
numbers for performance work, with units, baseline, and end-to-end context.
122+
Do not invent improvement numbers for documentation or correctness-only work.
123+
- State dependencies between stacked PRs. Recheck the composed result after
124+
rebasing or integrating changes; a branch-local pass does not prove the final
125+
stack works. Keep experiment evidence separate from unverified expectations.

‎README.md‎

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,102 @@
22
<picture>
33
<img alt="TrainCheck logo" width="55%" src="https://raw.githubusercontent.com/OrderLab/TrainCheck/main/docs/assets/images/traincheck_logo.png">
44
</picture>
5-
<h1>TrainCheck: Invariant Checking & Observability for AI Training</h1>
5+
<h1>TrainCheck: Invariant Checking for AI Training</h1>
66

77
[![Chat on Discord](https://img.shields.io/badge/Discord-Join%20us-5865F2?logo=discord&logoColor=white)](https://discord.gg/ZvYewjsQ9D)
88
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/OrderLab/TrainCheck)
99

1010
</div>
1111

12+
TrainCheck catches silent training bugs by learning what a healthy run does, then checking a new run against those learned invariants. It works by tracing PyTorch API calls and model state changes, so you can inspect training behavior before a loss curve or final metric tells you something went wrong.
1213

13-
**Stop flying blind.** TrainCheck gives you deep visibility into your training dynamics, continuously validating correctness and stability where standard metrics fail.
14+
## Install
1415

15-
---
16+
Install TrainCheck in the same Python environment that runs your training script:
1617

17-
### Why TrainCheck?
18+
```bash
19+
pip3 install traincheck
20+
```
1821

19-
✅ **Continuous Invariant Checking**
20-
TrainCheck validates the "physics" of your training process in real-time. It ensures your model adheres to learned invariants—such as gradient norms, tensor shapes, and update magnitudes—effectively catching silent corruption before it wastes GPU hours.
22+
For CUDA, conda, and source-install details, see the [Installation Guide](https://orderlab.io/TrainCheck/installation-guide/).
2123

22-
🚀 **Holistic Observability**
23-
Traditional tools only show you *if* your model crashed. TrainCheck shows you *why* it's degrading, analyzing internal state dynamics that loss curves miss.
24+
## Use TrainCheck
2425

25-
🧠 **Zero-Config Validation**
26-
No manual tests required. TrainCheck automatically learns the invariants of your specific model from healthy runs and flags deviations instantly.
26+
TrainCheck has four main steps.
2727

28-
⚡ **Universal Compatibility**
29-
Drop-in support for PyTorch, Hugging Face, and industry-class workloads using DeepSpeed/Megatron and more.
28+
### 1. Collect a Reference Trace
3029

31-
---
32-
## Installation
30+
Run `traincheck-collect` on a known-good training script. This should be a short run that covers the training behavior you want TrainCheck to learn.
31+
32+
```bash
33+
traincheck-collect \
34+
--pyscript reference.py \
35+
--models-to-track model \
36+
--output-dir reference_trace
37+
```
3338

34-
Install TrainCheck in the Python environment where you will run your training script:
39+
### 2. Infer Invariants
40+
41+
Turn the reference trace into invariants:
3542

3643
```bash
37-
pip3 install traincheck
44+
traincheck-infer -f reference_trace -o invariants.json
45+
```
46+
47+
### 3. Collect a Target Trace
48+
49+
Run the target training script with the inferred invariants. Passing `--invariants` lets TrainCheck trace only the APIs and variables needed for those checks.
50+
51+
```bash
52+
traincheck-collect \
53+
--pyscript target.py \
54+
--models-to-track model \
55+
--invariants invariants.json \
56+
--output-dir target_trace
3857
```
3958

40-
For detailed setup (CUDA configuration, UV, conda environments), see the [Installation Guide](https://orderlab.io/TrainCheck/installation-guide/).
59+
For long target runs, trace fewer steps:
4160

61+
```bash
62+
traincheck-collect \
63+
--pyscript target.py \
64+
--models-to-track model \
65+
--invariants invariants.json \
66+
--sampling-interval 10 \
67+
--warm-up-steps 10 \
68+
--output-dir target_trace
69+
```
4270

43-
### How It Works
71+
### 4. Check the Target Run
4472

45-
1. **Instrument**: We wrap your training loop with lightweight probes—no code changes needed.
46-
2. **Learn**: We analyze correct runs to infer *invariants* (mathematical rules of healthy training).
47-
3. **Check**: We monitor new runs in real-time, verifying every step against learned invariants to catch silent logic bugs and hardware faults.
73+
For live checking, start `traincheck-onlinecheck` while the target run is writing traces:
4874

49-
![Workflow](https://raw.githubusercontent.com/OrderLab/TrainCheck/main/docs/assets/images/workflow.png)
75+
```bash
76+
traincheck-onlinecheck -f target_trace -i invariants.json
77+
```
5078

51-
## 🔥 Try TrainCheck
79+
The easier offline path is to wait for trace collection to finish, then run:
5280

53-
Work through [5‑Minute Experience with TrainCheck](./docs/5-min-tutorial.md). You’ll learn how to:
54-
- Instrument a training script and collect a trace
55-
- Automatically infer invariants
56-
- Uncover silent bugs in the training script
81+
```bash
82+
traincheck-check -f target_trace -i invariants.json
83+
```
84+
85+
Both checkers write a results directory with failure logs and a `report.html` summary.
5786

58-
## Documentation
87+
## Learn More
5988

60-
- **[Installation Guide](https://orderlab.io/TrainCheck/installation-guide/)**
61-
- **[Usage Guide: Scenarios and Limitations](https://orderlab.io/TrainCheck/usage-guide/)**
62-
- **[TrainCheck Technical Doc](https://orderlab.io/TrainCheck/technical-doc/)**
89+
- [Use TrainCheck](https://orderlab.io/TrainCheck/usage-guide/) explains the full workflow and output files.
90+
- [5-Minute Tutorial](./docs/5-min-tutorial.md) walks through a real silent training issue.
91+
- [Installation Guide](https://orderlab.io/TrainCheck/installation-guide/) covers environment setup.
92+
- [Technical Documentation](https://orderlab.io/TrainCheck/technical-doc/) describes invariants, trace representation, and implementation details.
6393

6494
## Status
6595

66-
TrainCheck is under active development. Please join our 💬 [Discord server](https://discord.gg/VwxpJDvB) or file a GitHub issue for support. You can also reach the team at [traincheck@umich.edu](mailto:traincheck@umich.edu).
67-
We welcome feedback and contributions from early adopters.
96+
TrainCheck is under active development. Please join our [Discord server](https://discord.gg/VwxpJDvB), file a GitHub issue, or email [traincheck@umich.edu](mailto:traincheck@umich.edu).
6897

6998
## Contributing
7099

71-
We welcome and value any contributions and collaborations. Please check out [Contributing to TrainCheck](./CONTRIBUTING.md) for how to get involved.
100+
We welcome contributions. See [Contributing to TrainCheck](./CONTRIBUTING.md) for setup and contribution guidance.
72101

73102
## License
74103

@@ -77,6 +106,7 @@ TrainCheck is licensed under the [Apache License 2.0](./LICENSE).
77106
## Citation
78107

79108
If TrainCheck is relevant to your work, please cite our paper:
109+
80110
```bib
81111
@inproceedings{TrainCheckOSDI2025,
82112
author = {Jiang, Yuxuan and Zhou, Ziming and Xu, Boyu and Liu, Beijie and Xu, Runhui and Huang, Peng},
@@ -90,7 +120,6 @@ If TrainCheck is relevant to your work, please cite our paper:
90120
}
91121
```
92122

93-
94123
## Artifact Evaluation
95124

96-
🕵️‍♀️ OSDI AE members, please see [TrainCheck AE Guide](./docs/ae.md).
125+
OSDI AE members should use the [TrainCheck AE Guide](./docs/ae.md).

‎SUGGESTION.md‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Suggestion for PR #137
2+
3+
The current fix using `rsplit()` is better than the original `split()`, but it can still fail in edge cases where directory names repeat patterns.
4+
5+
## The Problem
6+
Consider a file path like `/abc/abcd/abc/abcd/file.py` where `root_module = "abc"`:
7+
- Current approach: `rsplit(f"/abc/", 1)` splits on the last `/abc/`, producing `abcd/file.py`
8+
- Result: `abc.abcd.file` instead of the correct `abc.abcd.abc.abcd.file`
9+
10+
## More Robust Solution
11+
Instead of relying on string parsing with the module name, get the actual filesystem location of the root module and remove it from the file path:
12+
13+
```python
14+
def get_module_path_from_file_path(file_path: str, root_module: str) -> str | None:
15+
import importlib.util
16+
17+
# Validate inputs
18+
if (
19+
not file_path.endswith(".py")
20+
or not os.path.exists(file_path)
21+
or f"/{root_module}/" not in file_path
22+
):
23+
return None
24+
25+
# Get the actual location of the root module
26+
try:
27+
spec = importlib.util.find_spec(root_module)
28+
if spec is None or spec.origin is None:
29+
return None
30+
31+
module_dir = os.path.dirname(spec.origin)
32+
# Go up one level if it's an __init__.py
33+
if os.path.basename(spec.origin) == "__init__.py":
34+
module_dir = os.path.dirname(module_dir)
35+
except (ImportError, AttributeError, ValueError):
36+
return None
37+
38+
# Get relative path from module directory
39+
try:
40+
relative_path = os.path.relpath(file_path, module_dir)
41+
except ValueError:
42+
# Paths on different drives on Windows
43+
return None
44+
45+
# Convert to module path
46+
if relative_path.endswith(".py"):
47+
relative_path = relative_path[:-3]
48+
module_path = root_module + "." + relative_path.replace(os.sep, ".")
49+
50+
return module_path
51+
```
52+
53+
## Benefits
54+
- **No string parsing vulnerability** - Works regardless of repeated naming patterns
55+
- **Uses actual filesystem paths** - More reliable and maintainable
56+
- **Handles edge cases** - Works with __init__.py files and package structures
57+
- **More explicit** - The intent is clearer to future maintainers
58+
59+
This approach is immune to all naming collision edge cases!

‎docs/ae-eval-s5.5-perf-overhead.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This evaluation measures the runtime overhead introduced by TrainCheck’s instr
2121
- Located in [overhead-e2e](../eval_scripts/perf_benchmark/overhead-e2e)
2222

2323
- The deployed 100 invariants:
24-
[eval_scripts/perf_benchmark/overhead-e2e/sampled_100_invariants.json](../eval_scripts/perf_benchmark/overhead-e2e/sampled_100_invariants.json)
24+
`eval_scripts/perf_benchmark/overhead-e2e/sampled_100_invariants.json`
2525

2626

2727
## 🛠 How to Run
105 KB
Loading
91.2 KB
Loading
104 KB
Loading
134 KB
Loading
103 KB
Loading

‎docs/blog/index.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# TrainCheck Blog
2+
3+
Research notes, engineering lessons, and project updates about dependable
4+
machine learning training.

0 commit comments

Comments
 (0)