|
222 | 222 | size="sm" |
223 | 223 | class="h-9 px-3 text-xs cursor-pointer" |
224 | 224 | :disabled=" |
| 225 | + !llm.hf_model_id.trim() || |
225 | 226 | typeof llm.concurrent_users !== 'number' || |
226 | | - llm.concurrent_users < 1 |
| 227 | + llm.concurrent_users < 1 || |
| 228 | + isAutofilling |
227 | 229 | " |
228 | 230 | @click="autofillModelSettings" |
229 | 231 | > |
230 | | - <Icon name="lucide:wand-2" class="h-3 w-3 mr-1" /> |
| 232 | + <Icon |
| 233 | + :name="isAutofilling ? 'lucide:loader-2' : 'lucide:wand-2'" |
| 234 | + :class="[ |
| 235 | + 'h-3 w-3 mr-1', |
| 236 | + isAutofilling ? 'animate-spin' : '', |
| 237 | + ]" |
| 238 | + /> |
231 | 239 | Autofill |
232 | 240 | </Button> |
233 | 241 | </div> |
234 | | - <p class="text-[11px] text-muted-foreground mt-1"> |
235 | | - Enter expected concurrent users and click Autofill to suggest |
236 | | - model settings. |
| 242 | + <p |
| 243 | + v-if="autofillError" |
| 244 | + class="text-[11px] text-destructive flex items-start gap-1 mt-1" |
| 245 | + > |
| 246 | + <Icon |
| 247 | + name="lucide:alert-circle" |
| 248 | + class="w-3 h-3 mt-0.5 shrink-0" |
| 249 | + /> |
| 250 | + <span>{{ autofillError }}</span> |
| 251 | + </p> |
| 252 | + <p v-else class="text-[11px] text-muted-foreground mt-1"> |
| 253 | + Enter HF Model ID and expected concurrent users, then click |
| 254 | + Autofill to fetch recommended model settings. |
237 | 255 | </p> |
238 | 256 | </FieldRow> |
239 | 257 | <FieldRow label="HF token"> |
@@ -439,10 +457,13 @@ const emit = defineEmits<{ |
439 | 457 | (e: 'created'): void; |
440 | 458 | }>(); |
441 | 459 |
|
442 | | -const { postModelServing } = useApi(); |
| 460 | +const { postModelServing, recommendModelServing } = useApi(); |
| 461 | +const toaster = useToaster(); |
443 | 462 |
|
444 | 463 | const mode = ref<Mode>('classical'); |
445 | 464 | const isSubmitting = ref(false); |
| 465 | +const isAutofilling = ref(false); |
| 466 | +const autofillError = ref<string | null>(null); |
446 | 467 |
|
447 | 468 | const blankClassical = () => ({ |
448 | 469 | isvc_name: '', |
@@ -600,26 +621,88 @@ const recommendForUsers = (users: number): RecommendedSettings => { |
600 | 621 | }; |
601 | 622 | }; |
602 | 623 |
|
603 | | -const autofillModelSettings = () => { |
| 624 | +const autofillModelSettings = async () => { |
604 | 625 | const users = llm.concurrent_users; |
605 | | - if (typeof users !== 'number' || users < 1) return; |
606 | | - const r = recommendForUsers(users); |
607 | | - llm.dtype = r.dtype; |
608 | | - llm.max_model_len = r.max_model_len; |
609 | | - llm.tensor_parallel_size = r.tensor_parallel_size; |
610 | | - llm.res_cpu_req = r.res_cpu_req; |
611 | | - llm.res_cpu_lim = r.res_cpu_lim; |
612 | | - llm.res_mem_req = r.res_mem_req; |
613 | | - llm.res_mem_lim = r.res_mem_lim; |
614 | | - llm.res_gpu_req = r.res_gpu_req; |
615 | | - llm.res_gpu_lim = r.res_gpu_lim; |
| 626 | + const hfModelId = llm.hf_model_id.trim(); |
| 627 | + if (!hfModelId || typeof users !== 'number' || users < 1) return; |
| 628 | +
|
| 629 | + autofillError.value = null; |
| 630 | + isAutofilling.value = true; |
| 631 | + try { |
| 632 | + type RecommendationItem = { |
| 633 | + profile?: string; |
| 634 | + max_model_len?: number; |
| 635 | + dtype?: string; |
| 636 | + tensor_parallel_size?: number; |
| 637 | + min_replicas?: number; |
| 638 | + max_replicas?: number; |
| 639 | + resources?: { |
| 640 | + requests?: Record<string, string>; |
| 641 | + limits?: Record<string, string>; |
| 642 | + }; |
| 643 | + }; |
| 644 | + type RecommendData = { recommendations?: RecommendationItem[] }; |
| 645 | +
|
| 646 | + const res = (await recommendModelServing( |
| 647 | + { |
| 648 | + hf_model_id: hfModelId, |
| 649 | + concurrent_users: users, |
| 650 | + expected_input_tokens: 2048, |
| 651 | + expected_output_tokens: 1024, |
| 652 | + quantization: 'none', |
| 653 | + profiles: ['throughput'], |
| 654 | + }, |
| 655 | + { |
| 656 | + onConflict: (msg) => { |
| 657 | + autofillError.value = msg; |
| 658 | + }, |
| 659 | + }, |
| 660 | + )) as { data?: RecommendData } | null; |
| 661 | +
|
| 662 | + if (autofillError.value) return; |
| 663 | +
|
| 664 | + const local = recommendForUsers(users); |
| 665 | + const recs = Array.isArray(res?.data?.recommendations) |
| 666 | + ? res.data.recommendations |
| 667 | + : []; |
| 668 | + const rec: RecommendationItem | undefined = |
| 669 | + recs.find((r) => r?.profile === 'throughput') ?? recs[0]; |
| 670 | +
|
| 671 | + const pickStr = (v: unknown): string | undefined => |
| 672 | + typeof v === 'string' && v.length > 0 ? v : undefined; |
| 673 | + const pickNum = (v: unknown): number | undefined => |
| 674 | + typeof v === 'number' && Number.isFinite(v) ? v : undefined; |
| 675 | +
|
| 676 | + const requests = rec?.resources?.requests ?? {}; |
| 677 | + const limits = rec?.resources?.limits ?? {}; |
| 678 | +
|
| 679 | + llm.dtype = pickStr(rec?.dtype) ?? local.dtype; |
| 680 | + llm.max_model_len = pickNum(rec?.max_model_len) ?? local.max_model_len; |
| 681 | + llm.tensor_parallel_size = |
| 682 | + pickNum(rec?.tensor_parallel_size) ?? local.tensor_parallel_size; |
| 683 | + llm.res_cpu_req = pickStr(requests.cpu) ?? local.res_cpu_req; |
| 684 | + llm.res_cpu_lim = pickStr(limits.cpu) ?? local.res_cpu_lim; |
| 685 | + llm.res_mem_req = pickStr(requests.memory) ?? local.res_mem_req; |
| 686 | + llm.res_mem_lim = pickStr(limits.memory) ?? local.res_mem_lim; |
| 687 | + llm.res_gpu_req = pickStr(requests['nvidia.com/gpu']) ?? local.res_gpu_req; |
| 688 | + llm.res_gpu_lim = pickStr(limits['nvidia.com/gpu']) ?? local.res_gpu_lim; |
| 689 | + llm.min_replicas = pickNum(rec?.min_replicas) ?? llm.min_replicas; |
| 690 | + llm.max_replicas = pickNum(rec?.max_replicas) ?? llm.max_replicas; |
| 691 | +
|
| 692 | + if (rec) { |
| 693 | + toaster.show('success', 'operation_completed'); |
| 694 | + } |
| 695 | + } finally { |
| 696 | + isAutofilling.value = false; |
| 697 | + } |
616 | 698 | }; |
617 | 699 |
|
618 | 700 | const resetState = () => { |
619 | 701 | Object.assign(classical, blankClassical()); |
620 | 702 | Object.assign(llm, blankLlm()); |
621 | 703 | mode.value = 'classical'; |
622 | 704 | isSubmitting.value = false; |
| 705 | + autofillError.value = null; |
623 | 706 | }; |
624 | 707 |
|
625 | 708 | watch( |
|
0 commit comments