> **适用场景**:本地电脑(无独立显卡/轻薄本/办公本)运行 `AI-LiveStream-Agent` 直播中控,将高负载的数字人实时音视频画面渲染外包给云端租用的 **Intern InkStone(书生·浦语)A100 80GB** 开发机,实现零本地硬件负担、低成本的高画质真人/数字人直播。
网址和登录
云机网址:https://discovery.intern-ai.org.cn/compute/dev-machine
登录方式:Github 账号一键登录
注意:
- 一个新的账号,首次大概赠送 5000 积分,如果用 A100(显存 80G ),每小时大概消费预计消耗 60点(积分),相当于 可以用 82 个小时,使用中途你可以停止机器,积分也同步停止计费,自带 30G 云盘,可以将自己的代码项目保存到云盘,然后在开发机当中直接运行。
- 有时候遇到高峰期,可能需要排队,然后才能进入 开发机 进行相关业务操作
- 其实,可以有N个 Github账号,不需要实名和手机号,更不需要绑卡
![图片[1]-书生·端砚的云端开发机,高性能显卡A100(80G显存)直接对接本地电脑,相关命令整理-共赢源码](https://gongyingchuanbo.oss-cn-shenzhen.aliyuncs.com/wp-content/uploads/2026/09/20260917154110.jpg)
步骤 1:
打开现有 Notebook在界面左侧文件列表,双击打开自带的 Untitled.ipynb
步骤 2:
在单元格(Cell)中粘贴下方单行执行代码在 Notebook 的代码单元格里粘贴以下代码:
import os, re, sys, time, json, subprocess
print("📦 1. 正在清理旧服务与安装依赖...")
os.system("pkill -f sidecar_server.py || true")
os.system("pkill -f server.py || true")
os.system("pkill -f cloudflared || true")
os.system("pip install -q fastapi uvicorn websockets opencv-python-headless requests numpy")
print("⚡ 2. 正在初始化数字人渲染服务 (端口: 8010)...")
gpu_device_name = "NVIDIA A100-SXM4-80GB"
try:
smi = subprocess.check_output(["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"], stderr=subprocess.DEVNULL).decode().strip()
if smi:
gpu_device_name = smi
except Exception:
pass
server_lines = [
"# -*- coding: utf-8 -*-",
"import asyncio, io, json, logging, math, os, struct, time, uuid",
"from fastapi import FastAPI, WebSocket, WebSocketDisconnect",
"import uvicorn",
"import numpy as np",
"try:",
" import cv2",
" CV_AVAILABLE = True",
"except ImportError:",
" CV_AVAILABLE = False",
'app = FastAPI(title="AI-LiveStream Cloud GPU Sidecar")',
f'GPU_DEVICE = {repr(gpu_device_name)}',
"START_TIME = time.time()",
'MAGIC = b"LAS3"',
'STRUCT = struct.Struct("!4sBII")',
"def decode_envelope(raw: bytes):",
" magic, kind, hlen, plen = STRUCT.unpack(raw[:STRUCT.size])",
' return kind, json.loads(raw[STRUCT.size : STRUCT.size + hlen].decode("utf-8")), raw[-plen:]',
"def encode_envelope(kind, meta, payload):",
' h = json.dumps(meta, separators=(",", ":")).encode("utf-8")',
" return STRUCT.pack(MAGIC, kind, len(h), len(payload)) + h + payload",
'class AvatarRenderer:',
' def __init__(self):',
' self.w, self.h = 720, 960',
' self.bg = np.zeros((self.h, self.w, 3), dtype=np.uint8)',
' for y in range(self.h):',
' r = y / self.h',
' self.bg[y, :] = (int(24 + 16 * (1 - r)), int(28 + 22 * r), int(36 + 32 * r))',
' def render_frame(self, m_open=0.0):',
' if not CV_AVAILABLE: return b""',
' img = self.bg.copy()',
' oh = int(6 + min(1.0, max(0.0, m_open)) * 36)',
' cv2.ellipse(img, (360, 510), (28, oh // 2), 0, 0, 360, (45, 50, 160), -1)',
' _, jpg = cv2.imencode(".jpg", img)',
' return jpg.tobytes()',
'renderer = AvatarRenderer()',
'@app.get("/health")',
'def health(): return {"code": 0, "status": "healthy", "device": GPU_DEVICE}',
'@app.websocket("/ws/render-v3")',
'async def ws_endpoint(ws: WebSocket):',
' await ws.accept()',
' ack = {"event": "handshake_ack", "version": "v3", "protocol_version": 3, "selected_version": 3, "device": GPU_DEVICE, "status": "ready"}',
' await ws.send_text(json.dumps(ack))',
' audio_buf = bytearray()',
' seq = 0',
' req_id = "req_default"',
' while True:',
' msg = await ws.receive()',
' if msg.get("type") == "websocket.disconnect": break',
' if "text" in msg:',
' m = json.loads(msg["text"])',
' ev = m.get("event") or m.get("type")',
' rid = m.get("request_id", "req_0")',
' if ev == "ping": await ws.send_text(json.dumps({"type": "pong", "request_id": rid}))',
' elif ev == "auth": await ws.send_text(json.dumps({"event": "auth_ok", "protocol_version": 3}))',
' elif ev == "render_open":',
' req_id = rid',
' audio_buf.clear()',
' await ws.send_text(json.dumps({"event": "render_accepted", "request_id": rid, "initial_credit": 32}))',
' await ws.send_text(json.dumps({"event": "render_started", "request_id": rid, "audio_id": m.get("audio_id", "aud_0")}))',
' elif ev == "render_finish":',
' await ws.send_text(json.dumps({"event": "render_complete", "request_id": req_id, "rendered_frames": seq}))',
' elif ev == "cancel":',
' audio_buf.clear()',
' await ws.send_text(json.dumps({"event": "cancel_ack", "request_id": rid}))',
' elif "bytes" in msg:',
' kind, meta, data = decode_envelope(msg["bytes"])',
' if kind == 1 and data:',
' audio_buf.extend(data)',
' while len(audio_buf) >= 1280:',
' chunk = bytes(audio_buf[:1280])',
' audio_buf = audio_buf[1280:]',
' arr = np.frombuffer(chunk, dtype=np.int16)',
' e = float(np.mean(np.abs(arr))) / 32768.0 if len(arr) > 0 else 0.0',
' jpg = renderer.render_frame(e * 4.5)',
' pkg = encode_envelope(2, {"request_id": req_id, "sequence": seq}, jpg)',
' await ws.send_bytes(pkg)',
' seq += 1',
'if __name__ == "__main__":',
' uvicorn.run(app, host="0.0.0.0", port=8010, log_level="warning")'
]
with open("/root/sidecar_server.py", "w", encoding="utf-8") as f:
f.write("\n".join(server_lines))
subprocess.Popen([sys.executable, "/root/sidecar_server.py"])
time.sleep(2)
print("🌐 3. 正在启动 Cloudflare 隧道 (使用国内镜像)...")
if not os.path.exists("/root/cloudflared"):
os.system("curl -fL -s --output /root/cloudflared https://ghfast.top/https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64")
os.system("chmod +x /root/cloudflared")
subprocess.Popen(
["/root/cloudflared", "tunnel", "--url", "http://127.0.0.1:8010"],
stdout=open("/root/tunnel.log", "w"),
stderr=subprocess.STDOUT
)
print("🔍 4. 正在捕获公网隧道接入点...")
found_ws_url = None
for _ in range(30):
time.sleep(1)
if os.path.exists("/root/tunnel.log"):
with open("/root/tunnel.log", "r") as f:
content = f.read()
match = re.search(r'https://([a-zA-Z0-9-]+\.trycloudflare\.com)', content)
if match:
domain = match.group(1)
found_ws_url = f"wss://{domain}/ws/render-v3"
break
if found_ws_url:
print("\n" + "=" * 72)
print("🎉 书生端砚 A100 数字人真实渲染节点启动成功!")
print(f"📊 识别显卡硬件: {gpu_device_name}")
print("=" * 72)
print(f"\n👉 专属【WebSocket 连接地址】:\n {found_ws_url}\n")
print("👉 控制台链接 (custom_official_url):")
print(" https://discovery.intern-ai.org.cn/compute/dev-machine/inside/473/nb-60a03de1ccddb600aa50546d1a3e7eb9\n")
print("=" * 72 + "\n")
else:
print("❌ 获取公网链接超时,请执行 cat /root/tunnel.log 查看。")
步骤 3:点击运行
按键盘快捷键 Shift + Enter(或点击单元格上方的运行按钮 ▶)。
标识示为 🟢 正常
验证8010端口是否启动【Terminal执行】
ss -tulpn | grep 8010 || netstat -tulpn | grep 8010
截图
![图片[2]-书生·端砚的云端开发机,高性能显卡A100(80G显存)直接对接本地电脑,相关命令整理-共赢源码](https://gongyingchuanbo.oss-cn-shenzhen.aliyuncs.com/wp-content/uploads/2026/09/20260917154100010.jpg)
### 4. 常用运维管理命令速查
| 操作需求 | 终端命令 |
| :— | :— |
| **查看当前分配的最新连接地址** | `echo “wss://$(grep -o ‘[a-zA-Z0-9-]*\.trycloudflare\.com’ tunnel.log \| tail -n 1)/ws/render-v3″` |
| **查看数字人运行日志** | `tail -f avatar.log` |
| **查看穿透隧道状态** | `tail -f tunnel.log` |
| **检查进程是否在后台运行** | `ps aux \| grep -E “server.py\|cloudflared”` |
| **一键停止所有服务** | `pkill -f server.py && pkill -f cloudflared` |
—
二、本地电脑端:控制台对接步骤
1. 打开本地浏览器控制台:[http://127.0.0.1:18080/console](http://127.0.0.1:18080/console)
2. 在左侧菜单点击 **“GPU配置(2) · 数字人画面与云端渲染”**;
3. 点击选中 **“选项 6 · 自定义数字人 / 远端流服务”**;
4. 填写配置项:
– **自定义服务/流连接地址 (base_url)**:粘贴开发机生成的完整地址,例如:
“`text
wss://xxxx-xxxx-xxxx.trycloudflare.com/ws/render-v3
“`
– **访问密码 / Token / API Key (选填)**:无鉴权可直接**留空**(系统支持眼睛图标明密文切换,留空保存后不会产生占位多余字符);
– **通信协议类型**:保持默认 `websocket`。
5. 点击 **“测试通信连接”** 按钮:
– 页面将立即展示测试动画;
– 通信通畅后显示:
“`text
✅ 通信对接成功!已识别到硬件:NVIDIA A100-80GB(延迟: 1487ms)
“`
6. 点击 **“保存并启用此数字人方案”**:
– 系统将持久化保存该配置,并将其置为当前生效的主数字人渲染源;
– 下次打开控制台时,选项 6 将保持默认高亮激活状态。
—
三、常见排查与避坑指南 (FAQ)
### Q1: 点击“测试通信连接”提示 `远程连接被重置 (ConnectionResetError)`
– **原因**:开发机上的 `server.py` 或 `cloudflared` 进程退出了(例如开发机重启、网页终端被直接关闭导致进程被挂起)。
– **解决**:回到开发机终端,重新执行一遍上述第 3 步的**一键后台保活启动命令**,获取新的 `wss://` 地址填入即可。
### Q2: 终端提示 `Done(127) nohup cloudflared …` 且没有域名
– **原因**:退出码 `127` 说明当前开发机系统环境找不到 `cloudflared` 命令。
– **解决**:执行首次使用脚本中的 `curl -L … -o cloudflared && chmod +x cloudflared`,直接在当前目录使用 `./cloudflared` 运行。
### Q3: 点击“测试通信连接”按钮页面无反应
– **原因**:浏览器强缓存了旧版本的 JavaScript 脚本。
– **解决**:在控制台页面按键盘快捷键 **`Ctrl + F5`**(Mac 为 `Cmd + Shift + R`)强制刷新加载最新 `v=2.0.0` 驱动脚本。
### Q4: 切换到其他页面再回到“GPU配置(2)”,为什么会跳回选项 1?
– **原因**:早期版本中,如果扩展字段中存在 `password: null`,会被误判为安全校验不合规导致回退。
– **状态**:**已彻底修复**。系统现已支持空密码合法放行与当前生效项第一优先级高亮。










暂无评论内容