Repository navigation
Run the clinician demo and inference on Apple silicon and CPU - #277
Merged
Merged
Conversation
Three readers streamed one patient through the model with their own copy of the same PackedLaneSampler loop. It now lives once in odyssey/inference/patient_stream.py (stream_patient, risk_within), and case_study and counterfactual use it. Behaviour is unchanged: the existing case-study and counterfactual tests pass unmodified. Also added, each with tests: - extract_patient_case reports risk at 8/24/72 h (event_risk_by_horizon); event_risk_24h is kept for the report that reads it. - ordered_sequence_rows: the exact row order build_patient_sequence tokenizes, so a position maps back to its raw row. - load_meds_subject: one subject from one shard, filter pushed into the scan. - code_metadata.load_code_descriptions, split out of steering so readers can name codes without importing steering. - occlude_codes: target-agnostic occlusion with a progress callback; occlusion_attribution is now a thin wrapper, plus event_occlusion_attribution for an event's risk. - concept_display_name moves from make_readout_table into concepts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zuuVqnBfTsozfaj5F4y3r
A web app that replays one admission and shows, moment by moment, the model's risk of ICU admission, vasopressors, AKI, Sepsis-3 and death against an alert line, what it thinks is going on, what it expects next, what-if value edits, occlusion evidence, and a scorecard against the tuned GBM. Runbook: docs/clinician_demo.md. - Standard library HTTP only (the GPU host's environment is pinned); loopback bind, Host allowlist, custom API header, no CORS, no-store, same-origin CSP, static path containment. - Two data modes: credentialed (held-out MIMIC-IV) and open (MIMIC-IV Clinical Database Demo). Every chart says whether the model trained on that patient. - Honest by construction: the gallery shows misses and false alarms with their rates; lead time counts from the alert episode still on at the event; risk is hidden after onset; readmission, steering, label overrides and rollouts are never shown (a test enforces the imports). - Vanilla ES modules, no build step, no third-party JS. - 200+ CPU tests on synthetic data: every module, server security over a real socket, and spies proving the run's chunk_size reaches every model call. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zuuVqnBfTsozfaj5F4y3r
for more information, see https://pre-commit.ci
- server: static files are served from a map built once at startup (static_files); a request is a dictionary lookup, so no filesystem path is ever built from request data. Clears the three CodeQL "uncontrolled data used in path expression" alerts. Symlinks out of the root and non-allowlisted extensions are still excluded, now at map-build time; tests cover both. - showcase: rename the `_thr` column and `thr` test argument, which the typos hook reads as "the". - pre-commit: no hook rewrites scripts/gemini/out/ (pre-commit.ci's end-of-file fixer had edited 12 verbatim GEMINI exports on this PR), and mypy/typos skip the two cohort producers committed verbatim as they ran. pyproject's mypy exclude never applied to them because pre-commit passes files by name; this is why main's code check has failed since 0de877f. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zuuVqnBfTsozfaj5F4y3r
This reverts commit 8348226, which added a trailing newline to 12 files under scripts/gemini/out/evals/. Those files are GEMINI's exported reports, kept byte-for-byte; the previous commit stops the hooks from touching them again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zuuVqnBfTsozfaj5F4y3r
Front end only; the API and Python are unchanged.
- Light theme by default, dark via a footer toggle. One accent colour;
event colours only on lines and dots.
- Replay: patient one-liner, sticky "now" bar (play, scrub, clock time),
five risk tiles with a status word (Low / Watch / Alert on / Happened)
and "n× the average patient", then the chart, what happened in the
stay, what the model believes now, and the what-if / evidence tools.
- The chart fades everything after "now".
- Alert stories are composed client-side in clock time ("Began Day 1,
22:53. The alert had been on since Day 1, 01:16: 22 h of warning").
- The 29-row concept strip, alert-line statistics and next-token list
sit behind disclosures; model name and checkpoint move to the footer.
- Gallery cards carry one key line; the full admission list is a
collapsed per-patient list with a note on how many were in training.
- Scorecard cells show one number per model, intervals on hover.
- Fix: class display rules overrode the browser's [hidden] rule, so the
search box showed in open mode and "Alert on now" showed on every row.
- Runbook: static files can be copied over a running deployment.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QebToqtFMVakRkx3PhF3ZX
The hybrid backbone imported mamba-ssm unconditionally, so a trained model could only run on a CUDA host. Without mamba-ssm it now builds from odyssey/models/backbones/mamba_portable.py: Mamba-2 (with the carried-state fix), MHA and RMSNorm in plain PyTorch, with mamba-ssm's parameter names, so a GPU checkpoint loads unchanged. Inference only. - odyssey/utils/device.py: one place for device choice (cuda, then mps, then cpu) and transfer (float64 time stamps become float32 on MPS). - streaming.move_to_device is now the single chunk-move helper; the duplicate train._move_chunk_to_device is gone. - The demo takes --device auto (default) and can read its alert lines from an exported aggregate file (apps.clinician_demo.export_thresholds) when the patient-level alerts_rows.parquet is not on the host. - Steering and concept attribution keep CUDA-else-CPU: they accumulate in float64 on device. Checked against the A100 on 12 open-demo patients (full_run_v10, ~165k positions): mean risk difference 0.0002, max 0.009; top-1 next-event agreement 99.8%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # .pre-commit-config.yaml # _typos.toml
…silicon # Conflicts: # odyssey/inference/run_inference.py
docs/checkpoints.md: what a run directory holds, how full_run_v10 was trained (one joint run, no separate pretraining or fine-tuning), how to prepare MEDS data with the checkpoint's own vocabulary and bins, how to score a patient (the example was run on full_run_v10), the portable backend, and the data-governance terms. It also says that EHR-Mamba3, ehr_mamba3.yaml and scripts/meds/run_pipeline.sh are from an older version of the repository. The README points to it. Also: the alert-line constant's docstring became a comment (check-docstring-first read it as a second module docstring), and scripts/probe_channel.py, new on main, now uses move_to_device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Update:
🤖 Generated with Claude Code |
# Conflicts: # apps/clinician_demo/__main__.py # apps/clinician_demo/service.py # apps/clinician_demo/thresholds.py # docs/clinician_demo.md # odyssey/inference/counterfactual.py # odyssey/inference/patient_stream.py # tests/apps/clinician_demo/test_main_and_layering.py # tests/apps/clinician_demo/test_thresholds.py
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #277 +/- ##
==========================================
+ Coverage 90.51% 90.98% +0.47%
==========================================
Files 69 71 +2
Lines 11073 11263 +190
==========================================
+ Hits 10023 10248 +225
+ Misses 1050 1015 -35
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #250 (
clinician-demo). Lets a trained hybrid checkpoint run without CUDA, so the clinician demo can run entirely on a laptop (tested on an Apple M4).What changes
odyssey/models/backbones/mamba_portable.py(new). Pure-PyTorch Mamba-2 (with our carried-state fix), MHA and RMSNorm. Parameter names matchmamba-ssm2.3.0, so a GPU checkpoint loads unchanged. Inference only: no backward kernel, no single-token decode, no varlen.EHRHybridBackbonepicks its layers in two small helpers (_mixer_classes,_inference_params_cls): the CUDA kernels whenmamba-ssmimports, otherwise the portable layers. The CUDA path is unchanged.odyssey/utils/device.py(new). One place for device choice (cuda, thenmps, thencpu) and transfer (MPS has no float64; time stamps become float32, which the model casts to anyway).streaming.move_to_deviceexisted next to a copy,train._move_chunk_to_device; the copy is removed and its callers usemove_to_device.--device auto(default). Newpython -m apps.clinician_demo.export_thresholds --run-dir …writes the alert lines as aggregates (demo_thresholds_aggregate.json). The demo reads that file when the patient-levelalerts_rows.parquetis absent. Runbook section added indocs/clinician_demo.md.Evidence
GPU vs port, real model (full_run_v10, 12 open-demo patients, ~165k positions; CUDA run on the A100):
CPU and MPS agree with each other more closely (max 0.002) than either agrees with CUDA. The gap matches TF32 / Triton accumulation order, not a port error.
Speed on an M4 (MPS): 2 min for the 12 patients (CPU: 5.5 min). In the demo a 6,000-entry stay traces in about 10 s.
Browser check (open mode only): gallery, replay, risk tiles and chart, concept beliefs, what-if (systolic 80 raises 24 h death risk 1.3% to 1.8%), evidence (40 re-runs in about 30 s) and scorecard all work, with no console errors. Also checked with run A (
full_run_v10_summary2), which needs Add a self-supervised window-summary head and the window-statistic probes #251's code to load.Tests
test_mamba_portable.py(17): chunked scan vs the step-by-step SSM recurrence (ragged lengths, grouped B/C, with and without an initial state), chunk carry-over, chunk-size invariance, norm and causal/GQA attention formulas, parameter names pinned to mamba-ssm, unsupported paths raise.test_hybrid_portable.py: the backbone builds and streams with carried state on CPU; MPS matches CPU.test_mamba_portable_gpu.py: the whole backbone, one state dict, CUDA kernels vs portable (on CUDA and on CPU). Passed on the A100, together with the existingtest_hybrid_gpu.py/test_mamba2_patch_gpu.py.test_device.py, plus alert-line export/load/fallback and--device autotests in the demo suite.test_train_gpu.py::test_train_writes_provenance_for_periodic_and_epoch_checkpointsand::test_train_resumes_mid_epoch_by_fast_forwardingfail on unmodifiedclinician-democode too (A100, same assertions).Data handling
Only open-mode data goes to a laptop. Patient-level files (
alerts_rows.parquet, credentialed shards, the full split file) stay on the GPU host; the runbook lists exactly what to copy.🤖 Generated with Claude Code