Ligant's picture
download
raw
9.01 kB
#!/usr/bin/env python3
"""Render short HF Batch workers. No submission unless --execute is supplied.
``--code-volume auto`` reads the run's persisted state and mounts the release it
is actually pinned to, instead of trusting an operator to remember. Getting this
wrong fails every tick within seconds, and did so three times before this existed
(docs/release-process.md R-05, appendix B-1).
"""
import argparse
import json
import re
import shlex
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent
CODE_BUCKET = 'hf://buckets/Ligant/castle-code'
DEFAULT_CREDENTIALS = REPO / 'credentials' / 'my-project-omni-507802-9a6505abf0f7.json'
def load_credentials(path: Path):
"""Accept a service-account JSON or a KEY=<json> secrets file."""
raw = Path(path).read_text(encoding='utf-8').strip()
if not raw.startswith('{'):
_, _, raw = raw.partition('=')
raw = raw.strip()
info = json.loads(raw)
from google.oauth2 import service_account
return service_account.Credentials.from_service_account_info(
info, scopes=['https://www.googleapis.com/auth/cloud-platform'])
def known_releases() -> list[str]:
return sorted(p.stem for p in (REPO / 'releases').glob('*.json'))
def resolve_release(state_uri: str, credentials_path: Path) -> dict:
"""Read the run's pin from GCS state and map its code_hash to a release name.
The state records ``code_hash``; the release name comes from the manifests in
``releases/``. This is the pre-R-04 path: once state also carries ``release``
this lookup becomes a verification rather than a resolution.
"""
from google.cloud import storage
from castle_pipeline.batch_cloud import parse_gs_uri, BatchCloud
credentials = load_credentials(credentials_path)
cloud = BatchCloud(_project_of(state_uri, credentials_path), 'global', credentials=credentials)
state, _ = cloud.read_state(state_uri)
if state is None:
raise SystemExit(f'No batch state at {state_uri}; nothing is pinned yet.')
config = state.get('config') or {}
recorded = config.get('release')
stored = config.get('code_hash')
releases = {}
for path in sorted((REPO / 'releases').glob('*.json')):
manifest = json.loads(path.read_text(encoding='utf-8'))
releases[manifest['code_hash']] = manifest['release']
resolved = recorded or releases.get(stored)
if not resolved:
raise SystemExit(
'E_CODE_VERSION_UNKNOWN\n'
f' run code_hash : {stored}\n'
' known : ' + ', '.join(f'{v} ({k[:8]})' for k, v in releases.items()) + '\n'
' fix : add the manifest via release.py, or pass an explicit --code-volume')
return {'release': resolved, 'code_hash': stored,
'code_volume': f'{CODE_BUCKET}/{resolved}',
'stage': state.get('current_stage'), 'status': state.get('status')}
def _project_of(state_uri: str, credentials_path: Path) -> str:
raw = Path(credentials_path).read_text(encoding='utf-8').strip()
if not raw.startswith('{'):
_, _, raw = raw.partition('=')
return json.loads(raw).get('project_id') or ''
def build_command(*, role, code_volume, output_volume, name, project, state_uri,
pipeline_args, credential_secret, image='python:3.12-slim',
flavor='cpu-basic', timeout=None, location='global'):
if role not in {'submit','hourly-tick','tick'}:
raise ValueError('Unknown worker role')
for value in (code_volume,output_volume):
if not value.startswith('hf://buckets/') or any(c in value[5:] for c in ':\r\n') or '..' in value.split('/'):
raise ValueError('Use explicit HF bucket prefixes')
if code_volume.rstrip('/')==output_volume.rstrip('/'):
raise ValueError('Code and output must be distinct')
if not re.fullmatch(r'[A-Z][A-Z0-9_]*',credential_secret):
raise ValueError('Supply the environment variable name containing ADC JSON, never its value')
if not project or not state_uri.startswith('gs://'):
raise ValueError('Explicit Google project and GCS state URI are required')
managed={'--project','--location','--state-uri','--output-dir','--scratch-dir','--submit'}
if any(arg.split('=')[0] in managed for arg in pipeline_args):
raise ValueError('Launcher owns project/location/state/output/scratch/submit flags')
if role!='submit' and pipeline_args:
raise ValueError('Tick scope comes entirely from persistent state')
cmd=['hf','jobs']
if role=='hourly-tick':
cmd+=['scheduled','run','hourly','--no-concurrency']
else:
cmd+=['run','--detach']
cmd+=['--flavor',flavor,'--timeout',timeout or ('3h' if role=='submit' else '30m'),
'--name',name,'--volume',code_volume.rstrip('/')+':/workspace:ro',
'--volume',output_volume.rstrip('/')+':/output',
'--secrets',credential_secret,'--env','CASTLE_ADC_SECRET_NAME='+credential_secret,
'--env','GOOGLE_APPLICATION_CREDENTIALS=/scratch/google-adc.json',
'--env','PYTHONUNBUFFERED=1','--env','PYTHONDONTWRITEBYTECODE=1',
'--env','HF_HOME=/scratch/hf','--env','HF_XET_CHUNK_CACHE_SIZE_BYTES=0',
'--env','HF_XET_HIGH_PERFORMANCE=0','--env','OMP_NUM_THREADS=1']
runtime=['python','/workspace/batch_pipeline.py','prepare' if role=='submit' else 'tick',
'--project',project,'--location',location,'--state-uri',state_uri,
'--output-dir','/output','--scratch-dir','/scratch/castle',*pipeline_args]
if role=='submit':runtime+=['--submit']
install_media=('if ! command -v ffmpeg >/dev/null || ! command -v ffprobe >/dev/null; then '
'apt-get update && apt-get install -y --no-install-recommends ffmpeg; fi; ') if role=='submit' else ''
bootstrap=('set -eu; mkdir -p /scratch/castle; '+install_media+
'python -m pip install --no-cache-dir -r /workspace/requirements-batch.txt; '
'exec python /workspace/bootstrap_batch.py '+shlex.join(runtime))
return cmd+['--',image,'bash','-lc',bootstrap]
def main(argv=None):
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('role',choices=['submit','hourly-tick','tick'])
for field in ['code-volume','output-volume','name','project','state-uri']:
p.add_argument('--'+field,required=True)
p.add_argument('--credential-secret',default='GOOGLE_ADC_JSON')
p.add_argument('--credentials-file',type=Path,default=DEFAULT_CREDENTIALS,
help='Local ADC used only to resolve --code-volume auto; never passed to the Job')
p.add_argument('--location',default='global')
p.add_argument('--image',default='python:3.12-slim')
p.add_argument('--flavor',default='cpu-basic')
p.add_argument('--timeout')
p.add_argument('--execute',action='store_true')
argv=list(sys.argv[1:] if argv is None else argv)
split=argv.index('--') if '--' in argv else len(argv)
args=p.parse_args(argv[:split]);forwarded=argv[split+1:]
values=vars(args).copy();execute=values.pop('execute');values.pop('credentials_file')
values['pipeline_args']=forwarded
resolved={'release':None,'code_hash':None,'stage':None,'status':None}
if args.code_volume=='auto':
if args.role=='submit':
raise SystemExit('--code-volume auto needs an existing state, so it cannot be used for the '
'first submit; name the release explicitly.')
resolved=resolve_release(args.state_uri,args.credentials_file)
values['code_volume']=resolved['code_volume']
elif args.code_volume.startswith('hf://buckets/'):
# Fail closed when the operator names a release that disagrees with the pin.
release=args.code_volume.rstrip('/').rsplit('/',1)[-1]
if release in known_releases():
pinned=resolve_release(args.state_uri,args.credentials_file)
if pinned['release']!=release:
raise SystemExit(
'E_CODE_VERSION_MISMATCH\n'
f" run pinned to : {pinned['release']} (code_hash {str(pinned['code_hash'])[:8]})\n"
f" requested : {release}\n"
f" fix : --code-volume auto\n"
f" or --code-volume {pinned['code_volume']}")
resolved=pinned
command=build_command(**values)
if args.role=='submit':
from batch_pipeline import build_parser
build_parser().parse_args(['prepare',*forwarded,'--project',args.project,'--location',args.location,
'--state-uri',args.state_uri,'--output-dir','/output','--scratch-dir','/scratch/castle'])
if not execute:
print(json.dumps({'submitted':False,'resolved':resolved,'argv':command},indent=2));return 0
return subprocess.run(command,check=False).returncode
if __name__=='__main__':
raise SystemExit(main())

Xet Storage Details

Size:
9.01 kB
·
Xet hash:
f8135daf42aff3d1817105d5ace31bc78f5acfe6466f5d914e8719ba26f6093d

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.