MotifA1-SFT / app.py
A03HCY's picture
Update app.py
b9fd4ed verified
Raw
History Blame Contribute Delete
18.4 kB
import gradio as gr
import torch
from typing import List, Dict, Generator, Tuple, Optional, Any
from codon.motif import MotifA1, MotifA1Tokenizer
from codon.utils.generate import chat, ChatChunk
from codon.utils.tokens import PackedTokenizer
LOCALIZATION = {
'English': {
'title': 'Motif-A1 Interactive Web Demo',
'subtitle': 'Explore the experimental 105.41M reasoning language model from the CodonProject. Built-in with native Chain-of-Thought (CoT) support.',
'model_control': 'Model Status',
'status_not_loaded': 'Loading model... Please wait...',
'status_loading': 'Loading model... Please wait...',
'status_loaded': 'Model and tokenizer successfully loaded!',
'gen_params': 'Generation Parameters',
'temp_label': 'Temperature',
'temp_info': 'Higher values increase creativity, lower values increase precision.',
'max_tokens_label': 'Max New Tokens',
'max_tokens_info': 'Maximum number of generated tokens.',
'top_p_label': 'Top P (Nucleus Sampling)',
'top_p_info': 'Set to 1.0 to disable. Keep below 1.0 for standard nucleus sampling.',
'top_k_label': 'Top K Sampling',
'top_k_info': 'Set to 0 to disable. Standard filter limit.',
'persona': 'Persona Setting',
'sys_prompt_label': 'System Prompt',
'sys_prompt_val': 'You are Motif-A1, an intelligent AI assistant developed by CodonProject. Think carefully step-by-step.',
'sys_prompt_placeholder': 'Type custom behavior or persona guidelines here...',
'chatbot_label': 'Motif-A1 Conversation Window',
'user_input_placeholder': 'Type your prompt here and press Enter (or click Send)...',
'submit_btn': 'Send',
'stop_btn': 'Stop',
'clear_btn': 'Clear History',
'thinking_active': 'Thinking Process (thinking...)',
'thinking_collapsed': 'Thinking Process (collapsed)',
'warning_not_loaded': 'Model is not loaded yet! Attempting auto-loading...',
'error_load_failed': 'Error: Model failed to load automatically.',
'error_gen_failed': 'Generation Error:'
},
'中文': {
'title': 'Motif-A1 交互式网页演示',
'subtitle': '探索由 CodonProject 开发的实验性 105.41M 推理大语言模型。内置原生思维链 (CoT) 支持。',
'model_control': '模型状态',
'status_not_loaded': '正在加载模型,请稍候...',
'status_loading': '正在加载模型,请稍候...',
'status_loaded': '模型和分词器已成功加载!',
'gen_params': '生成参数',
'temp_label': '温度 (Temperature)',
'temp_info': '较高的值增加创造力,较低的值提高精准度。',
'max_tokens_label': '最大生成长度 (Max New Tokens)',
'max_tokens_info': '生成文本的最大 Token 数量。',
'top_p_label': 'Top P (核采样)',
'top_p_info': '设置为 1.0 以禁用。保持小于 1.0 进行核采样。',
'top_k_label': 'Top K 采样',
'top_k_info': '设置为 0 以禁用。标准过滤器限制。',
'persona': '角色设定',
'sys_prompt_label': '系统提示词',
'sys_prompt_val': '你是由 CodonProject 开发的智能 AI 助手 Motif-A1。请一步步仔细思考。',
'sys_prompt_placeholder': '在此处输入自定义行为或角色设定指南...',
'chatbot_label': 'Motif-A1 对话窗口',
'user_input_placeholder': '在此输入您的内容并按回车(或点击发送)...',
'submit_btn': '发送',
'stop_btn': '停止',
'clear_btn': '清空历史',
'thinking_active': '思考过程 (思考中...)',
'thinking_collapsed': '思考过程 (已折叠)',
'warning_not_loaded': '模型尚未加载!正在尝试自动加载...',
'error_load_failed': '错误:模型自动加载失败。',
'error_gen_failed': '生成错误:'
}
}
class ModelManager:
'''
Manager class to handle model and tokenizer loading and generation.
Attributes:
model (Optional[MotifA1]): The language model instance.
tokenizer (Optional[PackedTokenizer]): The tokenizer instance.
device (torch.device): The device used for computation.
'''
def __init__(self) -> None:
self.model: Optional[MotifA1] = None
self.tokenizer: Optional[PackedTokenizer] = None
self.device: torch.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def load_model(self) -> str:
'''
Loads the model and tokenizer from remote source.
Returns:
str: Loading status message.
'''
try:
self.model = MotifA1().from_remote().to(self.device)
self.tokenizer = MotifA1Tokenizer().from_remote()
return f'Model and tokenizer successfully loaded on {self.device}!'
except Exception as e:
return f'Error loading model: {str(e)}'
def is_loaded(self) -> bool:
'''
Checks if both the model and tokenizer are loaded.
Returns:
bool: True if loaded, False otherwise.
'''
return self.model is not None and self.tokenizer is not None
# Global instance of ModelManager
model_manager = ModelManager()
def handle_user_message(
user_message: str,
chat_history: List[Dict[str, Any]],
clean_messages: List[Dict[str, str]]
) -> Tuple[str, List[Dict[str, Any]], List[Dict[str, str]]]:
'''
Handles the user input, adding it to the history list and cleaning the input box.
Args:
user_message (str): The raw text entered by the user.
chat_history (List[Dict[str, Any]]): Chatbot display history with role and content.
clean_messages (List[Dict[str, str]]): Clean dialog list for model context.
Returns:
Tuple[str, List[Dict[str, Any]], List[Dict[str, str]]]: Cleaned text, updated display history, and clean history.
'''
if not user_message.strip():
return '', chat_history, clean_messages
chat_history.append({'role': 'user', 'content': user_message})
clean_messages.append({'role': 'user', 'content': user_message})
chat_history.append({'role': 'assistant', 'content': ''})
return '', chat_history, clean_messages
def generate_response(
chat_history: List[Dict[str, Any]],
clean_messages: List[Dict[str, str]],
system_prompt: str,
temperature: float,
max_new_tokens: int,
top_p: float,
top_k: int,
language: str
) -> Generator[Tuple[List[Dict[str, Any]], List[Dict[str, str]]], None, None]:
'''
Generates model response in a streaming way and updates the UI in real time.
Args:
chat_history (List[Dict[str, Any]]): Chat history with UI HTML formatting.
clean_messages (List[Dict[str, str]]): Clean message history.
system_prompt (str): Custom system instruction.
temperature (float): Generation temperature.
max_new_tokens (int): Max response length.
top_p (float): Sampling probability threshold.
top_k (int): Top-K sampling filter.
language (str): UI language choice ('English' or '中文').
Yields:
Tuple[List[Dict[str, Any]], List[Dict[str, str]]]: Streaming updates to chatbot and clean state.
'''
loc = LOCALIZATION[language]
if not model_manager.is_loaded():
gr.Warning(loc['warning_not_loaded'])
status = model_manager.load_model()
if not model_manager.is_loaded():
chat_history[-1]['content'] = f'{loc["error_load_failed"]} {status}'
yield chat_history, clean_messages
return
tk: Optional[int] = int(top_k) if top_k > 0 else None
tp: Optional[float] = float(top_p) if top_p < 1.0 else None
messages_to_send: List[Dict[str, str]] = []
if system_prompt.strip():
messages_to_send.append({'role': 'system', 'content': system_prompt.strip()})
messages_to_send.extend(clean_messages)
cot_buffer = ''
response_buffer = ''
has_cot = False
try:
generator = chat(
model=model_manager.model,
tokenizer=model_manager.tokenizer,
device=model_manager.device,
messages=messages_to_send,
max_new_tokens=max_new_tokens,
temperature=temperature,
top_k=tk,
top_p=tp
)
for chunk in generator:
if chunk.is_cot:
has_cot = True
cot_buffer += chunk.content
else:
response_buffer += chunk.content
display_text = ''
if has_cot:
if chunk.is_cot:
display_text += (
f'<details open>\n'
f'<summary><b>{loc["thinking_active"]}</b></summary>\n\n'
f'<span style="color: #4a90e2; font-style: italic;">{cot_buffer}</span>\n\n'
f'</details>'
)
else:
display_text += (
f'<details>\n'
f'<summary><b>{loc["thinking_collapsed"]}</b></summary>\n\n'
f'<span style="color: #6c7a89; font-style: italic;">{cot_buffer}</span>\n\n'
f'</details>\n\n'
f'{response_buffer}'
)
else:
display_text += response_buffer
chat_history[-1]['content'] = display_text
yield chat_history, clean_messages
clean_messages.append({'role': 'assistant', 'content': response_buffer})
yield chat_history, clean_messages
except Exception as e:
error_msg = f'\n\n{loc["error_gen_failed"]} {str(e)}'
chat_history[-1]['content'] += error_msg
yield chat_history, clean_messages
def clear_history() -> Tuple[List[Any], List[Any]]:
'''
Clears both UI chat history and clean message history.
Returns:
Tuple[List[Any], List[Any]]: Two empty lists.
'''
return [], []
def autoload_callback(language: str) -> Generator[str, None, None]:
'''
Triggers automatically when the Web page is first opened to load remote model.
Args:
language (str): Default language choice.
Yields:
str: Automatic loading status message.
'''
loc = LOCALIZATION[language]
yield loc['status_loading']
status = model_manager.load_model()
if model_manager.is_loaded():
yield loc['status_loaded']
else:
yield f'{loc["error_load_failed"]} {status}'
def toggle_ui_language(lang: str) -> Tuple[gr.update, ...]:
'''
Switches UI element texts dynamically depending on language choice.
Args:
lang (str): 'English' or '中文'.
Returns:
Tuple[gr.update, ...]: Element updates.
'''
loc = LOCALIZATION[lang]
header_html = (
f'<div style="text-align: center; margin-bottom: 20px;">'
f'<h1 style="color: #2e4057; font-weight: 800; font-size: 2.5rem; margin-bottom: 10px;">{loc["title"]}</h1>'
f'<p style="color: #5f6c7d; font-size: 1.1rem;">{loc["subtitle"]}</p>'
f'</div>'
)
status_msg = loc['status_loaded'] if model_manager.is_loaded() else loc['status_not_loaded']
return (
gr.update(value=header_html),
gr.update(value=f'### {loc["model_control"]}'),
gr.update(label=loc['model_control'], value=status_msg),
gr.update(value=f'### {loc["gen_params"]}'),
gr.update(label=loc['temp_label'], info=loc['temp_info']),
gr.update(label=loc['max_tokens_label'], info=loc['max_tokens_info']),
gr.update(label=loc['top_p_label'], info=loc['top_p_info']),
gr.update(label=loc['top_k_label'], info=loc['top_k_info']),
gr.update(value=f'### {loc["persona"]}'),
gr.update(label=loc['sys_prompt_label'], value=loc['sys_prompt_val'], placeholder=loc['sys_prompt_placeholder']),
gr.update(label=loc['chatbot_label']),
gr.update(placeholder=loc['user_input_placeholder']),
gr.update(value=loc['submit_btn']),
gr.update(value=loc['stop_btn']),
gr.update(value=loc['clear_btn'])
)
# Setup UI Blocks
with gr.Blocks(title='Motif-A1 Interactive Web Demo', theme=gr.themes.Soft()) as demo:
# Header Section
header = gr.HTML(
'''
<div style="text-align: center; margin-bottom: 20px;">
<h1 style="color: #2e4057; font-weight: 800; font-size: 2.5rem; margin-bottom: 10px;">
Motif-A1 Interactive Web Demo
</h1>
<p style="color: #5f6c7d; font-size: 1.1rem;">
Explore the experimental 105.41M reasoning language model from the CodonProject. Built-in with native Chain-of-Thought (CoT) support.
</p>
</div>
'''
)
# Language Toggle Widget
with gr.Row():
lang_radio = gr.Radio(
choices=['English', '中文'],
value='English',
label='Language / 语言',
interactive=True
)
with gr.Row():
# Sidebar Panel (30% width)
with gr.Column(scale=3):
# Model Control Section
with gr.Group():
model_control_hdr = gr.Markdown('### Model Status')
status_box = gr.Textbox(
value='Loading model... Please wait...',
label='Model Status',
interactive=False
)
# Hyperparameters Section
with gr.Group():
gen_params_hdr = gr.Markdown('### Generation Parameters')
temp_slider = gr.Slider(
minimum=0.1,
maximum=1.5,
value=0.3,
step=0.05,
label='Temperature',
info='Higher values increase creativity, lower values increase precision.'
)
max_tokens_slider = gr.Slider(
minimum=64,
maximum=2048,
value=1024,
step=64,
label='Max New Tokens',
info='Maximum number of generated tokens.'
)
top_p_slider = gr.Slider(
minimum=0.0,
maximum=1.0,
value=0.95,
step=0.05,
label='Top P (Nucleus Sampling)',
info='Set to 1.0 to disable. Keep below 1.0 for standard nucleus sampling.'
)
top_k_slider = gr.Slider(
minimum=0,
maximum=100,
value=0,
step=1,
label='Top K Sampling',
info='Set to 0 to disable. Standard filter limit.'
)
# System Prompt Customization
with gr.Group():
persona_hdr = gr.Markdown('### Persona Setting')
sys_prompt_box = gr.Textbox(
value='You are Motif-A1, an intelligent AI assistant developed by CodonProject. Think carefully step-by-step.',
lines=3,
label='System Prompt',
placeholder='Type custom behavior or persona guidelines here...'
)
# Chatbot Panel (70% width)
with gr.Column(scale=7):
chatbot = gr.Chatbot(
label='Motif-A1 Conversation Window',
height=650
)
with gr.Row():
user_input = gr.Textbox(
placeholder='Type your prompt here and press Enter (or click Send)...',
lines=3,
scale=8,
label='Your Prompt',
show_label=False
)
with gr.Column(scale=2, min_width=100):
submit_btn = gr.Button('Send', variant='primary')
stop_btn = gr.Button('Stop', variant='stop')
clear_btn = gr.Button('Clear History')
# State variables to persist conversation histories
clean_state = gr.State(value=[])
# Core conversation event stream (Send button click)
submit_event = submit_btn.click(
fn=handle_user_message,
inputs=[user_input, chatbot, clean_state],
outputs=[user_input, chatbot, clean_state],
queue=False
).then(
fn=generate_response,
inputs=[chatbot, clean_state, sys_prompt_box, temp_slider, max_tokens_slider, top_p_slider, top_k_slider, lang_radio],
outputs=[chatbot, clean_state]
)
# Support textbox submission (Enter without Shift)
user_input_event = user_input.submit(
fn=handle_user_message,
inputs=[user_input, chatbot, clean_state],
outputs=[user_input, chatbot, clean_state],
queue=False
).then(
fn=generate_response,
inputs=[chatbot, clean_state, sys_prompt_box, temp_slider, max_tokens_slider, top_p_slider, top_k_slider, lang_radio],
outputs=[chatbot, clean_state]
)
# Stop button click triggers instant Cancellation of both possible submit flows
stop_btn.click(
fn=None,
inputs=None,
outputs=None,
cancels=[submit_event, user_input_event]
)
# Language Radio change trigger
lang_radio.change(
fn=toggle_ui_language,
inputs=[lang_radio],
outputs=[
header,
model_control_hdr,
status_box,
gen_params_hdr,
temp_slider,
max_tokens_slider,
top_p_slider,
top_k_slider,
persona_hdr,
sys_prompt_box,
chatbot,
user_input,
submit_btn,
stop_btn,
clear_btn
]
)
# Clear screen event
clear_btn.click(
fn=clear_history,
inputs=[],
outputs=[chatbot, clean_state],
queue=False
)
# Web page auto-load trigger on first open
demo.load(
fn=autoload_callback,
inputs=[lang_radio],
outputs=[status_box]
)
if __name__ == '__main__':
# Launch local server
demo.queue().launch()