Skip to main content

gq

CI PyPI Python License: MIT

A small, single-user GPU job scheduler for one Linux workstation. Queue commands, get whole NVIDIA GPUs assigned atomically, and keep jobs running after you close the terminal.

gq -g 1 uv run train.py          # queued, runs when a GPU is genuinely free
gq -g 2 torchrun --nproc-per-node=2 train.py
gq ps                            # what is queued and running
gq logs -f 12                    # follow a job's output

No cluster, no root, no config file. One pip install, one user-level daemon.

The part that matters

Most of the time, "is this GPU free?" is answered by looking at utilization. That answer is wrong in the case that bites hardest on a workstation:

Your Jupyter kernel finished a cell an hour ago. It sits at 0% utilization and still holds a CUDA context and several GB of memory. Start a training run on that GPU and you get an OOM — or worse, two jobs quietly fighting over one card.

gq treats a GPU as allocatable only when both of these hold:

  1. gq itself has no reservation on it, and
  2. NVML reports no compute process on it that gq does not own.

Utilization is never used as an ownership signal. A process gq does not recognise makes the GPU EXTERNAL and off-limits — gq routes around it and never kills it. When ownership is ambiguous, the GPU becomes UNKNOWN and stays unallocated. The design fails closed: the worst case is an idle GPU, never a double-booked one.

That care extends to the parts that usually break:

  • Reservation is atomic. The WAITING → STARTING transition and the full GPU assignment are written in one SQLite transaction, before the process launches. A crash between the two is not possible.
  • Restarts do not lose jobs. Stopping the daemon leaves your jobs running. On restart it reconciles against saved PID, /proc start-time ticks, process group, Linux boot ID, and current GPU UUIDs, and reattaches what it can prove is the same process.
  • Cancellation cannot hit the wrong process. gq signals a saved process group only after checking process identity, so a recycled PID is never mistaken for your job.

Install

uv tool install gq-local     # or: pipx install gq-local, pip install gq-local

Requires Linux, Python 3.11+, an NVIDIA driver with NVML, and no root. The only runtime dependency is nvidia-ml-py.

Quick start

gq daemon start          # optional; any command starts it on demand
gq gpu                   # what each GPU is doing, and who owns it
gq -g 1 uv run train.py  # submit; returns immediately
gq ps
gq logs -f 1

gq sets CUDA_VISIBLE_DEVICES for the job, so physical GPUs 2,3 appear inside your code as cuda:0 and cuda:1.

Scripts can carry their own requirements:

#!/bin/bash
#gq --gpus=2
#gq --name=experiment

uv run train.py
gq submit experiment.sh

Day to day:

Command
gq -g N CMD... / gq run -g N CMD... submit a command
gq submit SCRIPT submit a shell script with #gq directives
gq ps [--limit N] [--json] list jobs
gq gpu [--json] physical and logical GPU state
gq show JOB [--json] everything about one job
gq logs [-f] JOB read or follow a job's output
gq cancel JOB SIGTERM the process group, then SIGKILL after a grace period
gq rm JOB... delete terminal jobs and their logs
gq clean --older-than 30d sweep old terminal jobs and their logs
gq daemon start|stop|status manage the daemon

--json is the supported scripting interface; table layouts may change between releases.

A note on secrets

A queued job may not start for hours, so gq saves your environment in order to run it later with the same context. Variables that look like credentials are dropped rather than stored, and gq tells you which:

gq: not persisting 1 secret-looking variable(s): HF_TOKEN
    use --env-all (or GQ_ENV_ALL=1) to keep them, or --env-keep NAME

If a job genuinely needs one, --env-keep HF_TOKEN puts it back. Anything kept is stored in plaintext in a 0600 SQLite database — see SECURITY.md.

Is this the right tool?

You want Use
Many machines, many users, fair-share, accounting Slurm, or a Kubernetes scheduler
One workstation, your own jobs, whole GPUs, zero setup gq
A generic job queue with no GPU awareness task-spooler
Cloud or multi-node orchestration for ML SkyPilot, Determined

gq deliberately does not do multi-node scheduling, multiple users, CPU/RAM allocation, priorities, preemption, MIG, containers, or fractional GPU sharing. Some of those are on the roadmap; see CONTRIBUTING.md before proposing one.

Documentation

  • Usage — every command, #gq directives, environment handling, retention
  • Architecture — allocation invariant, IPC, crash and reboot recovery
  • Troubleshooting — stuck GPUs, stuck jobs, daemon problems
  • Security — threat model and how credentials are handled
  • Changelog

Development

git clone https://github.com/BirddWang/gq
cd gq
uv sync --all-groups
uv run pytest

The suite runs against a fake NVML provider, so no GPU is required to develop or to run CI. Hardware tests are opt-in:

GQ_RUN_GPU_TESTS=1 uv run pytest tests/integration

Contributions are welcome — please read CONTRIBUTING.md first, especially the section on the allocation invariant.

License

MIT — see LICENSE.

Metadata

Release files for gq-local 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gq-local 0.1.1
File Size Uploaded
gq_local-0.1.1.tar.gz 72.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gq-local 0.1.1
File Interpreter ABI Platform
gq_local-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 102.2 kB

Release files / gq_local-0.1.1.tar.gz

Download URL gq_local-0.1.1.tar.gz
Size 72.7 kB
Tags Source
SHA-256 checksum
How to use checksums
acb66009944a8b981f41757ac0137cc287fa184afb9b2ff050a7ce46e7cfccc4
BLAKE2b-256 checksum
How to use checksums
9f0cc540eeb6fcd15c46b331e717652e5878638416370b15296470d96ae16ffa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release files / gq_local-0.1.1-py3-none-any.whl

Download URL gq_local-0.1.1-py3-none-any.whl
Size 29.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a679836dea84e4d0a5c4eca0f873548c8542daf8f4a7e021c0de4af8051ea3b4
BLAKE2b-256 checksum
How to use checksums
d575cdb18a53fe299cef52f290dc6918b43c326f1dd62791beac6e53b521a5cc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

0.2.0

2 release files

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page