ops: add isolated ASR deployment and safe GPU test handoff

This commit is contained in:
2026-10-08 12:42:01 +08:00
parent fea33285e8
commit 8bb4bd07ae
14 changed files with 530 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Download a pinned official ModelScope snapshot; never replace mismatched files."""
import hashlib
import json
import os
from pathlib import Path
import sys
import urllib.parse
import urllib.request
MODEL = 'Qwen/Qwen3-ASR-1.7B'
REVISION = 'a04930dbe5419bfee073f7cade734f572689a3a8'
ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else '/home/www/qwen-vllm/asr/models/Qwen3-ASR-1.7B')
API = f'https://modelscope.cn/api/v1/models/{MODEL}/repo'
def digest(path):
h = hashlib.sha256()
with path.open('rb') as stream:
for chunk in iter(lambda: stream.read(8 * 1024 * 1024), b''):
h.update(chunk)
return h.hexdigest()
ROOT.mkdir(parents=True, exist_ok=True)
url = API + '/files?' + urllib.parse.urlencode({'Revision': REVISION, 'Recursive': 'true'})
with urllib.request.urlopen(url, timeout=60) as response:
listing = json.load(response)
assert listing['Code'] == 200 and listing['Success'], listing
files = [x for x in listing['Data']['Files'] if x['Type'] == 'blob' and x['Path'] != '.gitattributes']
manifest = {'provider': 'ModelScope', 'model': MODEL, 'revision': REVISION, 'api': url, 'files': files}
manifest_path = ROOT / 'DOWNLOAD_MANIFEST.json'
if manifest_path.exists():
old = json.loads(manifest_path.read_text())
assert old == manifest, 'Existing manifest differs; preserve and abort'
else:
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n')
for row in files:
relative = Path(row['Path'])
assert not relative.is_absolute() and '..' not in relative.parts
path = ROOT / relative
if path.exists():
assert path.stat().st_size == row['Size'] and digest(path) == row['Sha256'], f'Existing file mismatch: {path}'
print(json.dumps({'event': 'download_reused', 'path': str(path), 'sha256': row['Sha256'], 'bytes': row['Size']}), flush=True)
continue
path.parent.mkdir(parents=True, exist_ok=True)
partial = path.with_name(path.name + '.partial')
# A partial file is only a disposable incomplete transfer created by this script.
target = API + '?' + urllib.parse.urlencode({'Revision': REVISION, 'FilePath': row['Path']})
print(json.dumps({'event': 'download_started', 'file': row['Path'], 'bytes': row['Size'], 'revision': REVISION}), flush=True)
h = hashlib.sha256()
size = 0
with urllib.request.urlopen(target, timeout=120) as source, partial.open('wb') as output:
while True:
chunk = source.read(8 * 1024 * 1024)
if not chunk:
break
output.write(chunk)
h.update(chunk)
size += len(chunk)
assert size == row['Size'] and h.hexdigest() == row['Sha256'], f'Download integrity mismatch: {path}'
os.replace(partial, path)
print(json.dumps({'event': 'download_verified', 'path': str(path), 'sha256': h.hexdigest(), 'bytes': size}), flush=True)
config = json.loads((ROOT / 'config.json').read_text())
assert config['architectures'] == ['Qwen3ASRForConditionalGeneration'], config['architectures']
weights = json.loads((ROOT / 'model.safetensors.index.json').read_text())
assert set(weights['weight_map'].values()) <= {x['Path'] for x in files}
# Public model weights must remain readable through a read-only bind mount with cap_drop=ALL.
# This does not change the private service .env or acceptance directory permissions.
ROOT.chmod(0o755)
manifest_path.chmod(0o644)
for row in files:
(ROOT / row['Path']).chmod(0o644)
print(json.dumps({'event': 'model_ready', 'model': MODEL, 'revision': REVISION, 'manifest_sha256': digest(manifest_path), 'total_bytes': sum(x['Size'] for x in files), 'architectures': config['architectures'], 'model_type': config.get('model_type'), 'dtype': config.get('torch_dtype', config.get('dtype')), 'weight_metadata': weights['metadata']}), flush=True)