Spaces:
Running
Running
Move Nemotron text generation to WebGPU
Browse files- app.py +0 -12
- web/engineServer.js +0 -1
- web/engineTransformers.js +18 -4
app.py
CHANGED
|
@@ -265,7 +265,6 @@ TTS_MODE = os.environ.get("TINY_TTS_MODE", "").strip().lower()
|
|
| 265 |
VOXCPM_SPACE = os.environ.get("TINY_VOXCPM_SPACE", "").strip()
|
| 266 |
TINY_AYA_SPACE = os.environ.get("TINY_AYA_SPACE", "").strip()
|
| 267 |
MINICPM5_SPACE = os.environ.get("TINY_MINICPM5_SPACE", "").strip()
|
| 268 |
-
NEMOTRON_SPACE = os.environ.get("TINY_NEMOTRON_SPACE", "").strip()
|
| 269 |
_local_tts = None # VoiceDesign model
|
| 270 |
_local_clone = None # Base model (voice clone) — lazy, only if a clone is requested
|
| 271 |
_local_tts_lock = threading.Lock()
|
|
@@ -514,10 +513,6 @@ def _minicpm5_stream(system, user, max_tokens, temperature):
|
|
| 514 |
yield from _space_text_stream(MINICPM5_SPACE, system, user, max_tokens, temperature)
|
| 515 |
|
| 516 |
|
| 517 |
-
def _nemotron_stream(system, user, max_tokens, temperature):
|
| 518 |
-
yield from _space_text_stream(NEMOTRON_SPACE, system, user, max_tokens, temperature)
|
| 519 |
-
|
| 520 |
-
|
| 521 |
@fastapi_app.post("/voxcpm-tts")
|
| 522 |
async def voxcpm_tts(request: Request):
|
| 523 |
body = await request.json()
|
|
@@ -824,13 +819,6 @@ async def text_generate_stream(request: Request):
|
|
| 824 |
if stop.is_set():
|
| 825 |
break
|
| 826 |
loop.call_soon_threadsafe(q.put_nowait, ("delta", chunk))
|
| 827 |
-
elif model == "nemotron-3-nano-4b-zerogpu":
|
| 828 |
-
if not NEMOTRON_SPACE:
|
| 829 |
-
raise llm.LlmUnavailable("TINY_NEMOTRON_SPACE not set")
|
| 830 |
-
for chunk in _nemotron_stream(system, user, max_tokens, temperature):
|
| 831 |
-
if stop.is_set():
|
| 832 |
-
break
|
| 833 |
-
loop.call_soon_threadsafe(q.put_nowait, ("delta", chunk))
|
| 834 |
else:
|
| 835 |
for chunk in llm.stream_chat(
|
| 836 |
system,
|
|
|
|
| 265 |
VOXCPM_SPACE = os.environ.get("TINY_VOXCPM_SPACE", "").strip()
|
| 266 |
TINY_AYA_SPACE = os.environ.get("TINY_AYA_SPACE", "").strip()
|
| 267 |
MINICPM5_SPACE = os.environ.get("TINY_MINICPM5_SPACE", "").strip()
|
|
|
|
| 268 |
_local_tts = None # VoiceDesign model
|
| 269 |
_local_clone = None # Base model (voice clone) — lazy, only if a clone is requested
|
| 270 |
_local_tts_lock = threading.Lock()
|
|
|
|
| 513 |
yield from _space_text_stream(MINICPM5_SPACE, system, user, max_tokens, temperature)
|
| 514 |
|
| 515 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
@fastapi_app.post("/voxcpm-tts")
|
| 517 |
async def voxcpm_tts(request: Request):
|
| 518 |
body = await request.json()
|
|
|
|
| 819 |
if stop.is_set():
|
| 820 |
break
|
| 821 |
loop.call_soon_threadsafe(q.put_nowait, ("delta", chunk))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 822 |
else:
|
| 823 |
for chunk in llm.stream_chat(
|
| 824 |
system,
|
web/engineServer.js
CHANGED
|
@@ -7,7 +7,6 @@ const MODELS = [
|
|
| 7 |
{ id: 'server-local', label: 'Configured server model', params: 'local/remote', note: 'uses TINY_LLM_* on the Space or local app' },
|
| 8 |
{ id: 'tiny-aya-global-zerogpu', label: 'Tiny Aya Global 3.35B', params: '3.35B', note: 'ZeroGPU sidecar; multilingual' },
|
| 9 |
{ id: 'minicpm5-1b-zerogpu', label: 'MiniCPM5 1B', params: '1B', note: 'ZeroGPU sidecar; efficient MiniCPM5 text model' },
|
| 10 |
-
{ id: 'nemotron-3-nano-4b-zerogpu', label: 'Nemotron 3 Nano 4B', params: '4B', note: 'ZeroGPU sidecar; GGUF llama.cpp, slower startup' },
|
| 11 |
]
|
| 12 |
const get = (id) => MODELS.find((m) => m.id === id) || MODELS[0]
|
| 13 |
|
|
|
|
| 7 |
{ id: 'server-local', label: 'Configured server model', params: 'local/remote', note: 'uses TINY_LLM_* on the Space or local app' },
|
| 8 |
{ id: 'tiny-aya-global-zerogpu', label: 'Tiny Aya Global 3.35B', params: '3.35B', note: 'ZeroGPU sidecar; multilingual' },
|
| 9 |
{ id: 'minicpm5-1b-zerogpu', label: 'MiniCPM5 1B', params: '1B', note: 'ZeroGPU sidecar; efficient MiniCPM5 text model' },
|
|
|
|
| 10 |
]
|
| 11 |
const get = (id) => MODELS.find((m) => m.id === id) || MODELS[0]
|
| 12 |
|
web/engineTransformers.js
CHANGED
|
@@ -6,6 +6,7 @@ const MODELS = [
|
|
| 6 |
{ id: 'qwen2.5-0.5b', label: 'Qwen2.5 0.5B', params: '0.5B', repo: 'onnx-community/Qwen2.5-0.5B-Instruct' },
|
| 7 |
{ id: 'smollm2-360m', label: 'SmolLM2 360M', params: '360M', repo: 'HuggingFaceTB/SmolLM2-360M-Instruct' },
|
| 8 |
{ id: 'llama3.2-1b', label: 'Llama 3.2 1B', params: '1B', repo: 'onnx-community/Llama-3.2-1B-Instruct' },
|
|
|
|
| 9 |
]
|
| 10 |
const get = (id) => MODELS.find((m) => m.id === id) || MODELS[0]
|
| 11 |
|
|
@@ -26,7 +27,7 @@ async function pickDevice() {
|
|
| 26 |
}
|
| 27 |
|
| 28 |
let _lib = null, _pipe = null, _loadedId = null, _loadingId = null, _loadPromise = null, _device = 'wasm', _chain = Promise.resolve()
|
| 29 |
-
async function lib() { if (!_lib) _lib = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@
|
| 30 |
|
| 31 |
async function ensure(id, onProgress) {
|
| 32 |
const m = get(id)
|
|
@@ -39,13 +40,14 @@ async function ensure(id, onProgress) {
|
|
| 39 |
_loadPromise = (async () => {
|
| 40 |
const { pipeline } = await lib()
|
| 41 |
_device = await pickDevice()
|
|
|
|
| 42 |
const mk = (device) => pipeline('text-generation', m.repo, {
|
| 43 |
device, dtype: 'q4',
|
| 44 |
progress_callback: (p) => { if (onProgress && p.status === 'progress' && p.total) onProgress(p.loaded / p.total) },
|
| 45 |
})
|
| 46 |
let pipe
|
| 47 |
try { pipe = await mk(_device) }
|
| 48 |
-
catch (e) { if (_device !== 'wasm') { _device = 'wasm'; pipe = await mk('wasm') } else throw e }
|
| 49 |
_pipe = pipe; _loadedId = m.id; return pipe
|
| 50 |
})().catch((e) => { _loadPromise = null; _loadingId = null; throw e })
|
| 51 |
return _loadPromise
|
|
@@ -62,8 +64,20 @@ function stream(id, system, user, { maxTokens = 200, temperature = 0.8, onToken,
|
|
| 62 |
callback_function: (text) => { if (!text) return; full += text; if (onToken) onToken(text); st.tick() },
|
| 63 |
})
|
| 64 |
const messages = [{ role: 'system', content: system }, { role: 'user', content: user }]
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
return { text: full, stats: st.finish() }
|
| 68 |
}
|
| 69 |
const p = _chain.then(run, run); _chain = p.catch(() => {}); return p
|
|
|
|
| 6 |
{ id: 'qwen2.5-0.5b', label: 'Qwen2.5 0.5B', params: '0.5B', repo: 'onnx-community/Qwen2.5-0.5B-Instruct' },
|
| 7 |
{ id: 'smollm2-360m', label: 'SmolLM2 360M', params: '360M', repo: 'HuggingFaceTB/SmolLM2-360M-Instruct' },
|
| 8 |
{ id: 'llama3.2-1b', label: 'Llama 3.2 1B', params: '1B', repo: 'onnx-community/Llama-3.2-1B-Instruct' },
|
| 9 |
+
{ id: 'nemotron-3-nano-4b', label: 'Nemotron 3 Nano 4B', params: '4B', repo: 'onnx-community/NVIDIA-Nemotron-3-Nano-4B-BF16-ONNX', webgpuOnly: true, note: 'WebGPU only; large browser download' },
|
| 10 |
]
|
| 11 |
const get = (id) => MODELS.find((m) => m.id === id) || MODELS[0]
|
| 12 |
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
let _lib = null, _pipe = null, _loadedId = null, _loadingId = null, _loadPromise = null, _device = 'wasm', _chain = Promise.resolve()
|
| 30 |
+
async function lib() { if (!_lib) _lib = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.0.0-next.8'); return _lib }
|
| 31 |
|
| 32 |
async function ensure(id, onProgress) {
|
| 33 |
const m = get(id)
|
|
|
|
| 40 |
_loadPromise = (async () => {
|
| 41 |
const { pipeline } = await lib()
|
| 42 |
_device = await pickDevice()
|
| 43 |
+
if (m.webgpuOnly && _device !== 'webgpu') throw new Error(`${m.label} requires WebGPU support in this browser.`)
|
| 44 |
const mk = (device) => pipeline('text-generation', m.repo, {
|
| 45 |
device, dtype: 'q4',
|
| 46 |
progress_callback: (p) => { if (onProgress && p.status === 'progress' && p.total) onProgress(p.loaded / p.total) },
|
| 47 |
})
|
| 48 |
let pipe
|
| 49 |
try { pipe = await mk(_device) }
|
| 50 |
+
catch (e) { if (!m.webgpuOnly && _device !== 'wasm') { _device = 'wasm'; pipe = await mk('wasm') } else throw e }
|
| 51 |
_pipe = pipe; _loadedId = m.id; return pipe
|
| 52 |
})().catch((e) => { _loadPromise = null; _loadingId = null; throw e })
|
| 53 |
return _loadPromise
|
|
|
|
| 64 |
callback_function: (text) => { if (!text) return; full += text; if (onToken) onToken(text); st.tick() },
|
| 65 |
})
|
| 66 |
const messages = [{ role: 'system', content: system }, { role: 'user', content: user }]
|
| 67 |
+
if (get(id).id === 'nemotron-3-nano-4b') {
|
| 68 |
+
await pipe(messages, {
|
| 69 |
+
max_new_tokens: maxTokens,
|
| 70 |
+
do_sample: true,
|
| 71 |
+
temperature,
|
| 72 |
+
top_k: 40,
|
| 73 |
+
top_p: 0.9,
|
| 74 |
+
streamer,
|
| 75 |
+
tokenizer_encode_kwargs: { enable_thinking: false },
|
| 76 |
+
})
|
| 77 |
+
} else {
|
| 78 |
+
const prompt = pipe.tokenizer.apply_chat_template(messages, { tokenize: false, add_generation_prompt: true })
|
| 79 |
+
await pipe(prompt, { max_new_tokens: maxTokens, do_sample: true, temperature, top_k: 40, top_p: 0.9, streamer })
|
| 80 |
+
}
|
| 81 |
return { text: full, stats: st.finish() }
|
| 82 |
}
|
| 83 |
const p = _chain.then(run, run); _chain = p.catch(() => {}); return p
|