Skip to content

Make duplicate script runs harmless and attributable - #51

Open
NetZissou wants to merge 2 commits into
mainfrom
fix/cuml-availability-race
Open

NetZissou wants to merge 2 commits into
mainfrom
fix/cuml-availability-race

Conversation

@NetZissou

Copy link
Copy Markdown
Collaborator

My flaky mouse triggered a double-click on the "Project" btn. With "auto" mode, the two runs imported cuML at the same instant, and the second importer hit a partially initialized pkg, and the app silently ran:

  • one projection on cuML
  • one projection on sklearn

Both runs shared PID-named temp files for the cuML UMAP subprocess, so the first run's cleanup deleted the second run's output.


Changes

  • check_cuml_available() imports cuML under a lock, caches a success, and logs an import failure instead of swallowing it. The probe now costs one import per session instead of one per projection.
  • cuML UMAP subprocess temp files are unique per call now
  • Project to 2D / Run KMeans log each click with session and thread ids and flag a run in flight; a duplicate is logged, never refused.
  • Add spinner while the backend is resolved, so the cold cuML import no longer looks like a dead button.

Behavior Description

One Click

  1. The run reaches the Project to 2D handler; the button is True. It logs clicked (session=…, thread=…), sees no run in flight, sets the projection_running flag, and enters _run_projection.
  2. Spinner "Checking compute backend": first time in the session this imports cuML (seconds); afterwards it's instant thanks to the cache.
  3. Spinner "Running UMAP": the reduction runs, for cuML UMAP in a subprocess with its own temp files.
  4. The spinner exits, the run writes session_state (projection, provenance), logs "Projection complete", clears the flag, and the page renders the result.
One-click log
[2026-10-12 10:02:08] INFO [shared.services.embedding_service.load_model_unified:133] Model loaded in 21.92s
[2026-10-12 10:02:10] INFO [shared.utils.image_pipeline.embed_image_folder:150] [Embed] 55/55 images embedded (0 skipped)
[2026-10-12 10:02:10] INFO [shared.services.embedding_service.generate_embeddings:231] Embedding generation completed: 55 embeddings in 24.21s (2.3 images/sec)
[2026-10-12 10:02:10] INFO [apps.embed_explore.components.sidebar.render_embedding_section:80] Embeddings stored: shape=(55, 768), dtype=float32
[2026-10-12 10:02:21] INFO [apps.embed_explore.components.sidebar.render_projection_section:127] Project to 2D clicked (session=a493df98 thread=22726862743104)
[2026-10-12 10:02:21] INFO [shared.utils.backend.check_cuda_available:59] CUDA available via PyTorch: Tesla V100-PCIE-16GB
[2026-10-12 10:02:27] INFO [shared.utils.backend.resolve_backend:134] Auto-resolved reduction backend to cuML (GPU: Tesla V100-PCIE-16GB)
[2026-10-12 10:02:27] INFO [apps.embed_explore.components.sidebar._run_projection:170] ============================================================
[2026-10-12 10:02:27] INFO [apps.embed_explore.components.sidebar._run_projection:171] PROJECTION START (session=a493df98 thread=22726862743104)
[2026-10-12 10:02:27] INFO [apps.embed_explore.components.sidebar._run_projection:172] Device: Tesla V100-PCIE-16GB (CUDA: Yes)
[2026-10-12 10:02:27] INFO [apps.embed_explore.components.sidebar._run_projection:173] Backend: cuml (requested: auto)
[2026-10-12 10:02:27] INFO [apps.embed_explore.components.sidebar._run_projection:177] Records: 55 | Dim: 768
[2026-10-12 10:02:27] INFO [shared.services.clustering_service.run_dim_reduction:104] Dim reduction: samples=55, features=768, method=UMAP, backend=cuml, seed=614
[2026-10-12 10:02:27] INFO [shared.utils.clustering.reduce_dim:166] Dimensionality reduction: method=UMAP, samples=55, features=768, backend=cuml
[2026-10-12 10:02:27] INFO [shared.utils.clustering._prepare_embeddings:142] [reduce_dim] Prepared embeddings: 55 samples, 768 features, dtype=float32, L2-normalized (input norms: min=39.29, max=46.71, mean=42.99)
[2026-10-12 10:02:27] INFO [shared.utils.clustering.reduce_dim:182] Using cuML backend for UMAP
[2026-10-12 10:02:27] INFO [shared.utils.clustering._run_cuml_umap_subprocess:331] Running cuML UMAP in subprocess (55 samples, n_neighbors=15)
[2026-10-12 10:02:35] INFO [shared.utils.clustering._run_cuml_umap_subprocess:341] cuML UMAP subprocess completed successfully
[2026-10-12 10:02:35] INFO [shared.utils.clustering.reduce_dim:189] Dimensionality reduction completed in 7.99s
[2026-10-12 10:02:35] INFO [shared.services.clustering_service.run_dim_reduction:112] Dim reduction complete: (55, 2) in 7.99s
[2026-10-12 10:02:35] INFO [apps.embed_explore.components.sidebar._run_projection:186] Projection complete in 7.99s
[2026-10-12 10:02:35] INFO [apps.embed_explore.components.sidebar._run_projection:209] ============================================================
[2026-10-12 10:02:35] INFO [shared.components.visualization._render_chart_fragment:59] [Visualization] Density mode changed: None -> Off

Two clicks in quick succession

The second click arrives while the first click's rerun request is still being processed on the frontend. Streamlit coalesces them to one run.

Two clicks in quick succession log
[2026-10-12 10:02:35] INFO [apps.embed_explore.components.sidebar._run_projection:209] ============================================================
[2026-10-12 10:02:35] INFO [shared.components.visualization._render_chart_fragment:59] [Visualization] Density mode changed: None -> Off
[2026-10-12 10:04:35] INFO [apps.embed_explore.components.sidebar.render_projection_section:127] Project to 2D clicked (session=a493df98 thread=22726839629376)
[2026-10-12 10:04:35] INFO [shared.utils.backend.resolve_backend:134] Auto-resolved reduction backend to cuML (GPU: Tesla V100-PCIE-16GB)
[2026-10-12 10:04:35] INFO [apps.embed_explore.components.sidebar._run_projection:170] ============================================================
[2026-10-12 10:04:35] INFO [apps.embed_explore.components.sidebar._run_projection:171] PROJECTION START (session=a493df98 thread=22726839629376)
[2026-10-12 10:04:35] INFO [apps.embed_explore.components.sidebar._run_projection:172] Device: Tesla V100-PCIE-16GB (CUDA: Yes)
[2026-10-12 10:04:35] INFO [apps.embed_explore.components.sidebar._run_projection:173] Backend: cuml (requested: auto)
[2026-10-12 10:04:35] INFO [apps.embed_explore.components.sidebar._run_projection:177] Records: 55 | Dim: 768
[2026-10-12 10:04:35] INFO [shared.services.clustering_service.run_dim_reduction:104] Dim reduction: samples=55, features=768, method=UMAP, backend=cuml, seed=217
[2026-10-12 10:04:35] INFO [shared.utils.clustering.reduce_dim:166] Dimensionality reduction: method=UMAP, samples=55, features=768, backend=cuml
[2026-10-12 10:04:35] INFO [shared.utils.clustering._prepare_embeddings:142] [reduce_dim] Prepared embeddings: 55 samples, 768 features, dtype=float32, L2-normalized (input norms: min=39.29, max=46.71, mean=42.99)
[2026-10-12 10:04:35] INFO [shared.utils.clustering.reduce_dim:182] Using cuML backend for UMAP
[2026-10-12 10:04:35] INFO [shared.utils.clustering._run_cuml_umap_subprocess:331] Running cuML UMAP in subprocess (55 samples, n_neighbors=15)
[2026-10-12 10:04:42] INFO [shared.utils.clustering._run_cuml_umap_subprocess:341] cuML UMAP subprocess completed successfully
[2026-10-12 10:04:42] INFO [shared.utils.clustering.reduce_dim:189] Dimensionality reduction completed in 6.28s
[2026-10-12 10:04:42] INFO [shared.services.clustering_service.run_dim_reduction:112] Dim reduction complete: (55, 2) in 6.28s
[2026-10-12 10:04:42] INFO [apps.embed_explore.components.sidebar._run_projection:186] Projection complete in 6.28s
[2026-10-12 10:04:42] INFO [apps.embed_explore.components.sidebar._run_projection:209] ============================================================

A second click after the first run has started

  1. Streamlit starts run B on a new thread and flags run A for stopping.
  2. Run B executes the script from the top. At the handler it logs clicked, finds the flag set by A, and logs WARNING Duplicate projection request…. It does not refuse, B is the run that will survive. it sets the flag and starts its own projection.
  3. Run A is deep in the reduction with no Streamlit calls, so it keeps going: its subprocess finishes and logs "completed successfully" and "Dim reduction complete".
  4. Run A's next Streamlit call is the spinner exit right after the reduction. It raises StopException there. The finally in single_run clears the flag; A never logs "Projection complete" and never writes state.
  5. Run B finishes normally and commits the result.

Example below shows four clicked lines, three warnings, four subprocesses completed, one "Projection complete". The last click wins, the page shows its result, and the only cost is the abandoned GPU work.

Multiple-clicks, all subprocess completes, the latest is used as projection
[2026-10-12 10:07:15] INFO [apps.embed_explore.components.sidebar._run_projection:170] ============================================================
[2026-10-12 10:07:15] INFO [apps.embed_explore.components.sidebar._run_projection:171] PROJECTION START (session=a493df98 thread=22726839629376)
[2026-10-12 10:07:15] INFO [apps.embed_explore.components.sidebar._run_projection:172] Device: Tesla V100-PCIE-16GB (CUDA: Yes)
[2026-10-12 10:07:15] INFO [apps.embed_explore.components.sidebar._run_projection:173] Backend: cuml (requested: auto)
[2026-10-12 10:07:15] INFO [apps.embed_explore.components.sidebar._run_projection:177] Records: 55 | Dim: 768
[2026-10-12 10:07:15] INFO [shared.services.clustering_service.run_dim_reduction:104] Dim reduction: samples=55, features=768, method=UMAP, backend=cuml, seed=217
[2026-10-12 10:07:15] INFO [shared.utils.clustering.reduce_dim:166] Dimensionality reduction: method=UMAP, samples=55, features=768, backend=cuml
[2026-10-12 10:07:15] INFO [shared.utils.clustering._prepare_embeddings:142] [reduce_dim] Prepared embeddings: 55 samples, 768 features, dtype=float32, L2-normalized (input norms: min=39.29, max=46.71, mean=42.99)
[2026-10-12 10:07:15] INFO [shared.utils.clustering.reduce_dim:182] Using cuML backend for UMAP
[2026-10-12 10:07:15] INFO [shared.utils.clustering._run_cuml_umap_subprocess:331] Running cuML UMAP in subprocess (55 samples, n_neighbors=15)
[2026-10-12 10:07:18] INFO [shared.utils.clustering._run_cuml_umap_subprocess:341] cuML UMAP subprocess completed successfully
[2026-10-12 10:07:18] INFO [shared.utils.clustering.reduce_dim:189] Dimensionality reduction completed in 5.50s
[2026-10-12 10:07:18] INFO [shared.services.clustering_service.run_dim_reduction:112] Dim reduction complete: (55, 2) in 5.50s
[2026-10-12 10:07:20] INFO [shared.utils.clustering._run_cuml_umap_subprocess:341] cuML UMAP subprocess completed successfully
[2026-10-12 10:07:20] INFO [shared.utils.clustering.reduce_dim:189] Dimensionality reduction completed in 5.13s
[2026-10-12 10:07:20] INFO [shared.services.clustering_service.run_dim_reduction:112] Dim reduction complete: (55, 2) in 5.13s
[2026-10-12 10:07:20] INFO [shared.utils.clustering._run_cuml_umap_subprocess:341] cuML UMAP subprocess completed successfully
[2026-10-12 10:07:20] INFO [shared.utils.clustering._run_cuml_umap_subprocess:341] cuML UMAP subprocess completed successfully
[2026-10-12 10:07:20] INFO [shared.utils.clustering.reduce_dim:189] Dimensionality reduction completed in 5.70s
[2026-10-12 10:07:20] INFO [shared.services.clustering_service.run_dim_reduction:112] Dim reduction complete: (55, 2) in 5.70s
[2026-10-12 10:07:20] INFO [shared.utils.clustering.reduce_dim:189] Dimensionality reduction completed in 4.99s
[2026-10-12 10:07:20] INFO [shared.services.clustering_service.run_dim_reduction:112] Dim reduction complete: (55, 2) in 4.99s
[2026-10-12 10:07:21] INFO [apps.embed_explore.components.sidebar._run_projection:186] Projection complete in 5.22s
[2026-10-12 10:07:21] INFO [apps.embed_explore.components.sidebar._run_projection:209] ============================================================

NetZissou and others added 2 commits September 22, 2026 10:11
Two Streamlit script runs resolving the auto backend at the same moment
imported cuML concurrently; the second importer could see a partially
initialized package, get an ImportError, and be reported as "cuML not
available", so one run projected on sklearn while the other used the GPU.

We now guard the import with a lock and cache a successful result, log import failures instead of swallowing them, and tag the projection, KMeans and embedding log banners with the Streamlit session and thread ids so duplicate runs can be attributed.

Also give the cuML UMAP subprocess unique temp files per call: two
concurrent runs in one process shared PID-named files, and the first
run's cleanup deleted the second run's output, which then fell back to
sklearn with "subprocess failed (rc=0)".

Refs #49

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Show a spinner while the compute backend is resolved (the first cuML
probe of a session imports cuML, which can take several seconds with no
feedback), log every Project to 2D / Run KMeans click with its session
and thread ids, and flag runs in flight so a duplicate is visible in the
log.

The duplicate is never refused: Streamlit stops the older script
run at its next Streamlit call once a newer one exists, so refusing the
newer run left no projection running at all.
@NetZissou NetZissou self-assigned this Oct 12, 2026
@NetZissou NetZissou added bug Something isn't working enhancement New feature or request labels Oct 12, 2026
@NetZissou NetZissou linked an issue Oct 12, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate Script Run

1 participant