Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 88 additions & 20 deletions apps/console/web/src/components/CreateJob.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ const STEP_INDEX: Record<Step, number> = { model: 1, template: 2, dataset: 3, se
const MIN_CLIENT_MAX_CONCURRENCY = 1;
const MAX_CLIENT_MAX_CONCURRENCY = 1024;
const MIN_ADAPTIVE_MAX_FACTOR = 1;
const MIN_ADAPTIVE_HEALTHY_WINDOW = 1;
const MIN_ADAPTIVE_ADDITIVE_INCREASE = 1;
const ADAPTIVE_CLIENT_PARAM_FIELDS = new Set([
'adaptiveMaxFactor',
'adaptiveHealthyWindow',
'adaptiveAdditiveIncrease',
]);
const BYTES_PER_MEBIBYTE = 1024 * 1024;
const FULL_PARSE_CACHE_MAX_BYTES = 20 * BYTES_PER_MEBIBYTE;

Expand All @@ -100,6 +107,8 @@ export function CreateJob({ onBack }: CreateJobProps) {
const [maxConcurrency, setMaxConcurrency] = useState('');
const [adaptiveConcurrency, setAdaptiveConcurrency] = useState(true);
const [adaptiveMaxFactor, setAdaptiveMaxFactor] = useState('');
const [adaptiveHealthyWindow, setAdaptiveHealthyWindow] = useState('');
const [adaptiveAdditiveIncrease, setAdaptiveAdditiveIncrease] = useState('');
const [retryMaxRetries, setRetryMaxRetries] = useState('');
const [retryBaseDelay, setRetryBaseDelay] = useState('');
const [retryMaxDelay, setRetryMaxDelay] = useState('');
Expand Down Expand Up @@ -404,7 +413,9 @@ export function CreateJob({ onBack }: CreateJobProps) {
setParamErrors(prev => ({ ...prev, replicas: err }));
}, [replicaLimits, replicas]);

const hasParamErrors = Object.values(paramErrors).some(e => e !== '');
const hasParamErrors = Object.entries(paramErrors).some(([field, error]) => (
error !== '' && (adaptiveConcurrency || !ADAPTIVE_CLIENT_PARAM_FIELDS.has(field))
));

// Assemble extra_body.aibrix.client from the Settings inputs, omitting blanks
// so unset fields fall back to metadata-service defaults. Returns undefined
Expand All @@ -421,7 +432,9 @@ export function CreateJob({ onBack }: CreateJobProps) {
maxConcurrency: parseNumber(maxConcurrency),
// Only forward the toggle when the user diverged from the default (on).
adaptiveConcurrency: adaptiveConcurrency ? undefined : false,
adaptiveMaxFactor: parseNumber(adaptiveMaxFactor),
adaptiveMaxFactor: adaptiveConcurrency ? parseNumber(adaptiveMaxFactor) : undefined,
adaptiveHealthyWindow: adaptiveConcurrency ? parseNumber(adaptiveHealthyWindow) : undefined,
adaptiveAdditiveIncrease: adaptiveConcurrency ? parseNumber(adaptiveAdditiveIncrease) : undefined,
retryPolicy: hasRetry ? retry : undefined,
};
const hasAny = Object.values(client).some(v => v !== undefined);
Expand Down Expand Up @@ -1077,7 +1090,24 @@ export function CreateJob({ onBack }: CreateJobProps) {
Optional smart-client concurrency and retry controls. Leave blank to use service defaults.
</p>

<div className="grid grid-cols-2 gap-4">
<label className="flex items-center gap-2 mb-2 text-sm">
<input
type="checkbox"
checked={adaptiveConcurrency}
onChange={(e) => setAdaptiveConcurrency(e.target.checked)}
className="rounded border-gray-300 text-teal-600 focus:ring-teal-500/30"
/>
Adaptive concurrency
<span className="text-xs text-gray-400">
(grow toward Max Concurrency; off = fixed concurrency)
</span>
</label>

<p className="text-xs text-gray-400 mb-4">
Initial probing grows by at least 25%; after the first decrease, growth uses the Additive Increase step.
</p>

<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm mb-1">Max Concurrency</label>
<p className="text-xs text-gray-400 mb-1">{MIN_CLIENT_MAX_CONCURRENCY} - {MAX_CLIENT_MAX_CONCURRENCY}</p>
Expand All @@ -1101,33 +1131,71 @@ export function CreateJob({ onBack }: CreateJobProps) {
<input
type="text"
value={adaptiveMaxFactor}
disabled={!adaptiveConcurrency}
onChange={(e) => handleParamChange('adaptiveMaxFactor', e.target.value, setAdaptiveMaxFactor, MIN_ADAPTIVE_MAX_FACTOR, undefined, false)}
placeholder="e.g. 8"
className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 ${
paramErrors.adaptiveMaxFactor ? 'border-red-300' : 'border-gray-200'
className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed ${
adaptiveConcurrency && paramErrors.adaptiveMaxFactor ? 'border-red-300' : 'border-gray-200'
}`}
/>
{paramErrors.adaptiveMaxFactor && (
{adaptiveConcurrency && paramErrors.adaptiveMaxFactor && (
<p className="text-xs text-red-500 mt-1">{paramErrors.adaptiveMaxFactor}</p>
)}
</div>
</div>

<label className="flex items-center gap-2 mt-3 text-sm">
<input
type="checkbox"
checked={adaptiveConcurrency}
onChange={(e) => setAdaptiveConcurrency(e.target.checked)}
className="rounded border-gray-300 text-teal-600 focus:ring-teal-500/30"
/>
Adaptive concurrency
<span className="text-xs text-gray-400">
(grow toward Max Concurrency; off = fixed concurrency)
</span>
</label>
<div>
<label className="block text-sm mb-1">Adaptive Healthy Window</label>
<p className="text-xs text-gray-400 mb-1">Healthy completions before each increase, &ge; 1</p>
<input
type="text"
value={adaptiveHealthyWindow}
disabled={!adaptiveConcurrency}
onChange={(e) => handleParamChange(
'adaptiveHealthyWindow',
e.target.value,
setAdaptiveHealthyWindow,
MIN_ADAPTIVE_HEALTHY_WINDOW,
undefined,
true,
)}
placeholder="e.g. 8"
className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed ${
adaptiveConcurrency && paramErrors.adaptiveHealthyWindow ? 'border-red-300' : 'border-gray-200'
}`}
/>
Comment thread
Jeffwan marked this conversation as resolved.
{adaptiveConcurrency && paramErrors.adaptiveHealthyWindow && (
<p className="text-xs text-red-500 mt-1">{paramErrors.adaptiveHealthyWindow}</p>
)}
</div>

<div>
<label className="block text-sm mb-1">Adaptive Additive Increase</label>
<p className="text-xs text-gray-400 mb-1">Fixed growth step after the first decrease, &ge; 1</p>
<input
type="text"
value={adaptiveAdditiveIncrease}
disabled={!adaptiveConcurrency}
onChange={(e) => handleParamChange(
'adaptiveAdditiveIncrease',
e.target.value,
setAdaptiveAdditiveIncrease,
MIN_ADAPTIVE_ADDITIVE_INCREASE,
undefined,
true,
)}
placeholder="e.g. 1"
className={`w-full px-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-teal-500/30 focus:border-teal-500 disabled:bg-gray-50 disabled:text-gray-400 disabled:cursor-not-allowed ${
adaptiveConcurrency && paramErrors.adaptiveAdditiveIncrease ? 'border-red-300' : 'border-gray-200'
}`}
/>
Comment thread
Jeffwan marked this conversation as resolved.
{adaptiveConcurrency && paramErrors.adaptiveAdditiveIncrease && (
<p className="text-xs text-red-500 mt-1">{paramErrors.adaptiveAdditiveIncrease}</p>
)}
</div>
</div>

<h4 className="text-sm font-medium mt-4 mb-2">Retry Policy</h4>
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm mb-1">Max Retries</label>
<p className="text-xs text-gray-400 mb-1">&ge; 0</p>
Expand Down
18 changes: 18 additions & 0 deletions apps/console/web/src/utils/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,24 @@ describe('api helpers', () => {
});
});

it('serializes adaptive AIMD settings to the job client contract', () => {
expect(camelToSnake({
client: {
adaptiveConcurrency: true,
adaptiveMaxFactor: 16,
adaptiveHealthyWindow: 8,
adaptiveAdditiveIncrease: 2,
},
})).toEqual({
client: {
adaptive_concurrency: true,
adaptive_max_factor: 16,
adaptive_healthy_window: 8,
adaptive_additive_increase: 2,
},
});
});

it('publishes the first jobs page while older pages are still loading', async () => {
let resolveSecondPage!: (response: Response) => void;
const secondPage = new Promise<Response>((resolve) => {
Expand Down
Loading