Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
adjust for event
Browse files
ui/src/app/api/hf-jobs/route.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { tmpdir } from 'os';
|
|
| 7 |
export async function POST(request: NextRequest) {
|
| 8 |
try {
|
| 9 |
const body = await request.json();
|
| 10 |
-
const { action, token, hardware, namespace, jobConfig, datasetRepo } = body;
|
| 11 |
|
| 12 |
switch (action) {
|
| 13 |
case 'checkStatus':
|
|
@@ -59,7 +59,12 @@ export async function POST(request: NextRequest) {
|
|
| 59 |
await writeFile(scriptPath, uvScript);
|
| 60 |
|
| 61 |
// Submit HF job using uv run
|
| 62 |
-
const jobId = await submitHFJobUV(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
return NextResponse.json({
|
| 65 |
success: true,
|
|
@@ -597,7 +602,7 @@ def generate_model_card_readme(repo_id: str, config: dict, model_name: str, cura
|
|
| 597 |
tags.append("sd3")
|
| 598 |
|
| 599 |
# Add LoRA-specific tags
|
| 600 |
-
tags.extend(["lora", "diffusers", "template:sd-lora", "ai-toolkit"
|
| 601 |
|
| 602 |
# Generate widgets and gallery section from sample images
|
| 603 |
curated_samples = curated_samples or []
|
|
@@ -778,7 +783,7 @@ if __name__ == "__main__":
|
|
| 778 |
`;
|
| 779 |
}
|
| 780 |
|
| 781 |
-
async function submitHFJobUV(token: string, hardware: string, scriptPath: string): Promise<string> {
|
| 782 |
return new Promise((resolve, reject) => {
|
| 783 |
// Ensure token is available
|
| 784 |
if (!token) {
|
|
@@ -787,17 +792,25 @@ async function submitHFJobUV(token: string, hardware: string, scriptPath: string
|
|
| 787 |
}
|
| 788 |
|
| 789 |
console.log('Setting up environment with HF_TOKEN for job submission');
|
| 790 |
-
|
| 791 |
-
|
|
|
|
| 792 |
// Use hf jobs uv run command with timeout and detach to get job ID
|
| 793 |
-
const
|
| 794 |
'jobs', 'uv', 'run',
|
| 795 |
'--flavor', hardware,
|
| 796 |
'--timeout', '5h',
|
| 797 |
'--secrets', 'HF_TOKEN',
|
| 798 |
-
'--detach'
|
| 799 |
-
|
| 800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
env: {
|
| 802 |
...process.env,
|
| 803 |
HF_TOKEN: token
|
|
|
|
| 7 |
export async function POST(request: NextRequest) {
|
| 8 |
try {
|
| 9 |
const body = await request.json();
|
| 10 |
+
const { action, token, hardware, namespace, jobConfig, datasetRepo, participateHackathon } = body;
|
| 11 |
|
| 12 |
switch (action) {
|
| 13 |
case 'checkStatus':
|
|
|
|
| 59 |
await writeFile(scriptPath, uvScript);
|
| 60 |
|
| 61 |
// Submit HF job using uv run
|
| 62 |
+
const jobId = await submitHFJobUV(
|
| 63 |
+
token,
|
| 64 |
+
hardware,
|
| 65 |
+
scriptPath,
|
| 66 |
+
participateHackathon ? 'lora-training-frenzi' : undefined
|
| 67 |
+
);
|
| 68 |
|
| 69 |
return NextResponse.json({
|
| 70 |
success: true,
|
|
|
|
| 602 |
tags.append("sd3")
|
| 603 |
|
| 604 |
# Add LoRA-specific tags
|
| 605 |
+
tags.extend(["lora", "diffusers", "template:sd-lora", "ai-toolkit"])
|
| 606 |
|
| 607 |
# Generate widgets and gallery section from sample images
|
| 608 |
curated_samples = curated_samples or []
|
|
|
|
| 783 |
`;
|
| 784 |
}
|
| 785 |
|
| 786 |
+
async function submitHFJobUV(token: string, hardware: string, scriptPath: string, namespaceOverride?: string): Promise<string> {
|
| 787 |
return new Promise((resolve, reject) => {
|
| 788 |
// Ensure token is available
|
| 789 |
if (!token) {
|
|
|
|
| 792 |
}
|
| 793 |
|
| 794 |
console.log('Setting up environment with HF_TOKEN for job submission');
|
| 795 |
+
const namespaceArgs = namespaceOverride ? ` --namespace ${namespaceOverride}` : '';
|
| 796 |
+
console.log(`Command: hf jobs uv run --flavor ${hardware} --timeout 5h --secrets HF_TOKEN --detach${namespaceArgs} ${scriptPath}`);
|
| 797 |
+
|
| 798 |
// Use hf jobs uv run command with timeout and detach to get job ID
|
| 799 |
+
const args = [
|
| 800 |
'jobs', 'uv', 'run',
|
| 801 |
'--flavor', hardware,
|
| 802 |
'--timeout', '5h',
|
| 803 |
'--secrets', 'HF_TOKEN',
|
| 804 |
+
'--detach'
|
| 805 |
+
];
|
| 806 |
+
|
| 807 |
+
if (namespaceOverride) {
|
| 808 |
+
args.push('--namespace', namespaceOverride);
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
args.push(scriptPath);
|
| 812 |
+
|
| 813 |
+
const childProcess = spawn('hf', args, {
|
| 814 |
env: {
|
| 815 |
...process.env,
|
| 816 |
HF_TOKEN: token
|
ui/src/app/dashboard/page.tsx
CHANGED
|
@@ -19,6 +19,13 @@ export default function Dashboard() {
|
|
| 19 |
<div className="flex-1" />
|
| 20 |
</TopBar>
|
| 21 |
<MainContent>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
<div className="border border-gray-800 rounded-xl bg-gray-900 p-6 flex flex-col gap-4">
|
| 23 |
<div>
|
| 24 |
<h2 className="text-xl font-semibold text-gray-100">
|
|
|
|
| 19 |
<div className="flex-1" />
|
| 20 |
</TopBar>
|
| 21 |
<MainContent>
|
| 22 |
+
<div className="mb-6">
|
| 23 |
+
<img
|
| 24 |
+
src="https://d112y698adiu2z.cloudfront.net/photos/production/challenge_photos/003/754/358/datas/full_width.png"
|
| 25 |
+
alt="FLUX.1 Kontext Dev Hackathon banner"
|
| 26 |
+
className="w-full rounded-lg border border-gray-800"
|
| 27 |
+
/>
|
| 28 |
+
</div>
|
| 29 |
<div className="border border-gray-800 rounded-xl bg-gray-900 p-6 flex flex-col gap-4">
|
| 30 |
<div>
|
| 31 |
<h2 className="text-xl font-semibold text-gray-100">
|
ui/src/app/jobs/new/SimpleJob.tsx
CHANGED
|
@@ -125,6 +125,16 @@ export default function SimpleJob({
|
|
| 125 |
return newQuantizationOptions;
|
| 126 |
}, [modelArch]);
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
return (
|
| 129 |
<>
|
| 130 |
<form onSubmit={handleSubmit} className="space-y-8">
|
|
@@ -956,6 +966,7 @@ export default function SimpleJob({
|
|
| 956 |
<div className="mt-8">
|
| 957 |
<HFJobsWorkflow
|
| 958 |
jobConfig={jobConfig}
|
|
|
|
| 959 |
onComplete={(jobId, localJobId) => {
|
| 960 |
console.log('HF Job submitted:', jobId, 'Local job ID:', localJobId);
|
| 961 |
if (onHFJobComplete) {
|
|
|
|
| 125 |
return newQuantizationOptions;
|
| 126 |
}, [modelArch]);
|
| 127 |
|
| 128 |
+
const isFluxKontextDev = useMemo(() => {
|
| 129 |
+
try {
|
| 130 |
+
const process = jobConfig?.config?.process?.[0];
|
| 131 |
+
const model = process?.model ?? {};
|
| 132 |
+
return model?.arch === 'flux_kontext' && model?.name_or_path === 'black-forest-labs/FLUX.1-Kontext-dev';
|
| 133 |
+
} catch (error) {
|
| 134 |
+
return false;
|
| 135 |
+
}
|
| 136 |
+
}, [jobConfig]);
|
| 137 |
+
|
| 138 |
return (
|
| 139 |
<>
|
| 140 |
<form onSubmit={handleSubmit} className="space-y-8">
|
|
|
|
| 966 |
<div className="mt-8">
|
| 967 |
<HFJobsWorkflow
|
| 968 |
jobConfig={jobConfig}
|
| 969 |
+
hackathonEligible={isFluxKontextDev}
|
| 970 |
onComplete={(jobId, localJobId) => {
|
| 971 |
console.log('HF Job submitted:', jobId, 'Local job ID:', localJobId);
|
| 972 |
if (onHFJobComplete) {
|
ui/src/components/HFJobsWorkflow.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
-
import { useState } from 'react';
|
| 4 |
import { Button } from '@headlessui/react';
|
| 5 |
import { SelectInput, TextInput, Checkbox } from '@/components/formInputs';
|
| 6 |
import Card from '@/components/Card';
|
|
@@ -130,11 +130,12 @@ import { useAuth } from '@/contexts/AuthContext';
|
|
| 130 |
interface HFJobsWorkflowProps {
|
| 131 |
jobConfig: JobConfig;
|
| 132 |
onComplete: (jobId: string, localJobId?: string) => void;
|
|
|
|
| 133 |
}
|
| 134 |
|
| 135 |
type Step = 'validate' | 'upload' | 'submit' | 'complete';
|
| 136 |
|
| 137 |
-
export default function HFJobsWorkflow({ jobConfig, onComplete }: HFJobsWorkflowProps) {
|
| 138 |
const { settings } = useSettings();
|
| 139 |
const { token: authToken } = useAuth();
|
| 140 |
const [defaultNamespace, setDefaultNamespace] = useState('');
|
|
@@ -149,6 +150,13 @@ export default function HFJobsWorkflow({ jobConfig, onComplete }: HFJobsWorkflow
|
|
| 149 |
const [hardware, setHardware] = useState(settings.HF_JOBS_DEFAULT_HARDWARE || 'a100-large');
|
| 150 |
const [namespace, setNamespace] = useState(settings.HF_JOBS_NAMESPACE || '');
|
| 151 |
const [autoUpload, setAutoUpload] = useState(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
// Progress state
|
| 154 |
const [validationResult, setValidationResult] = useState<any>(null);
|
|
@@ -301,6 +309,7 @@ export default function HFJobsWorkflow({ jobConfig, onComplete }: HFJobsWorkflow
|
|
| 301 |
namespace: resolvedNamespace,
|
| 302 |
jobConfig,
|
| 303 |
datasetRepo,
|
|
|
|
| 304 |
});
|
| 305 |
|
| 306 |
if (response.data.success) {
|
|
@@ -385,6 +394,26 @@ export default function HFJobsWorkflow({ jobConfig, onComplete }: HFJobsWorkflow
|
|
| 385 |
return (
|
| 386 |
<Card title="Validate HF Token">
|
| 387 |
<div className="space-y-4">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
<p className="text-sm text-gray-400">
|
| 389 |
First, let's validate your Hugging Face token and get your username for dataset uploads.
|
| 390 |
</p>
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
+
import { useEffect, useState } from 'react';
|
| 4 |
import { Button } from '@headlessui/react';
|
| 5 |
import { SelectInput, TextInput, Checkbox } from '@/components/formInputs';
|
| 6 |
import Card from '@/components/Card';
|
|
|
|
| 130 |
interface HFJobsWorkflowProps {
|
| 131 |
jobConfig: JobConfig;
|
| 132 |
onComplete: (jobId: string, localJobId?: string) => void;
|
| 133 |
+
hackathonEligible?: boolean;
|
| 134 |
}
|
| 135 |
|
| 136 |
type Step = 'validate' | 'upload' | 'submit' | 'complete';
|
| 137 |
|
| 138 |
+
export default function HFJobsWorkflow({ jobConfig, onComplete, hackathonEligible = false }: HFJobsWorkflowProps) {
|
| 139 |
const { settings } = useSettings();
|
| 140 |
const { token: authToken } = useAuth();
|
| 141 |
const [defaultNamespace, setDefaultNamespace] = useState('');
|
|
|
|
| 150 |
const [hardware, setHardware] = useState(settings.HF_JOBS_DEFAULT_HARDWARE || 'a100-large');
|
| 151 |
const [namespace, setNamespace] = useState(settings.HF_JOBS_NAMESPACE || '');
|
| 152 |
const [autoUpload, setAutoUpload] = useState(true);
|
| 153 |
+
const [participateHackathon, setParticipateHackathon] = useState(false);
|
| 154 |
+
|
| 155 |
+
useEffect(() => {
|
| 156 |
+
if (!hackathonEligible && participateHackathon) {
|
| 157 |
+
setParticipateHackathon(false);
|
| 158 |
+
}
|
| 159 |
+
}, [hackathonEligible, participateHackathon]);
|
| 160 |
|
| 161 |
// Progress state
|
| 162 |
const [validationResult, setValidationResult] = useState<any>(null);
|
|
|
|
| 309 |
namespace: resolvedNamespace,
|
| 310 |
jobConfig,
|
| 311 |
datasetRepo,
|
| 312 |
+
participateHackathon: hackathonEligible && participateHackathon,
|
| 313 |
});
|
| 314 |
|
| 315 |
if (response.data.success) {
|
|
|
|
| 394 |
return (
|
| 395 |
<Card title="Validate HF Token">
|
| 396 |
<div className="space-y-4">
|
| 397 |
+
{hackathonEligible && (
|
| 398 |
+
<Checkbox
|
| 399 |
+
label={
|
| 400 |
+
<span>
|
| 401 |
+
Participating in the free FLUX.1 Kontext Dev Hackathon? If so, please join this
|
| 402 |
+
<a
|
| 403 |
+
href="https://huggingface.co/organizations/lora-training-frenzi/share/kEyyVNQXBPWqmARdwHFVdIiFqqONHZPOtz"
|
| 404 |
+
target="_blank"
|
| 405 |
+
rel="noopener noreferrer"
|
| 406 |
+
className="text-blue-400 underline mx-1"
|
| 407 |
+
>
|
| 408 |
+
organization
|
| 409 |
+
</a>
|
| 410 |
+
before starting your job.
|
| 411 |
+
</span>
|
| 412 |
+
}
|
| 413 |
+
checked={participateHackathon}
|
| 414 |
+
onChange={value => setParticipateHackathon(value)}
|
| 415 |
+
/>
|
| 416 |
+
)}
|
| 417 |
<p className="text-sm text-gray-400">
|
| 418 |
First, let's validate your Hugging Face token and get your username for dataset uploads.
|
| 419 |
</p>
|