lemonade-sdk 7.0.3__py3-none-any.whl → 8.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lemonade-sdk might be problematic. Click here for more details.

Files changed (55) hide show
  1. lemonade/api.py +3 -3
  2. lemonade/cli.py +11 -17
  3. lemonade/common/build.py +0 -47
  4. lemonade/common/network.py +50 -0
  5. lemonade/common/status.py +2 -21
  6. lemonade/common/system_info.py +19 -4
  7. lemonade/profilers/memory_tracker.py +3 -1
  8. lemonade/tools/accuracy.py +3 -4
  9. lemonade/tools/adapter.py +1 -2
  10. lemonade/tools/{huggingface_bench.py → huggingface/bench.py} +2 -87
  11. lemonade/tools/huggingface/load.py +235 -0
  12. lemonade/tools/{huggingface_load.py → huggingface/utils.py} +87 -255
  13. lemonade/tools/humaneval.py +9 -3
  14. lemonade/tools/{llamacpp_bench.py → llamacpp/bench.py} +1 -1
  15. lemonade/tools/{llamacpp.py → llamacpp/load.py} +18 -2
  16. lemonade/tools/mmlu.py +7 -15
  17. lemonade/tools/{ort_genai/oga.py → oga/load.py} +31 -422
  18. lemonade/tools/oga/utils.py +423 -0
  19. lemonade/tools/perplexity.py +4 -3
  20. lemonade/tools/prompt.py +2 -1
  21. lemonade/tools/quark/quark_load.py +2 -1
  22. lemonade/tools/quark/quark_quantize.py +5 -5
  23. lemonade/tools/report/table.py +3 -3
  24. lemonade/tools/server/llamacpp.py +159 -34
  25. lemonade/tools/server/serve.py +169 -147
  26. lemonade/tools/server/static/favicon.ico +0 -0
  27. lemonade/tools/server/static/styles.css +568 -0
  28. lemonade/tools/server/static/webapp.html +439 -0
  29. lemonade/tools/server/tray.py +458 -0
  30. lemonade/tools/server/{port_utils.py → utils/port.py} +22 -3
  31. lemonade/tools/server/utils/system_tray.py +395 -0
  32. lemonade/tools/server/{instructions.py → webapp.py} +4 -10
  33. lemonade/version.py +1 -1
  34. lemonade_install/install.py +46 -28
  35. {lemonade_sdk-7.0.3.dist-info → lemonade_sdk-8.0.0.dist-info}/METADATA +84 -22
  36. lemonade_sdk-8.0.0.dist-info/RECORD +70 -0
  37. lemonade_server/cli.py +182 -27
  38. lemonade_server/model_manager.py +192 -20
  39. lemonade_server/pydantic_models.py +9 -4
  40. lemonade_server/server_models.json +5 -3
  41. lemonade/common/analyze_model.py +0 -26
  42. lemonade/common/labels.py +0 -61
  43. lemonade/common/onnx_helpers.py +0 -176
  44. lemonade/common/plugins.py +0 -10
  45. lemonade/common/tensor_helpers.py +0 -83
  46. lemonade/tools/server/static/instructions.html +0 -262
  47. lemonade_sdk-7.0.3.dist-info/RECORD +0 -69
  48. /lemonade/tools/{ort_genai → oga}/__init__.py +0 -0
  49. /lemonade/tools/{ort_genai/oga_bench.py → oga/bench.py} +0 -0
  50. /lemonade/tools/server/{thread_utils.py → utils/thread.py} +0 -0
  51. {lemonade_sdk-7.0.3.dist-info → lemonade_sdk-8.0.0.dist-info}/WHEEL +0 -0
  52. {lemonade_sdk-7.0.3.dist-info → lemonade_sdk-8.0.0.dist-info}/entry_points.txt +0 -0
  53. {lemonade_sdk-7.0.3.dist-info → lemonade_sdk-8.0.0.dist-info}/licenses/LICENSE +0 -0
  54. {lemonade_sdk-7.0.3.dist-info → lemonade_sdk-8.0.0.dist-info}/licenses/NOTICE.md +0 -0
  55. {lemonade_sdk-7.0.3.dist-info → lemonade_sdk-8.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,439 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Lemonade Server</title>
7
+ <link rel="icon" href="/static/favicon.ico">
8
+ <link rel="stylesheet" href="/static/styles.css">
9
+ <script>
10
+ window.SERVER_PORT = {{SERVER_PORT}};
11
+ </script>
12
+ {{SERVER_MODELS_JS}}
13
+ </head>
14
+ <body>
15
+ <nav class="navbar">
16
+ <a href="https://github.com/lemonade-sdk/lemonade" target="_blank">GitHub</a>
17
+ <a href="https://lemonade-server.ai/docs/" target="_blank">Docs</a>
18
+ <a href="https://lemonade-server.ai/docs/server/server_models/" target="_blank">Models</a>
19
+ <a href="https://lemonade-server.ai/docs/server/apps/" target="_blank">Featured Apps</a>
20
+ </nav>
21
+ <main class="main">
22
+ <div class="title">🍋 Lemonade Server</div>
23
+ <div class="tab-container">
24
+ <div class="tabs">
25
+ <button class="tab active" id="tab-chat" onclick="showTab('chat')">LLM Chat</button>
26
+ <button class="tab" id="tab-models" onclick="showTab('models')">Model Management</button>
27
+ </div>
28
+ <div class="tab-content active" id="content-chat">
29
+ <div class="chat-container">
30
+ <div class="chat-history" id="chat-history"></div>
31
+ <div class="chat-input-row">
32
+ <select id="model-select"></select>
33
+ <input type="text" id="chat-input" placeholder="Type your message..." />
34
+ <button id="send-btn">Send</button>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <div class="tab-content" id="content-models"> <div class="model-mgmt-register-form collapsed"> <h3 class="model-mgmt-form-title" onclick="toggleAddModelForm()">
39
+ Add a Model
40
+ <span class="tooltip-icon" data-tooltip="Lemonade Server has a built-in set of suggested models, however you can use this form to add any compatible GGUF or ONNX model you like from Hugging Face.">ⓘ</span>
41
+ </h3>
42
+ <form id="register-model-form" autocomplete="off" class="form-content">
43
+ <div class="register-form-row">
44
+ <label class="register-label">
45
+ Model Name
46
+ <span class="tooltip-icon" data-tooltip="Enter a unique short name for your model. This is how the model will be referenced by Lemonade Server and connected apps. It will be prefixed with 'user.' to distinguish it from the built-in models.">ⓘ</span>
47
+ </label>
48
+ <div class="register-model-name-group">
49
+ <span class="register-model-prefix styled-prefix">user.</span>
50
+ <input type="text" id="register-model-name" name="model_name" placeholder="Gemma-3-12b-it-GGUF" required autocomplete="off">
51
+ </div>
52
+ </div>
53
+ <div class="register-form-row">
54
+ <label class="register-label">
55
+ Checkpoint
56
+ <span class="tooltip-icon" data-tooltip="Specify the model checkpoint path from Hugging Face (e.g., org-name/model-name:variant).">ⓘ</span>
57
+ </label>
58
+ <input type="text" id="register-checkpoint" name="checkpoint" placeholder="unsloth/gemma-3-12b-it-GGUF:Q4_0" class="register-textbox" autocomplete="off">
59
+ </div>
60
+ <div class="register-form-row">
61
+ <label class="register-label">
62
+ Recipe
63
+ <span class="tooltip-icon" data-tooltip="Select the Lemonade recipe corresponding to the inference engine and device Lemonade Server should use for the model. Use llamacpp for GGUF models. For OGA/ONNX models, click the More Info button to learn about the oga-* recipes.">ⓘ</span>
64
+ </label>
65
+ <select id="register-recipe" name="recipe" required>
66
+ <option value="llamacpp">llamacpp</option>
67
+ <option value="oga-hybrid">oga-hybrid</option>
68
+ <option value="oga-cpu">oga-cpu</option>
69
+ </select>
70
+ <a href="https://lemonade-server.ai/docs/lemonade_api/" target="_blank" class="register-doc-link">More info</a>
71
+ </div>
72
+ <div class="register-form-row register-form-row-tight">
73
+ <label class="register-label">
74
+ mmproj file
75
+ <span class="tooltip-icon" data-tooltip="Specify an mmproj file from the same Hugging Face checkpoint as the model. This is used for multimodal models, such as VLMs. Leave empty if not needed.">ⓘ</span>
76
+ </label>
77
+ <input type="text" id="register-mmproj" name="mmproj" placeholder="(Optional) mmproj-F16.gguf" autocomplete="off">
78
+ <label class="register-label reasoning-inline">
79
+ <input type="checkbox" id="register-reasoning" name="reasoning">
80
+ Reasoning
81
+ <span class="tooltip-icon" data-tooltip="Enable to inform Lemonade Server that the model has reasoning capabilities that will use thinking tokens.">ⓘ</span>
82
+ </label>
83
+ </div>
84
+ <div class="register-form-row register-form-row-tight">
85
+ <button type="submit" id="register-submit">Install</button>
86
+ <span id="register-model-status" class="register-status"></span> </div>
87
+ </form>
88
+ </div>
89
+ <div class="model-mgmt-container">
90
+ <div class="model-mgmt-pane">
91
+ <h3>Installed Models</h3>
92
+ <table class="model-table" id="installed-models-table">
93
+ <colgroup><col style="width:100%"></colgroup>
94
+ <tbody id="installed-models-tbody"></tbody>
95
+ </table>
96
+ </div>
97
+ <div class="model-mgmt-pane">
98
+ <h3>Suggested Models</h3>
99
+ <table class="model-table" id="suggested-models-table">
100
+ <tbody id="suggested-models-tbody"></tbody>
101
+ </table>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </main>
107
+ <footer class="site-footer">
108
+ <div class="dad-joke">When life gives you LLMs, make an LLM aide.</div>
109
+ <div class="copyright">Copyright 2025 AMD</div>
110
+ </footer>
111
+ <script src="https://cdn.jsdelivr.net/npm/openai@4.21.0/dist/openai.min.js"></script>
112
+ <script> // Tab switching logic
113
+ function showTab(tab) {
114
+ document.getElementById('tab-chat').classList.remove('active');
115
+ document.getElementById('tab-models').classList.remove('active');
116
+ document.getElementById('content-chat').classList.remove('active');
117
+ document.getElementById('content-models').classList.remove('active');
118
+ if (tab === 'chat') {
119
+ document.getElementById('tab-chat').classList.add('active');
120
+ document.getElementById('content-chat').classList.add('active');
121
+ } else {
122
+ document.getElementById('tab-models').classList.add('active');
123
+ document.getElementById('content-models').classList.add('active');
124
+ }
125
+ }
126
+
127
+ // Toggle Add Model form
128
+ function toggleAddModelForm() {
129
+ const form = document.querySelector('.model-mgmt-register-form');
130
+ form.classList.toggle('collapsed');
131
+ }
132
+
133
+ // Helper to get server base URL
134
+ function getServerBaseUrl() {
135
+ const port = window.SERVER_PORT || 8000;
136
+ return `http://localhost:${port}`;
137
+ }
138
+
139
+ // Populate model dropdown from /api/v1/models endpoint
140
+ async function loadModels() {
141
+ try {
142
+ const resp = await fetch(getServerBaseUrl() + '/api/v1/models');
143
+ const data = await resp.json();
144
+ const select = document.getElementById('model-select');
145
+ select.innerHTML = '';
146
+ if (!data.data || !Array.isArray(data.data)) {
147
+ select.innerHTML = '<option>No models found (malformed response)</option>';
148
+ return;
149
+ }
150
+ if (data.data.length === 0) {
151
+ select.innerHTML = '<option>No models available</option>';
152
+ return;
153
+ }
154
+ let defaultIndex = 0;
155
+ data.data.forEach(function(model, index) {
156
+ const modelId = model.id || model.name || model;
157
+ const opt = document.createElement('option');
158
+ opt.value = modelId;
159
+ opt.textContent = modelId;
160
+ if (modelId === 'Llama-3.2-1B-Instruct-Hybrid') {
161
+ defaultIndex = index;
162
+ }
163
+ select.appendChild(opt);
164
+ });
165
+ select.selectedIndex = defaultIndex;
166
+ } catch (e) {
167
+ const select = document.getElementById('model-select');
168
+ select.innerHTML = `<option>Error loading models: ${e.message}</option>`;
169
+ console.error('Error loading models:', e);
170
+ }
171
+ }
172
+ loadModels();
173
+
174
+ // Helper function to create model name with labels
175
+ function createModelNameWithLabels(modelId, allModels) {
176
+ // Create container for model name and labels
177
+ const container = document.createElement('div');
178
+ container.className = 'model-labels-container';
179
+
180
+ // Add model name
181
+ const nameSpan = document.createElement('span');
182
+ nameSpan.textContent = modelId;
183
+ container.appendChild(nameSpan);
184
+
185
+ // Add labels if they exist
186
+ const modelData = allModels[modelId];
187
+ if (modelData) {
188
+ // Add reasoning label if reasoning is true
189
+ if (modelData.reasoning === true) {
190
+ const reasoningLabel = document.createElement('span');
191
+ reasoningLabel.className = 'model-label reasoning';
192
+ reasoningLabel.textContent = 'reasoning';
193
+ container.appendChild(reasoningLabel);
194
+ }
195
+
196
+ // Add other labels if they exist
197
+ if (modelData.labels && Array.isArray(modelData.labels)) {
198
+ modelData.labels.forEach(label => {
199
+ const labelSpan = document.createElement('span');
200
+ const labelLower = label.toLowerCase();
201
+ const labelClass = (labelLower === 'vision') ? 'vision' : 'other';
202
+ labelSpan.className = `model-label ${labelClass}`;
203
+ labelSpan.textContent = label;
204
+ container.appendChild(labelSpan);
205
+ });
206
+ }
207
+ }
208
+
209
+ return container;
210
+ }
211
+
212
+ // Model Management Tab Logic
213
+ async function refreshModelMgmtUI() {
214
+ // Get installed models from /api/v1/models
215
+ let installed = [];
216
+ try {
217
+ const resp = await fetch(getServerBaseUrl() + '/api/v1/models');
218
+ const data = await resp.json();
219
+ if (data.data && Array.isArray(data.data)) {
220
+ installed = data.data.map(m => m.id || m.name || m);
221
+ }
222
+ } catch (e) {}
223
+ // All models from server_models.json (window.SERVER_MODELS)
224
+ const allModels = window.SERVER_MODELS || {};
225
+ // Filter suggested models not installed
226
+ const suggested = Object.keys(allModels).filter(
227
+ k => allModels[k].suggested && !installed.includes(k)
228
+ );
229
+ // Render installed models as a table (two columns, second is invisible)
230
+ const installedTbody = document.getElementById('installed-models-tbody');
231
+ installedTbody.innerHTML = '';
232
+ installed.forEach(function(mid) {
233
+ var tr = document.createElement('tr');
234
+ var tdName = document.createElement('td');
235
+
236
+ tdName.appendChild(createModelNameWithLabels(mid, allModels));
237
+ tdName.style.paddingRight = '1em';
238
+ tdName.style.verticalAlign = 'middle';
239
+
240
+ var tdBtn = document.createElement('td');
241
+ tdBtn.style.width = '1%';
242
+ tdBtn.style.verticalAlign = 'middle';
243
+ const btn = document.createElement('button');
244
+ btn.textContent = '−';
245
+ btn.title = 'Delete model';
246
+ btn.style.cursor = 'pointer';
247
+ btn.onclick = async function() {
248
+ if (!confirm(`Are you sure you want to delete the model "${mid}"?`)) {
249
+ return;
250
+ }
251
+ btn.disabled = true;
252
+ btn.textContent = 'Deleting...';
253
+ btn.style.backgroundColor = '#888';
254
+ try {
255
+ const response = await fetch(getServerBaseUrl() + '/api/v1/delete', {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify({ model_name: mid })
259
+ });
260
+ if (!response.ok) {
261
+ const errorData = await response.json();
262
+ throw new Error(errorData.detail || 'Failed to delete model');
263
+ }
264
+ await refreshModelMgmtUI();
265
+ await loadModels(); // update chat dropdown too
266
+ } catch (e) {
267
+ btn.textContent = 'Error';
268
+ btn.disabled = false;
269
+ alert(`Failed to delete model: ${e.message}`);
270
+ }
271
+ };
272
+ tdBtn.appendChild(btn);
273
+
274
+ tr.appendChild(tdName);
275
+ tr.appendChild(tdBtn);
276
+ installedTbody.appendChild(tr);
277
+ });
278
+ // Render suggested models as a table
279
+ const suggestedTbody = document.getElementById('suggested-models-tbody');
280
+ suggestedTbody.innerHTML = '';
281
+ suggested.forEach(mid => {
282
+ const tr = document.createElement('tr');
283
+ const tdName = document.createElement('td');
284
+
285
+ tdName.appendChild(createModelNameWithLabels(mid, allModels));
286
+ tdName.style.paddingRight = '1em';
287
+ tdName.style.verticalAlign = 'middle';
288
+ const tdBtn = document.createElement('td');
289
+ tdBtn.style.width = '1%';
290
+ tdBtn.style.verticalAlign = 'middle';
291
+ const btn = document.createElement('button');
292
+ btn.textContent = '+';
293
+ btn.title = 'Install model';
294
+ btn.onclick = async function() {
295
+ btn.disabled = true;
296
+ btn.textContent = 'Installing...';
297
+ btn.classList.add('installing-btn');
298
+ try {
299
+ await fetch(getServerBaseUrl() + '/api/v1/pull', {
300
+ method: 'POST',
301
+ headers: { 'Content-Type': 'application/json' },
302
+ body: JSON.stringify({ model_name: mid })
303
+ });
304
+ await refreshModelMgmtUI();
305
+ await loadModels(); // update chat dropdown too
306
+ } catch (e) {
307
+ btn.textContent = 'Error';
308
+ }
309
+ };
310
+ tdBtn.appendChild(btn);
311
+ tr.appendChild(tdName);
312
+ tr.appendChild(tdBtn);
313
+ suggestedTbody.appendChild(tr);
314
+ });
315
+ }
316
+ // Initial load
317
+ refreshModelMgmtUI();
318
+ // Optionally, refresh when switching to the tab
319
+ document.getElementById('tab-models').addEventListener('click', refreshModelMgmtUI);
320
+
321
+ // Chat logic (streaming with OpenAI JS client placeholder)
322
+ const chatHistory = document.getElementById('chat-history');
323
+ const chatInput = document.getElementById('chat-input');
324
+ const sendBtn = document.getElementById('send-btn');
325
+ const modelSelect = document.getElementById('model-select');
326
+ let messages = [];
327
+
328
+ function appendMessage(role, text) {
329
+ const div = document.createElement('div');
330
+ div.className = 'chat-message ' + role;
331
+ // Add a bubble for iMessage style
332
+ const bubble = document.createElement('div');
333
+ bubble.className = 'chat-bubble ' + role;
334
+ bubble.innerHTML = text;
335
+ div.appendChild(bubble);
336
+ chatHistory.appendChild(div);
337
+ chatHistory.scrollTop = chatHistory.scrollHeight;
338
+ }
339
+
340
+ async function sendMessage() {
341
+ const text = chatInput.value.trim();
342
+ if (!text) return;
343
+ appendMessage('user', text);
344
+ messages.push({ role: 'user', content: text });
345
+ chatInput.value = '';
346
+ sendBtn.disabled = true;
347
+ // Streaming OpenAI completions (placeholder, adapt as needed)
348
+ let llmText = '';
349
+ appendMessage('llm', '...');
350
+ const llmDiv = chatHistory.lastChild.querySelector('.chat-bubble.llm');
351
+ try {
352
+ // Use the correct endpoint for chat completions
353
+ const resp = await fetch(getServerBaseUrl() + '/api/v1/chat/completions', {
354
+ method: 'POST',
355
+ headers: { 'Content-Type': 'application/json' },
356
+ body: JSON.stringify({
357
+ model: modelSelect.value,
358
+ messages: messages,
359
+ stream: true
360
+ })
361
+ });
362
+ if (!resp.body) throw new Error('No stream');
363
+ const reader = resp.body.getReader();
364
+ let decoder = new TextDecoder();
365
+ llmDiv.textContent = '';
366
+ while (true) {
367
+ const { done, value } = await reader.read();
368
+ if (done) break;
369
+ const chunk = decoder.decode(value);
370
+ if (chunk.trim() === 'data: [DONE]' || chunk.trim() === '[DONE]') continue;
371
+ // Try to extract the content from the OpenAI chunk
372
+ const match = chunk.match(/"content"\s*:\s*"([^"]*)"/);
373
+ if (match && match[1]) {
374
+ llmText += match[1];
375
+ llmDiv.textContent = llmText;
376
+ }
377
+ }
378
+ messages.push({ role: 'assistant', content: llmText });
379
+ } catch (e) {
380
+ llmDiv.textContent = '[Error: ' + e.message + ']';
381
+ }
382
+ sendBtn.disabled = false;
383
+ }
384
+ sendBtn.onclick = sendMessage;
385
+ chatInput.addEventListener('keydown', function(e) {
386
+ if (e.key === 'Enter') sendMessage();
387
+ });
388
+
389
+ // Register & Install Model logic
390
+ const registerForm = document.getElementById('register-model-form');
391
+ const registerStatus = document.getElementById('register-model-status');
392
+ if (registerForm) {
393
+ registerForm.onsubmit = async function(e) {
394
+ e.preventDefault();
395
+ registerStatus.textContent = '';
396
+ let name = document.getElementById('register-model-name').value.trim();
397
+ // Always prepend 'user.' if not already present
398
+ if (!name.startsWith('user.')) {
399
+ name = 'user.' + name;
400
+ }
401
+ const checkpoint = document.getElementById('register-checkpoint').value.trim();
402
+ const recipe = document.getElementById('register-recipe').value;
403
+ const reasoning = document.getElementById('register-reasoning').checked;
404
+ const mmproj = document.getElementById('register-mmproj').value.trim();
405
+ if (!name || !recipe) { return; }
406
+ const payload = { model_name: name, recipe, reasoning };
407
+ if (checkpoint) payload.checkpoint = checkpoint;
408
+ if (mmproj) payload.mmproj = mmproj;
409
+ const btn = document.getElementById('register-submit');
410
+ btn.disabled = true;
411
+ btn.textContent = 'Installing...';
412
+ try {
413
+ const resp = await fetch(getServerBaseUrl() + '/api/v1/pull', {
414
+ method: 'POST',
415
+ headers: { 'Content-Type': 'application/json' },
416
+ body: JSON.stringify(payload)
417
+ });
418
+ if (!resp.ok) {
419
+ const err = await resp.json().catch(() => ({}));
420
+ throw new Error(err.detail || 'Failed to register model.'); }
421
+ registerStatus.textContent = 'Model installed!';
422
+ registerStatus.style.color = '#27ae60';
423
+ registerStatus.className = 'register-status success';
424
+ registerForm.reset();
425
+ await refreshModelMgmtUI();
426
+ await loadModels(); // update chat dropdown too
427
+ } catch (e) {
428
+ registerStatus.textContent = e.message + ' See the Lemonade Server log for details.';
429
+ registerStatus.style.color = '#dc3545';
430
+ registerStatus.className = 'register-status error';
431
+ }
432
+ btn.disabled = false;
433
+ btn.textContent = 'Install';
434
+ refreshModelMgmtUI();
435
+ };
436
+ }
437
+ </script>
438
+ </body>
439
+ </html>