Skip to content

Cloud provider preflight (Modal, Azure) + abliterix wiring - #6

Merged
colbyt merged 2 commits into
mainfrom
cloud-providers-and-abliterix
Aug 9, 2026
Merged

colbyt merged 2 commits into
mainfrom
cloud-providers-and-abliterix

Conversation

@colbyt

@colbyt colbyt commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Two additions, both requested ahead of the credentials/hardware that will exercise them.

forge cloud — rented-compute preflight

Fleet nodes come through the lease system; cloud GPUs don't (paid by the second, uncontended), so a cloud run is an --off-fleet run that dispatches to a provider. Modal and Azure account details are still pending, so this lands the layer that is useful without them — a declarative statement of what each provider needs, and a check naming exactly what's absent:

modal    NOT READY  Modal serverless GPU containers (up to 8 GPUs/container)
    MISSING  MODAL_TOKEN_ID — ...; or ~/.modal.toml
    MISSING  modal CLI — install: uv tool install modal  # then: modal token new
    gpus     B200:8, B200+:8, B300:8, H200:8, H100:8, RTX-PRO-6000
  • Preflight is local-only: no network, no spend. It answers "could this possibly work" from env vars, provider config files, and $PATH.
  • A provider's own config file satisfies a credential — modal token new and az login export nothing, and reporting those as missing would send an operator chasing a non-problem.
  • Exits 0 even when not ready. It exists to be run before credentials arrive; a red report is information, not a failed command.
  • Job submission is deliberately out of scope. The gap between "plumbing exists" and "plumbing can spend money" should be crossed on purpose, in a separate review.

GPU shapes are verified against vendor docs (2026-08-07), not asserted: Modal accepts B200/B300 at up to 8 per container; Azure's Blackwell SKUs are 4-GPU Grace-Blackwell VMs (ND GB300 v6 / ND GB200 v6), so an 8-GPU job there is a two-VM NVLink-domain problem. Live availability and quota are explicitly not claimed — az vm list-skus against the real subscription is the only truth.

abliterix

abliterix runs an Optuna search over abliteration parameters, scoring refusal rate against KL divergence from the base model, and exports a reversible LoRA or baked projections with a SHA256 reproducibility manifest.

  • recipes/abliterate-abliterix.yamlprofile: cuda-datacenter, since the models this was wired in for exceed every Berkeley GPU.
  • tool_cmd is pre-filled from the project README and marked UNVERIFIED. The repo's rule stands: run abliterix --help on the node and reconcile before the first real run. It's pre-filled rather than null because the shape is documented and stable — it is still an unverified claim until someone looks.
  • bootstrap/fleet-node.sh installs it into the profile env (outside the lock — it pulls vLLM) and prints --help so that reconciliation actually happens.
  • common.render_tool_cmd() adds {model_path} to both surgery wrappers so one recipe templates across models. Relative paths resolve under model_root only when they exist on disk; HF repo ids pass through unmangled. The null-tool_cmd refusal contract is unchanged and still tested.

Not tested on hardware yet — abliterix needs vLLM, so it won't install on macOS, and testing was deferred rather than risk disturbing GPUs currently serving.

114 tests pass.

Rented compute is a new axis for forge: fleet nodes come through the lease
system, but cloud GPUs are paid by the second and uncontended, so a cloud run
is an --off-fleet run that dispatches to a provider.

Credentials for both accounts are still pending, so this lands the layer that
is useful *without* them: a declarative statement of what each provider needs
and a preflight that names exactly what is absent.

- src/forge/providers/: Provider/Credential base, plus modal and azure.
  Preflight is local-only (no network, no spend) and treats a provider's own
  config file (~/.modal.toml, ~/.azure) as satisfying a credential, since
  `modal token new` and `az login` export nothing.
- forge cloud providers|preflight, --json throughout. Preflight exits 0 even
  when not ready — a red report before the creds arrive is information, not a
  command failure.
- GPU shapes verified against vendor docs 2026-08-07, not asserted: Modal
  takes B200/B300 up to 8 per container; Azure's Blackwell SKUs are 4-GPU
  Grace-Blackwell VMs (ND GB300 v6 / ND GB200 v6), so 8x on Azure is a two-VM
  job. Availability and quota are deliberately NOT claimed.

Abliteration:
- recipes/abliterate-abliterix.yaml wires abliterix (Optuna search over
  abliteration params, scored refusal-rate vs KL divergence). tool_cmd is
  pre-filled from the project README and marked UNVERIFIED — reconcile
  against `abliterix --help` on the node before the first run.
- bootstrap/fleet-node.sh installs abliterix into the profile env (outside the
  lock; it pulls vLLM) and prints its --help so that reconciliation happens.
- common.render_tool_cmd() adds {model_path} to the surgery wrappers, so one
  recipe templates across models. Relative paths resolve under model_root only
  when they exist on disk; HF repo ids pass through unmangled. The null
  tool_cmd refusal contract is unchanged and still tested.

114 tests pass.

@colbyt colbyt left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forge PR #6 review — Cloud provider preflight (Modal, Azure) + abliterix wiring

Reviewed: gh pr diff 6, full diff (632 lines) read in context; ran pytest tests/test_cloud.py tests/test_surgery_tool_cmd.py against the PR branch on this machine; checked recipes.py,
envs/profiles.yaml, config.py, output.py, cli.py exit-code conventions across the repo;
checked PR against projects/forge/WEIGHT-SOURCES.md (commit b1079ac46, dual weight-source
requirement for rented compute).

Verdict: mergeable-after-nits. Design and scope discipline are good (preflight-only, no spend,
no network I/O, job submission explicitly deferred — this correctly respects the dual
weight-source requirement by not building job submission at all yet). One real, currently
reproducing test bug needs a fix before merge; one design gap (Azure readiness signal) is worth
a follow-up issue, not a blocker.

Findings, ranked

1. (Medium-High, currently reproducing) Credential path-check isn't test-isolated — a real ~/.modal.toml on the machine makes test_preflight_reports_missing_credentials_by_name fail locally although CI is green

src/forge/providers/base.py:291-296:

def satisfied_by(self, env):
    if env.get(self.env):
        return "env"
    if self.path and Path(self.path).expanduser().exists():
        return "file"
    return None

Credential.satisfied_by takes an env dict for the env-var check but the file-existence check
always reads the real filesystem (Path(self.path).expanduser() — i.e. real $HOME), regardless
of what env was passed to preflight(). tests/test_cloud.py:26-31 calls
providers.get("modal").preflight(env={}) and asserts ok is False, expecting env={} to mean
"nothing satisfies this provider." That's not what happens if the machine running the tests
happens to have ~/.modal.toml on disk.

Reproduced: checked out pr6 in the actual forge checkout and ran the test suite —

FAILED tests/test_cloud.py::test_preflight_reports_missing_credentials_by_name
assert True is False

~/.modal.toml exists on this machine (dated 2026-08-07 14:39, this is Colby's real Modal token
file — worth confirming that's expected/current, raw observation only). GitHub Actions' ubuntu
runner has no such file, so CI is green while local pytest fails — the PR's "114 tests pass"
claim held in CI/on the author's clean environment but does not hold on a dev machine with a real
credential file already in place, which given the whole point of this layer (rented-compute
credentials arriving) is exactly the environment forge devs will be running in soon.

Fix: isolate the filesystem in the affected tests — e.g. monkeypatch.setenv("HOME", str(tmp_path)) (or monkeypatch.setattr(Path, "home", ...)/patch os.path.expanduser) so
~/.modal.toml and ~/.azure resolve under an empty tmp dir regardless of the host's real
dotfiles. Affects test_preflight_reports_missing_credentials_by_name and
test_human_preflight_marks_missing_lines (same class of leak for Azure's ~/.azure) at minimum.

2. (Medium, design gap — flag for follow-up, not necessarily this PR) Azure readiness is checked as "does ~/.azure exist," which is a much weaker signal than the Modal token-file check

src/forge/providers/azure_provider.py:217,227-231:

AZURE_CONFIG = "~/.azure"
...
Credential("AZURE_SUBSCRIPTION_ID", "...", path=AZURE_CONFIG),
Credential("AZURE_TENANT_ID", "...", path=AZURE_CONFIG),

The docstring says "az login writes ~/.azure/, which satisfies the credential without env
vars" — but the Azure CLI is documented to create the ~/.azure directory (config, telemetry
cache, etc.) on first invocation of any az command, not only after a successful az login.
If that's accurate, az --version alone (something bootstrap/CI tooling might run) would flip
ok to True with no real subscription access behind it — the opposite failure mode from
Modal's check (Modal's ~/.modal.toml is written specifically by modal token new, so it's a
much stronger signal). This is exactly the "honesty" property the module's docstring and PR body
claim ("a red report is information... not a failed command" / preflight should be trustworthy) —
worth verifying against a real az install before relying on it, and if confirmed, checking a
login-specific artifact (e.g. ~/.azure/azureProfile.json) instead of the bare directory.
Not blocking since Azure isn't wired to spend anything yet, but flag it now since it's the kind
of thing that's easy to forget once credentials actually land.

3. (Low, consistency nit) cmd_providers's human-format loop duplicates a variant of _human_preflight's row formatting

src/forge/cloud.py:104-120 (_human_preflight) and src/forge/cloud.py:123-130
(cmd_providers) both build f"{provider:8} ..." header lines and a gpus line independently
with slightly different formatting (gpus vs gpus ). Not a bug, but two near-duplicate
formatters for the same data will drift. Minor; could factor a shared _row_header(r) helper if
touched again.

4. (Informational, not a finding) forge cloud preflight exits 0 even when nothing is ready

Confirmed intentional and well-documented (module docstring, inline comment at
src/forge/cloud.py:147-149, PR body, and a dedicated test
test_preflight_exits_zero_even_when_not_ready). Reasonable given the JSON-first contract
(emit's ok field is the real signal) — just flagging since "failure modes that exit 0" was
explicitly in scope for this review. Any future shell wrapper around this command must check the
JSON ok field, not the process exit code.

Dual weight-source requirement (commit b1079ac46 / WEIGHT-SOURCES.md)

Respected. This PR ships preflight only — no job submission, no weight-fetch code — which is
exactly what WEIGHT-SOURCES.md says should exist today ("Status: requirement captured
2026-08-07, nothing built. The forge cloud layer today is preflight-only"). No --weights
resolver appears here, so there's nothing yet that could bake in a single weight source. Fine to
merge without addressing the dual-source design — it's out of scope by the PR's own stated
boundary, and that boundary is honest.

Other categories checked, no issues found

  • Error handling on cloud API calls: N/A — this PR makes no network/API calls anywhere
    (verified: no requests/subprocess calls to modal/az in the diff; only shutil.which
    and Path.exists). Deliberately scoped that way per the module docstring.
  • Credential handling / no secrets in logs: clean. Credential.describe() and
    _human_preflight only ever emit variable names, descriptions, and source (env/file),
    never values. Confirmed by reading base.py:298-303 and cloud.py:104-120.
  • Idempotency: preflight is read-only and stateless; same inputs (env + filesystem) always
    produce the same result (modulo Finding 1's test isolation issue).
  • render_tool_cmd / model_path templating (train/common.py:37-58): reasonable, tested
    from multiple angles (relative-path-under-model_root, HF-repo-id passthrough, absolute-path
    passthrough); the null-tool_cmd refusal contract in abliterate.py/reap.py is preserved
    and re-tested (test_null_tool_cmd_still_refuses).
  • Recipe validity: recipes/abliterate-abliterix.yaml satisfies recipes.py's REQUIRED
    keys and METHODS/profile checks (cuda-datacenter exists in envs/profiles.yaml); test
    test_abliterix_recipe_is_valid_and_wired exercises load_recipe against it directly.
  • bootstrap/fleet-node.sh: new abliterix install block follows the existing file's own
    pattern exactly (|| echo "WARN: ...", no set -e trip), consistent with the llama.cpp block
    immediately above it.

…hen Azure readiness check

Review finding 1: satisfied_by() checks Path(self.path).expanduser() against
the real filesystem regardless of the env dict passed to preflight(), so
test_preflight_reports_missing_credentials_by_name and
test_human_preflight_marks_missing_lines silently depended on the dev
machine's real ~/.modal.toml / ~/.azure being absent. Reproduced locally
(real ~/.modal.toml on this machine flipped the Modal test's assertion).
Fixed by monkeypatching HOME to an empty tmp_path in both tests.

Review finding 2: Azure's readiness check treated the mere existence of
~/.azure/ as a login signal, but the Azure CLI creates that directory
(config, telemetry cache) on the first invocation of ANY az command, not
just az login -- so e.g. bootstrap tooling running az --version would flip
ok to True with no real subscription access behind it. Point the credential
at ~/.azure/azureProfile.json instead, which az login writes specifically,
and add tests pinning both the negative (bare dir alone) and positive
(profile file present) cases.
@colbyt
colbyt merged commit dbcf34f into main Aug 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant