Skip to content

Add SFT dashboard views and workload filtering - #519

Open
omkaark wants to merge 1 commit into
omkaark/sft-observabilityfrom
omkaark/sft-dashboard
Open

Add SFT dashboard views and workload filtering#519
omkaark wants to merge 1 commit into
omkaark/sft-observabilityfrom
omkaark/sft-dashboard

Conversation

@omkaark

@omkaark omkaark commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

SFT runs now show training loss and supervised targets instead of empty generated responses and reward charts. Adds an SFT/RL filter and labels, a loss chart using the existing chart component, and a Training samples tab that preserves exact target text and labels context separately.

Example run (dashboard here, Qwen3 32B, 8xH200, 1000 steps, 128 bsz, 4096 max ctx len, not tuned):

import hashlib

from pydantic.dataclasses import dataclass
from modal_training_gym import ModelArchitecture, Qwen3_8B_Recipe, SFTDataset
from modal_training_gym.common.models.base import HFModelConfiguration
from modal_training_gym import TrainConfig


class Qwen32B(HFModelConfiguration):
    model_name = "Qwen/Qwen3-32B"
    architecture = ModelArchitecture(
        num_layers=64,
        hidden_size=5120,
        ffn_hidden_size=25600,
        num_attention_heads=64,
        num_query_groups=8,
        kv_channels=128,
        vocab_size=151936,
        untie_embeddings_and_output_weights=True,
        rotary_base=1000000,
    )


@dataclass
class Qwen32BRecipe(Qwen3_8B_Recipe):
    make_vocab_size_divisible_by: int = 16
    optimizer_cpu_offload: bool = False
    pipeline_model_parallel_size: int = 1
    context_parallel_size: int = 1


class FilteredUltraChat(SFTDataset):
    def cache_key(self):
        key = f"{super().cache_key()}:qwen3-32b:max4096:v1"
        return hashlib.sha256(key.encode()).hexdigest()

    def _load_hf_dataset(self):
        from transformers import AutoTokenizer

        tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-32B")
        dataset = super()._load_hf_dataset()
        original_count = len(dataset)

        def fits(batch):
            tokens = tokenizer.apply_chat_template(
                batch["messages"], tokenize=True, return_dict=False
            )
            return [len(row) <= 4096 for row in tokens]

        dataset = dataset.filter(fits, batched=True, batch_size=128)
        print(
            f"UltraChat: kept {len(dataset)} / {original_count} conversations <=4096 tokens",
            flush=True,
        )
        return dataset


def config():
    return TrainConfig(
        model=Qwen32B(),
        dataset=FilteredUltraChat(
            hf_repo="HuggingFaceH4/ultrachat_200k",
            hf_split="train_sft",
            messages_column="messages",
        ),
        recipe=Qwen32BRecipe(
            training_type="sft",
            gpu_type="H200",
            num_gpus_per_node=8,
            tensor_model_parallel_size=8,
            sequence_parallel=True,
            ref_load="/checkpoints/torch_dist/Qwen--Qwen3-32B-tp8-div16-sft-proof",
            num_steps=1000,
            global_batch_size=128,
            max_tokens_per_gpu=4096,
            lr=1e-5,
            save_interval=250,
            shuffle=True,
            max_retries=5,
            image_overlay=lambda image: image.add_local_python_source(
                "sft_ultrachat", copy=True
            ),
            extra_config={"seed": 1234, "rollout_seed": 42},
        ),
    )


if __name__ == "__main__":
    from sft_ultrachat import config

    run = config().launch(prepare_inputs=True)
    print(f"Run ID: {run.training_run_id}")
    print(f"Modal app: {run.modal_app_id}")

Video:
https://github.com/user-attachments/assets/251abe78-4583-4e7c-9a52-2b32494f3fab

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

@omkaark
omkaark force-pushed the omkaark/sft-dashboard branch 3 times, most recently from 6d9d888 to 8e4c88e Compare September 8, 2026 03:58
@omkaark
omkaark marked this pull request as ready for review September 8, 2026 04:07
devin-ai-integration[bot]

This comment was marked as resolved.

@omkaark
omkaark force-pushed the omkaark/sft-dashboard branch from 338fceb to 94f6b2f Compare September 8, 2026 15:03
@omkaark
omkaark force-pushed the omkaark/sft-dashboard branch from 94f6b2f to 7a9751e Compare September 9, 2026 21:35

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 4 new potential issues.

Devin Review


export async function fetchRunSteps(trainingRunId, { signal } = {}) {
const res = await fetch(`${SERVER}/runs/${encodeURIComponent(trainingRunId)}/steps`, { signal });
if (!res.ok) return [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Failed loss requests erase history

An HTTP failure becomes an empty result in fetchRunSteps. The next poll erases loaded loss history and hides the reporting failure.

Suggested change
if (!res.ok) return [];
if (!res.ok) throw new Error(await getErrorFromResponse(res));
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +181 to +183
$effect(() => {
if (sft && activeTab === "rollouts") selectTab("summary");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Legacy sample links trap navigation

Opening an SFT rollouts deep link makes selectTab push a summary entry. Back restores rollouts, which immediately pushes summary again.

Prompt for agents
In dashboards/frontend/src/pages/TrainingRunDetailPage.svelte, normalize the unsupported rollouts tab for SFT without adding a history entry. Preserve the existing deep-link compatibility while ensuring Back can leave the run page. Consider replacing the current URL entry or making URL parsing workload-aware after the run type is known.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 194 to 195
if is_sft(args):
report_samples(rollout_id, args, samples)
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 SFT samples disappeared from this revision

The description promises a Training samples tab with target inspection and export. This revision hides that tab and stops producing its data.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread dashboards/frontend/src/components/SftProgress.svelte Outdated
@omkaark
omkaark force-pushed the omkaark/sft-dashboard branch 2 times, most recently from 9a94190 to 1919319 Compare September 10, 2026 00:11
@omkaark
omkaark force-pushed the omkaark/sft-dashboard branch from 1919319 to d9ab31d Compare September 10, 2026 19:58

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

⚠️ 1 issue in files not directly in the diff

⚠️ RL JSON reward contract breaks

run get --json removes current_reward from every RL payload and emits current_metric instead. Existing consumers read no reward or fail when they require the established key.

Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant